diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/loader | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/loader')
138 files changed, 32595 insertions, 0 deletions
diff --git a/Source/WebCore/loader/CachedMetadata.h b/Source/WebCore/loader/CachedMetadata.h new file mode 100644 index 0000000..120e4c0 --- /dev/null +++ b/Source/WebCore/loader/CachedMetadata.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CachedMetadata_h +#define CachedMetadata_h + +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + +// Metadata retrieved from the embedding application's cache. +// +// Serialized data is NOT portable across architectures. However, reading the +// data type ID will reject data generated with a different byte-order. +class CachedMetadata : public RefCounted<CachedMetadata> { +public: + static PassRefPtr<CachedMetadata> create(unsigned dataTypeID, const char* data, size_t size) + { + return adoptRef(new CachedMetadata(dataTypeID, data, size)); + } + + static PassRefPtr<CachedMetadata> deserialize(const char* data, size_t size) + { + return adoptRef(new CachedMetadata(data, size)); + } + + const Vector<char>& serialize() const + { + return m_serializedData; + } + + ~CachedMetadata() { } + + unsigned dataTypeID() const + { + return readUnsigned(dataTypeIDStart); + } + + const char* data() const + { + if (m_serializedData.size() < dataStart) + return 0; + return m_serializedData.data() + dataStart; + } + + size_t size() const + { + if (m_serializedData.size() < dataStart) + return 0; + return m_serializedData.size() - dataStart; + } + +private: + // Reads an unsigned value at position. Returns 0 on error. + unsigned readUnsigned(size_t position) const + { + if (m_serializedData.size() < position + sizeof(unsigned)) + return 0; + return *reinterpret_cast_ptr<unsigned*>(const_cast<char*>(m_serializedData.data() + position)); + } + + // Appends an unsigned value to the end of the serialized data. + void appendUnsigned(unsigned value) + { + m_serializedData.append(reinterpret_cast<const char*>(&value), sizeof(unsigned)); + } + + CachedMetadata(const char* data, size_t size) + { + // Serialized metadata should have non-empty data. + ASSERT(size > dataStart); + + m_serializedData.append(data, size); + } + + CachedMetadata(unsigned dataTypeID, const char* data, size_t size) + { + // Don't allow an ID of 0, it is used internally to indicate errors. + ASSERT(dataTypeID); + ASSERT(data); + + appendUnsigned(dataTypeID); + m_serializedData.append(data, size); + } + + // Serialization offsets. Format: [DATA_TYPE_ID][DATA]. + static const size_t dataTypeIDStart = 0; + static const size_t dataStart = sizeof(unsigned); + + // Since the serialization format supports random access, storing it in + // serialized form avoids need for a copy during serialization. + Vector<char> m_serializedData; +}; + +} + +#endif diff --git a/Source/WebCore/loader/CrossOriginAccessControl.cpp b/Source/WebCore/loader/CrossOriginAccessControl.cpp new file mode 100644 index 0000000..8ca821e --- /dev/null +++ b/Source/WebCore/loader/CrossOriginAccessControl.cpp @@ -0,0 +1,128 @@ +/* + * 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 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 "CrossOriginAccessControl.h" + +#include "HTTPParsers.h" +#include "ResourceResponse.h" +#include "SecurityOrigin.h" +#include <wtf/HashSet.h> +#include <wtf/Threading.h> +#include <wtf/text/AtomicString.h> + +namespace WebCore { + +bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method) +{ + return method == "GET" || method == "HEAD" || method == "POST"; +} + +bool isOnAccessControlSimpleRequestHeaderWhitelist(const String& name, const String& value) +{ + if (equalIgnoringCase(name, "accept") || equalIgnoringCase(name, "accept-language") || equalIgnoringCase(name, "content-language")) + return true; + + // Preflight is required for MIME types that can not be sent via form submission. + if (equalIgnoringCase(name, "content-type")) { + String mimeType = extractMIMETypeFromMediaType(value); + return equalIgnoringCase(mimeType, "application/x-www-form-urlencoded") + || equalIgnoringCase(mimeType, "multipart/form-data") + || equalIgnoringCase(mimeType, "text/plain"); + } + + return false; +} + +bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap) +{ + if (!isOnAccessControlSimpleRequestMethodWhitelist(method)) + return false; + + HTTPHeaderMap::const_iterator end = headerMap.end(); + for (HTTPHeaderMap::const_iterator it = headerMap.begin(); it != end; ++it) { + if (!isOnAccessControlSimpleRequestHeaderWhitelist(it->first, it->second)) + return false; + } + + return true; +} + +typedef HashSet<String, CaseFoldingHash> HTTPHeaderSet; +static PassOwnPtr<HTTPHeaderSet> createAllowedCrossOriginResponseHeadersSet() +{ + OwnPtr<HTTPHeaderSet> headerSet = adoptPtr(new HashSet<String, CaseFoldingHash>); + + headerSet->add("cache-control"); + headerSet->add("content-language"); + headerSet->add("content-type"); + headerSet->add("expires"); + headerSet->add("last-modified"); + headerSet->add("pragma"); + + return headerSet.release(); +} + +bool isOnAccessControlResponseHeaderWhitelist(const String& name) +{ + AtomicallyInitializedStatic(HTTPHeaderSet*, allowedCrossOriginResponseHeaders = createAllowedCrossOriginResponseHeadersSet().leakPtr()); + + return allowedCrossOriginResponseHeaders->contains(name); +} + +bool passesAccessControlCheck(const ResourceResponse& response, bool includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription) +{ + // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent, + // even with Access-Control-Allow-Credentials set to true. + const String& accessControlOriginString = response.httpHeaderField("Access-Control-Allow-Origin"); + if (accessControlOriginString == "*" && !includeCredentials) + return true; + + if (securityOrigin->isUnique()) { + errorDescription = "Cannot make any requests from " + securityOrigin->toString() + "."; + return false; + } + + // FIXME: Access-Control-Allow-Origin can contain a list of origins. + RefPtr<SecurityOrigin> accessControlOrigin = SecurityOrigin::createFromString(accessControlOriginString); + if (!accessControlOrigin->isSameSchemeHostPort(securityOrigin)) { + errorDescription = (accessControlOriginString == "*") ? "Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true." + : "Origin " + securityOrigin->toString() + " is not allowed by Access-Control-Allow-Origin."; + return false; + } + + if (includeCredentials) { + const String& accessControlCredentialsString = response.httpHeaderField("Access-Control-Allow-Credentials"); + if (accessControlCredentialsString != "true") { + errorDescription = "Credentials flag is true, but Access-Control-Allow-Credentials is not \"true\"."; + return false; + } + } + + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/CrossOriginAccessControl.h b/Source/WebCore/loader/CrossOriginAccessControl.h new file mode 100644 index 0000000..c44963b --- /dev/null +++ b/Source/WebCore/loader/CrossOriginAccessControl.h @@ -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 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 <wtf/Forward.h> + +namespace WebCore { + + class HTTPHeaderMap; + class ResourceResponse; + class SecurityOrigin; + + bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap&); + bool isOnAccessControlSimpleRequestMethodWhitelist(const String&); + bool isOnAccessControlSimpleRequestHeaderWhitelist(const String& name, const String& value); + bool isOnAccessControlResponseHeaderWhitelist(const String&); + + bool passesAccessControlCheck(const ResourceResponse&, bool includeCredentials, SecurityOrigin*, String& errorDescription); + +} // namespace WebCore diff --git a/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp b/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp new file mode 100644 index 0000000..18e4be2 --- /dev/null +++ b/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008, 2009 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 "CrossOriginPreflightResultCache.h" + +#include "CrossOriginAccessControl.h" +#include "ResourceResponse.h" +#include <wtf/CurrentTime.h> +#include <wtf/StdLibExtras.h> +#include <wtf/Threading.h> + +namespace WebCore { + +using namespace std; + +// These values are at the discretion of the user agent. +static const unsigned defaultPreflightCacheTimeoutSeconds = 5; +static const unsigned maxPreflightCacheTimeoutSeconds = 600; // Should be short enough to minimize the risk of using a poisoned cache after switching to a secure network. + +static bool parseAccessControlMaxAge(const String& string, unsigned& expiryDelta) +{ + // FIXME: this will not do the correct thing for a number starting with a '+' + bool ok = false; + expiryDelta = string.toUIntStrict(&ok); + return ok; +} + +template<class HashType> +static void addToAccessControlAllowList(const String& string, unsigned start, unsigned end, HashSet<String, HashType>& set) +{ + StringImpl* stringImpl = string.impl(); + if (!stringImpl) + return; + + // Skip white space from start. + while (start <= end && isSpaceOrNewline((*stringImpl)[start])) + ++start; + + // only white space + if (start > end) + return; + + // Skip white space from end. + while (end && isSpaceOrNewline((*stringImpl)[end])) + --end; + + set.add(string.substring(start, end - start + 1)); +} + +template<class HashType> +static bool parseAccessControlAllowList(const String& string, HashSet<String, HashType>& set) +{ + unsigned start = 0; + size_t end; + while ((end = string.find(',', start)) != notFound) { + if (start == end) + return false; + + addToAccessControlAllowList(string, start, end - 1, set); + start = end + 1; + } + if (start != string.length()) + addToAccessControlAllowList(string, start, string.length() - 1, set); + + return true; +} + +bool CrossOriginPreflightResultCacheItem::parse(const ResourceResponse& response, String& errorDescription) +{ + m_methods.clear(); + if (!parseAccessControlAllowList(response.httpHeaderField("Access-Control-Allow-Methods"), m_methods)) { + errorDescription = "Cannot parse Access-Control-Allow-Methods response header field."; + return false; + } + + m_headers.clear(); + if (!parseAccessControlAllowList(response.httpHeaderField("Access-Control-Allow-Headers"), m_headers)) { + errorDescription = "Cannot parse Access-Control-Allow-Headers response header field."; + return false; + } + + unsigned expiryDelta; + if (parseAccessControlMaxAge(response.httpHeaderField("Access-Control-Max-Age"), expiryDelta)) { + if (expiryDelta > maxPreflightCacheTimeoutSeconds) + expiryDelta = maxPreflightCacheTimeoutSeconds; + } else + expiryDelta = defaultPreflightCacheTimeoutSeconds; + + m_absoluteExpiryTime = currentTime() + expiryDelta; + return true; +} + +bool CrossOriginPreflightResultCacheItem::allowsCrossOriginMethod(const String& method, String& errorDescription) const +{ + if (m_methods.contains(method) || isOnAccessControlSimpleRequestMethodWhitelist(method)) + return true; + + errorDescription = "Method " + method + " is not allowed by Access-Control-Allow-Methods."; + return false; +} + +bool CrossOriginPreflightResultCacheItem::allowsCrossOriginHeaders(const HTTPHeaderMap& requestHeaders, String& errorDescription) const +{ + HTTPHeaderMap::const_iterator end = requestHeaders.end(); + for (HTTPHeaderMap::const_iterator it = requestHeaders.begin(); it != end; ++it) { + if (!m_headers.contains(it->first) && !isOnAccessControlSimpleRequestHeaderWhitelist(it->first, it->second)) { + errorDescription = "Request header field " + it->first.string() + " is not allowed by Access-Control-Allow-Headers."; + return false; + } + } + return true; +} + +bool CrossOriginPreflightResultCacheItem::allowsRequest(bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders) const +{ + String ignoredExplanation; + if (m_absoluteExpiryTime < currentTime()) + return false; + if (includeCredentials && !m_credentials) + return false; + if (!allowsCrossOriginMethod(method, ignoredExplanation)) + return false; + if (!allowsCrossOriginHeaders(requestHeaders, ignoredExplanation)) + return false; + return true; +} + +CrossOriginPreflightResultCache& CrossOriginPreflightResultCache::shared() +{ + DEFINE_STATIC_LOCAL(CrossOriginPreflightResultCache, cache, ()); + ASSERT(isMainThread()); + return cache; +} + +void CrossOriginPreflightResultCache::appendEntry(const String& origin, const KURL& url, PassOwnPtr<CrossOriginPreflightResultCacheItem> preflightResult) +{ + ASSERT(isMainThread()); + CrossOriginPreflightResultCacheItem* resultPtr = preflightResult.leakPtr(); + pair<CrossOriginPreflightResultHashMap::iterator, bool> addResult = m_preflightHashMap.add(make_pair(origin, url), resultPtr); + if (!addResult.second) { + // FIXME: We need to delete the old value before replacing with the new one. + addResult.first->second = resultPtr; + } +} + +bool CrossOriginPreflightResultCache::canSkipPreflight(const String& origin, const KURL& url, bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders) +{ + ASSERT(isMainThread()); + CrossOriginPreflightResultHashMap::iterator cacheIt = m_preflightHashMap.find(std::make_pair(origin, url)); + if (cacheIt == m_preflightHashMap.end()) + return false; + + if (cacheIt->second->allowsRequest(includeCredentials, method, requestHeaders)) + return true; + + delete cacheIt->second; + m_preflightHashMap.remove(cacheIt); + return false; +} + +void CrossOriginPreflightResultCache::empty() +{ + ASSERT(isMainThread()); + deleteAllValues(m_preflightHashMap); + m_preflightHashMap.clear(); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/CrossOriginPreflightResultCache.h b/Source/WebCore/loader/CrossOriginPreflightResultCache.h new file mode 100644 index 0000000..1016aed --- /dev/null +++ b/Source/WebCore/loader/CrossOriginPreflightResultCache.h @@ -0,0 +1,85 @@ +/* + * 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 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 CrossOriginPreflightResultCache_h +#define CrossOriginPreflightResultCache_h + +#include "KURLHash.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + + class HTTPHeaderMap; + class ResourceResponse; + + class CrossOriginPreflightResultCacheItem : public Noncopyable { + public: + CrossOriginPreflightResultCacheItem(bool credentials) + : m_absoluteExpiryTime(0) + , m_credentials(credentials) + { + } + + bool parse(const ResourceResponse&, String& errorDescription); + bool allowsCrossOriginMethod(const String&, String& errorDescription) const; + bool allowsCrossOriginHeaders(const HTTPHeaderMap&, String& errorDescription) const; + bool allowsRequest(bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders) const; + + private: + typedef HashSet<String, CaseFoldingHash> HeadersSet; + + // FIXME: A better solution to holding onto the absolute expiration time might be + // to start a timer for the expiration delta that removes this from the cache when + // it fires. + double m_absoluteExpiryTime; + bool m_credentials; + HashSet<String> m_methods; + HeadersSet m_headers; + }; + + class CrossOriginPreflightResultCache : public Noncopyable { + public: + static CrossOriginPreflightResultCache& shared(); + + void appendEntry(const String& origin, const KURL&, PassOwnPtr<CrossOriginPreflightResultCacheItem>); + bool canSkipPreflight(const String& origin, const KURL&, bool includeCredentials, const String& method, const HTTPHeaderMap& requestHeaders); + + void empty(); + + private: + CrossOriginPreflightResultCache() { } + + typedef HashMap<std::pair<String, KURL>, CrossOriginPreflightResultCacheItem*> CrossOriginPreflightResultHashMap; + + CrossOriginPreflightResultHashMap m_preflightHashMap; + }; + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/loader/DocumentLoadTiming.h b/Source/WebCore/loader/DocumentLoadTiming.h new file mode 100644 index 0000000..d4aa7d0 --- /dev/null +++ b/Source/WebCore/loader/DocumentLoadTiming.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2010 Google, 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 GOOGLE 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 GOOGLE 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 DocumentLoadTiming_h +#define DocumentLoadTiming_h + +namespace WebCore { + +struct DocumentLoadTiming { + DocumentLoadTiming() + : navigationStart(0.0) + , unloadEventStart(0.0) + , unloadEventEnd(0.0) + , redirectStart(0.0) + , redirectEnd(0.0) + , redirectCount(0) + , fetchStart(0.0) + , responseEnd(0.0) + , loadEventStart(0.0) + , loadEventEnd(0.0) + , hasCrossOriginRedirect(false) + , hasSameOriginAsPreviousDocument(false) + { + } + + double navigationStart; + double unloadEventStart; + double unloadEventEnd; + double redirectStart; + double redirectEnd; + short redirectCount; + double fetchStart; + double responseEnd; + double loadEventStart; + double loadEventEnd; + bool hasCrossOriginRedirect; + bool hasSameOriginAsPreviousDocument; +}; + +} + +#endif diff --git a/Source/WebCore/loader/DocumentLoader.cpp b/Source/WebCore/loader/DocumentLoader.cpp new file mode 100644 index 0000000..4e7c656 --- /dev/null +++ b/Source/WebCore/loader/DocumentLoader.cpp @@ -0,0 +1,840 @@ +/* + * 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" + +#include "ApplicationCacheHost.h" +#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 "CachedResourceLoader.h" +#include "DOMWindow.h" +#include "Document.h" +#include "DocumentParser.h" +#include "Event.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.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 <wtf/Assertions.h> +#include <wtf/text/CString.h> +#include <wtf/unicode/Unicode.h> + +namespace WebCore { + +static void cancelAll(const ResourceLoaderSet& loaders) +{ + Vector<RefPtr<ResourceLoader> > loadersCopy; + copyToVector(loaders, loadersCopy); + size_t size = loadersCopy.size(); + for (size_t i = 0; i < size; ++i) + loadersCopy[i]->cancel(); +} + +static void setAllDefersLoading(const ResourceLoaderSet& loaders, bool defers) +{ + Vector<RefPtr<ResourceLoader> > loadersCopy; + copyToVector(loaders, loadersCopy); + size_t size = loadersCopy.size(); + for (size_t i = 0; i < size; ++i) + loadersCopy[i]->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_wasOnloadHandled(false) + , m_stopRecordingResponses(false) + , m_substituteResourceDeliveryTimer(this, &DocumentLoader::substituteResourceDeliveryTimerFired) + , m_didCreateGlobalHistoryEntry(false) +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + , m_applicationCacheHost(adoptPtr(new ApplicationCacheHost(this))) +#endif +{ +} + +FrameLoader* DocumentLoader::frameLoader() const +{ + if (!m_frame) + return 0; + return m_frame->loader(); +} + +DocumentLoader::~DocumentLoader() +{ + ASSERT(!m_frame || frameLoader()->activeDocumentLoader() != this || !frameLoader()->isLoading()); +} + +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::replaceRequestURLForSameDocumentNavigation(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) +{ + ASSERT(!error.isNull()); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + m_applicationCacheHost->failedLoadingMainResource(); +#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(DatabasePolicy databasePolicy) +{ + // 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->parsing()) + m_frame->loader()->stopLoading(UnloadEventPolicyNone, databasePolicy); + } + + // Always cancel multipart loaders + cancelAll(m_multipartSubresourceLoaders); + + // Appcache uses ResourceHandle directly, DocumentLoader doesn't count these loads. +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + m_applicationCacheHost->stopLoadingInFrame(m_frame); +#endif + + 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(); + } +} + +void DocumentLoader::finishedLoading() +{ + m_gotFirstByte = true; + commitIfReady(); + if (FrameLoader* loader = frameLoader()) { + loader->finishedLoadingDocument(this); + loader->writer()->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(); + FrameLoader* frameLoader = DocumentLoader::frameLoader(); + if (!frameLoader) + return; +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + if (ArchiveFactory::isArchiveMimeType(response().mimeType())) + return; +#endif + frameLoader->client()->committedLoad(this, data, length); +} + +void DocumentLoader::commitData(const char* bytes, int length) +{ + // Set the text encoding. This is safe to call multiple times. + bool userChosen = true; + String encoding = overrideEncoding(); + if (encoding.isNull()) { + userChosen = false; + encoding = response().textEncodingName(); + } + // FIXME: DocumentWriter should be owned by DocumentLoader. + m_frame->loader()->writer()->setEncoding(encoding, userChosen); + ASSERT(m_frame->document()->parsing()); + m_frame->loader()->writer()->addData(bytes, 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()->writer()->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() +{ + if (!m_frame) { + setLoading(false); + return; + } + ASSERT(this == frameLoader()->activeDocumentLoader()); + bool wasLoading = m_loading; + setLoading(frameLoader()->isLoading()); + + if (wasLoading && !m_loading) { + if (DOMWindow* window = m_frame->existingDOMWindow()) + window->finishedLoading(); + } +} + +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); +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + m_applicationCacheHost->setDOMApplicationCache(0); +#endif + 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(); + m_mainResourceLoader = 0; + } + + if (this == frameLoader()->activeDocumentLoader()) + 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; + Document* doc = m_frame->document(); + if (doc->cachedResourceLoader()->requestCount()) + return true; + if (DocumentParser* parser = doc->parser()) + if (parser->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 = adoptPtr(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 = adoptPtr(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()->uniqueName()); +} + +PassRefPtr<ArchiveResource> DocumentLoader::subresource(const KURL& url) const +{ + if (!isCommitted()) + return 0; + + CachedResource* resource = m_frame->document()->cachedResourceLoader()->cachedResource(url); + if (!resource || !resource->isLoaded()) + return archiveResourceForURL(url); + + // FIXME: This has the side effect of making the resource non-purgeable. + // It would be better if it didn't have this permanent effect. + if (!resource->makePurgeable(false)) + return 0; + + RefPtr<SharedBuffer> data = resource->data(); + if (!data) + return 0; + + return ArchiveResource::create(data.release(), url, resource->response()); +} + +void DocumentLoader::getSubresources(Vector<PassRefPtr<ArchiveResource> >& subresources) const +{ + if (!isCommitted()) + return; + + Document* document = m_frame->document(); + + const CachedResourceLoader::DocumentResourceMap& allResources = document->cachedResourceLoader()->allCachedResources(); + CachedResourceLoader::DocumentResourceMap::const_iterator end = allResources.end(); + for (CachedResourceLoader::DocumentResourceMap::const_iterator it = allResources.begin(); it != end; ++it) { + RefPtr<ArchiveResource> subresource = this->subresource(KURL(ParsedURLString, 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(0); + } 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; + + if (m_pageTitle != title) { + frameLoader()->willChangeTitle(this); + m_pageTitle = title; + frameLoader()->didChangeTitle(this); + } +} + +void DocumentLoader::setIconURL(const String& iconURL) +{ + if (iconURL.isEmpty()) + return; + + if (m_pageIconURL != iconURL) { + m_pageIconURL = iconURL; + frameLoader()->didChangeIcons(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(); +} + +bool DocumentLoader::urlForHistoryReflectsFailure() const +{ + return m_substituteData.isValid() || m_response.httpStatusCode() >= 400; +} + +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()->addExtraFieldsToMainResourceRequest(m_request); + + 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::transferLoadingResourcesFromPage(Page* oldPage) +{ + ASSERT(oldPage != m_frame->page()); + + FrameLoader* loader = frameLoader(); + ASSERT(loader); + + const ResourceRequest& request = originalRequest(); + if (isLoadingMainResource()) { + loader->dispatchTransferLoadingResourceFromPage( + m_mainResourceLoader->identifier(), this, request, oldPage); + } + + if (isLoadingSubresources()) { + ResourceLoaderSet::const_iterator it = m_subresourceLoaders.begin(); + ResourceLoaderSet::const_iterator end = m_subresourceLoaders.end(); + for (; it != end; ++it) { + loader->dispatchTransferLoadingResourceFromPage( + (*it)->identifier(), this, request, oldPage); + } + } +} + +void DocumentLoader::iconLoadDecisionAvailable() +{ + if (m_frame) + m_frame->loader()->iconLoadDecisionAvailable(); +} + +} diff --git a/Source/WebCore/loader/DocumentLoader.h b/Source/WebCore/loader/DocumentLoader.h new file mode 100644 index 0000000..2328160 --- /dev/null +++ b/Source/WebCore/loader/DocumentLoader.h @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 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 "DocumentLoadTiming.h" +#include "NavigationAction.h" +#include "ResourceError.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "SubstituteData.h" +#include "Timer.h" + +namespace WebCore { + + class ApplicationCacheHost; +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + class Archive; + class ArchiveResource; + class ArchiveResourceCollection; +#endif + class Frame; + class FrameLoader; + class MainResourceLoader; + class Page; + class ResourceLoader; + class SchedulePair; + class SharedBuffer; + 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 replaceRequestURLForSameDocumentNavigation(const KURL&); + bool isStopping() const { return m_isStopping; } + void stopLoading(DatabasePolicy = DatabasePolicyStop); + 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; } + void handledOnloadEvents() { m_wasOnloadHandled = true; } + bool wasOnloadHandled() { return m_wasOnloadHandled; } + bool isLoadingInAPISense() const; + void setPrimaryLoadComplete(bool); + void setTitle(const String&); + void setIconURL(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; } + const String& iconURL() const { return m_pageIconURL; } + + KURL urlForHistory() const; + bool urlForHistoryReflectsFailure() const; + + // These accessors accommodate WebCore's somewhat fickle custom of creating history + // items for redirects, but only sometimes. For "source" and "destination", + // these accessors return the URL that would have been used if a history + // item were created. This allows WebKit to link history items reflecting + // redirects into a chain from start to finish. + String clientRedirectSourceForHistory() const { return m_clientRedirectSourceForHistory; } // null if no client redirect occurred. + String clientRedirectDestinationForHistory() const { return urlForHistory(); } + void setClientRedirectSourceForHistory(const String& clientedirectSourceForHistory) { m_clientRedirectSourceForHistory = clientedirectSourceForHistory; } + + String serverRedirectSourceForHistory() const { return urlForHistory() == url() ? String() : urlForHistory(); } // null if no server redirect occurred. + String serverRedirectDestinationForHistory() const { return url(); } + + bool didCreateGlobalHistoryEntry() const { return m_didCreateGlobalHistoryEntry; } + void setDidCreateGlobalHistoryEntry(bool didCreateGlobalHistoryEntry) { m_didCreateGlobalHistoryEntry = didCreateGlobalHistoryEntry; } + + 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 transferLoadingResourcesFromPage(Page*); + + void setDeferMainResourceDataLoad(bool defer) { m_deferMainResourceDataLoad = defer; } + bool deferMainResourceDataLoad() const { return m_deferMainResourceDataLoad; } + + void didTellClientAboutLoad(const String& url) + { + if (!url.isEmpty()) + m_resourcesClientKnowsAbout.add(url); + } + bool haveToldClientAboutLoad(const String& url) { return m_resourcesClientKnowsAbout.contains(url); } + void recordMemoryCacheLoadForFutureClientNotification(const String& url); + void takeMemoryCacheLoadsForClientNotification(Vector<String>& loads); + + DocumentLoadTiming* timing() { return &m_documentLoadTiming; } + void resetTiming() { m_documentLoadTiming = DocumentLoadTiming(); } + + // The WebKit layer calls this function when it's ready for the data to + // actually be added to the document. + void commitData(const char* bytes, int length); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + ApplicationCacheHost* applicationCacheHost() const { return m_applicationCacheHost.get(); } +#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_wasOnloadHandled; + + String m_pageTitle; + String m_pageIconURL; + + 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 + + HashSet<String> m_resourcesClientKnowsAbout; + Vector<String> m_resourcesLoadedFromMemoryCacheForClientNotification; + + String m_clientRedirectSourceForHistory; + bool m_didCreateGlobalHistoryEntry; + + DocumentLoadTiming m_documentLoadTiming; + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + friend class ApplicationCacheHost; // for substitute resource delivery + OwnPtr<ApplicationCacheHost> m_applicationCacheHost; +#endif + }; + + inline void DocumentLoader::recordMemoryCacheLoadForFutureClientNotification(const String& url) + { + m_resourcesLoadedFromMemoryCacheForClientNotification.append(url); + } + + inline void DocumentLoader::takeMemoryCacheLoadsForClientNotification(Vector<String>& loadsSet) + { + loadsSet.swap(m_resourcesLoadedFromMemoryCacheForClientNotification); + m_resourcesLoadedFromMemoryCacheForClientNotification.clear(); + } + +} + +#endif // DocumentLoader_h diff --git a/Source/WebCore/loader/DocumentThreadableLoader.cpp b/Source/WebCore/loader/DocumentThreadableLoader.cpp new file mode 100644 index 0000000..dee5001 --- /dev/null +++ b/Source/WebCore/loader/DocumentThreadableLoader.cpp @@ -0,0 +1,372 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DocumentThreadableLoader.h" + +#include "AuthenticationChallenge.h" +#include "CrossOriginAccessControl.h" +#include "CrossOriginPreflightResultCache.h" +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "ResourceHandle.h" +#include "ResourceLoadScheduler.h" +#include "ResourceRequest.h" +#include "SecurityOrigin.h" +#include "SubresourceLoader.h" +#include "ThreadableLoaderClient.h" +#include <wtf/UnusedParam.h> + +namespace WebCore { + +void DocumentThreadableLoader::loadResourceSynchronously(Document* document, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options) +{ + // The loader will be deleted as soon as this function exits. + RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, &client, LoadSynchronously, request, options)); + ASSERT(loader->hasOneRef()); +} + +PassRefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document* document, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options) +{ + RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, request, options)); + if (!loader->m_loader) + loader = 0; + return loader.release(); +} + +DocumentThreadableLoader::DocumentThreadableLoader(Document* document, ThreadableLoaderClient* client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options) + : m_client(client) + , m_document(document) + , m_options(options) + , m_sameOriginRequest(document->securityOrigin()->canRequest(request.url())) + , m_async(blockingBehavior == LoadAsynchronously) +{ + ASSERT(document); + ASSERT(client); + + if (m_sameOriginRequest || m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) { + loadRequest(request, DoSecurityCheck); + return; + } + + if (m_options.crossOriginRequestPolicy == DenyCrossOriginRequests) { + m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are not supported.")); + return; + } + + ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); + + OwnPtr<ResourceRequest> crossOriginRequest = adoptPtr(new ResourceRequest(request)); + crossOriginRequest->removeCredentials(); + crossOriginRequest->setAllowCookies(m_options.allowCredentials); + + if (!m_options.forcePreflight && isSimpleCrossOriginAccessRequest(crossOriginRequest->httpMethod(), crossOriginRequest->httpHeaderFields())) + makeSimpleCrossOriginAccessRequest(*crossOriginRequest); + else { + m_actualRequest = crossOriginRequest.release(); + + if (CrossOriginPreflightResultCache::shared().canSkipPreflight(document->securityOrigin()->toString(), m_actualRequest->url(), m_options.allowCredentials, m_actualRequest->httpMethod(), m_actualRequest->httpHeaderFields())) + preflightSuccess(); + else + makeCrossOriginAccessRequestWithPreflight(*m_actualRequest); + } +} + +void DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest(const ResourceRequest& request) +{ + ASSERT(isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields())); + + // Cross-origin requests are only defined for HTTP. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied. + if (!request.url().protocolInHTTPFamily()) { + m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are only supported for HTTP.")); + return; + } + + // Make a copy of the passed request so that we can modify some details. + ResourceRequest crossOriginRequest(request); + crossOriginRequest.setHTTPOrigin(m_document->securityOrigin()->toString()); + + loadRequest(crossOriginRequest, DoSecurityCheck); +} + +void DocumentThreadableLoader::makeCrossOriginAccessRequestWithPreflight(const ResourceRequest& request) +{ + ResourceRequest preflightRequest(request.url()); + preflightRequest.removeCredentials(); + preflightRequest.setHTTPOrigin(m_document->securityOrigin()->toString()); + preflightRequest.setAllowCookies(m_options.allowCredentials); + preflightRequest.setHTTPMethod("OPTIONS"); + preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod()); + + const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields(); + + if (requestHeaderFields.size() > 0) { + Vector<UChar> headerBuffer; + HTTPHeaderMap::const_iterator it = requestHeaderFields.begin(); + append(headerBuffer, it->first); + ++it; + + HTTPHeaderMap::const_iterator end = requestHeaderFields.end(); + for (; it != end; ++it) { + headerBuffer.append(','); + headerBuffer.append(' '); + append(headerBuffer, it->first); + } + + preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", String::adopt(headerBuffer)); + } + + loadRequest(preflightRequest, DoSecurityCheck); +} + +DocumentThreadableLoader::~DocumentThreadableLoader() +{ + if (m_loader) + m_loader->clearClient(); +} + +void DocumentThreadableLoader::cancel() +{ + if (!m_loader) + return; + + m_loader->cancel(); + m_loader->clearClient(); + m_loader = 0; + m_client = 0; +} + +void DocumentThreadableLoader::willSendRequest(SubresourceLoader* loader, ResourceRequest& request, const ResourceResponse&) +{ + ASSERT(m_client); + ASSERT_UNUSED(loader, loader == m_loader); + + if (!isAllowedRedirect(request.url())) { + RefPtr<DocumentThreadableLoader> protect(this); + m_client->didFailRedirectCheck(); + request = ResourceRequest(); + } +} + +void DocumentThreadableLoader::didSendData(SubresourceLoader* loader, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) +{ + ASSERT(m_client); + ASSERT_UNUSED(loader, loader == m_loader); + + m_client->didSendData(bytesSent, totalBytesToBeSent); +} + +void DocumentThreadableLoader::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response) +{ + ASSERT(m_client); + ASSERT_UNUSED(loader, loader == m_loader); + + String accessControlErrorDescription; + if (m_actualRequest) { + if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin(), accessControlErrorDescription)) { + preflightFailure(response.url(), accessControlErrorDescription); + return; + } + + OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult = adoptPtr(new CrossOriginPreflightResultCacheItem(m_options.allowCredentials)); + if (!preflightResult->parse(response, accessControlErrorDescription) + || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription) + || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) { + preflightFailure(response.url(), accessControlErrorDescription); + return; + } + + CrossOriginPreflightResultCache::shared().appendEntry(m_document->securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release()); + } else { + if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { + if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin(), accessControlErrorDescription)) { + m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, response.url().string(), accessControlErrorDescription)); + return; + } + } + + m_client->didReceiveResponse(response); + } +} + +void DocumentThreadableLoader::didReceiveData(SubresourceLoader* loader, const char* data, int lengthReceived) +{ + ASSERT(m_client); + ASSERT_UNUSED(loader, loader == m_loader); + + // Ignore response body of preflight requests. + if (m_actualRequest) + return; + + m_client->didReceiveData(data, lengthReceived); +} + +void DocumentThreadableLoader::didFinishLoading(SubresourceLoader* loader) +{ + ASSERT(loader == m_loader); + ASSERT(m_client); + didFinishLoading(loader->identifier()); +} + +void DocumentThreadableLoader::didFinishLoading(unsigned long identifier) +{ + if (m_actualRequest) { + ASSERT(!m_sameOriginRequest); + ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); + preflightSuccess(); + } else + m_client->didFinishLoading(identifier); +} + +void DocumentThreadableLoader::didFail(SubresourceLoader* loader, const ResourceError& error) +{ + ASSERT(m_client); + // m_loader may be null if we arrive here via SubresourceLoader::create in the ctor + ASSERT_UNUSED(loader, loader == m_loader || !m_loader); + + m_client->didFail(error); +} + +bool DocumentThreadableLoader::getShouldUseCredentialStorage(SubresourceLoader* loader, bool& shouldUseCredentialStorage) +{ + ASSERT_UNUSED(loader, loader == m_loader || !m_loader); + + if (!m_options.allowCredentials) { + shouldUseCredentialStorage = false; + return true; + } + + return false; // Only FrameLoaderClient can ultimately permit credential use. +} + +void DocumentThreadableLoader::didReceiveAuthenticationChallenge(SubresourceLoader* loader, const AuthenticationChallenge& challenge) +{ + ASSERT(loader == m_loader); + // Users are not prompted for credentials for cross-origin requests. + if (!m_sameOriginRequest) { +#if PLATFORM(MAC) || USE(CFNETWORK) || USE(CURL) + loader->handle()->receivedRequestToContinueWithoutCredential(challenge); +#else + // These platforms don't provide a way to continue without credentials, cancel the load altogether. + UNUSED_PARAM(challenge); + RefPtr<DocumentThreadableLoader> protect(this); + m_client->didFail(loader->blockedError()); + cancel(); +#endif + } +} + +void DocumentThreadableLoader::receivedCancellation(SubresourceLoader* loader, const AuthenticationChallenge& challenge) +{ + ASSERT(m_client); + ASSERT_UNUSED(loader, loader == m_loader); + m_client->didReceiveAuthenticationCancellation(challenge.failureResponse()); +} + +void DocumentThreadableLoader::preflightSuccess() +{ + OwnPtr<ResourceRequest> actualRequest; + actualRequest.swap(m_actualRequest); + + // It should be ok to skip the security check since we already asked about the preflight request. + loadRequest(*actualRequest, SkipSecurityCheck); +} + +void DocumentThreadableLoader::preflightFailure(const String& url, const String& errorDescription) +{ + m_actualRequest = 0; // Prevent didFinishLoading() from bypassing access check. + m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, url, errorDescription)); +} + +void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, SecurityCheckPolicy securityCheck) +{ + // Any credential should have been removed from the cross-site requests. + const KURL& requestURL = request.url(); + ASSERT(m_sameOriginRequest || requestURL.user().isEmpty()); + ASSERT(m_sameOriginRequest || requestURL.pass().isEmpty()); + + if (m_async) { + // Don't sniff content or send load callbacks for the preflight request. + bool sendLoadCallbacks = m_options.sendLoadCallbacks && !m_actualRequest; + bool sniffContent = m_options.sniffContent && !m_actualRequest; + + // Clear the loader so that any callbacks from SubresourceLoader::create will not have the old loader. + m_loader = 0; + m_loader = resourceLoadScheduler()->scheduleSubresourceLoad(m_document->frame(), this, request, ResourceLoadPriorityMedium, securityCheck, sendLoadCallbacks, sniffContent); + return; + } + + // FIXME: ThreadableLoaderOptions.sniffContent is not supported for synchronous requests. + StoredCredentials storedCredentials = m_options.allowCredentials ? AllowStoredCredentials : DoNotAllowStoredCredentials; + + Vector<char> data; + ResourceError error; + ResourceResponse response; + unsigned long identifier = std::numeric_limits<unsigned long>::max(); + if (m_document->frame()) + identifier = m_document->frame()->loader()->loadResourceSynchronously(request, storedCredentials, error, response, data); + + // No exception for file:/// resources, see <rdar://problem/4962298>. + // Also, if we have an HTTP response, then it wasn't a network error in fact. + if (!error.isNull() && !requestURL.isLocalFile() && response.httpStatusCode() <= 0) { + m_client->didFail(error); + return; + } + + // FIXME: FrameLoader::loadSynchronously() does not tell us whether a redirect happened or not, so we guess by comparing the + // request and response URLs. This isn't a perfect test though, since a server can serve a redirect to the same URL that was + // requested. Also comparing the request and response URLs as strings will fail if the requestURL still has its credentials. + if (requestURL != response.url() && !isAllowedRedirect(response.url())) { + m_client->didFailRedirectCheck(); + return; + } + + didReceiveResponse(0, response); + + const char* bytes = static_cast<const char*>(data.data()); + int len = static_cast<int>(data.size()); + didReceiveData(0, bytes, len); + + didFinishLoading(identifier); +} + +bool DocumentThreadableLoader::isAllowedRedirect(const KURL& url) +{ + if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) + return true; + + // FIXME: We need to implement access control for each redirect. This will require some refactoring though, because the code + // that processes redirects doesn't know about access control and expects a synchronous answer from its client about whether + // a redirect should proceed. + return m_sameOriginRequest && m_document->securityOrigin()->canRequest(url); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/DocumentThreadableLoader.h b/Source/WebCore/loader/DocumentThreadableLoader.h new file mode 100644 index 0000000..ebf3a25 --- /dev/null +++ b/Source/WebCore/loader/DocumentThreadableLoader.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DocumentThreadableLoader_h +#define DocumentThreadableLoader_h + +#include "FrameLoaderTypes.h" +#include "SubresourceLoaderClient.h" +#include "ThreadableLoader.h" +#include <wtf/Forward.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + class Document; + class KURL; + class ResourceRequest; + class ThreadableLoaderClient; + + class DocumentThreadableLoader : public RefCounted<DocumentThreadableLoader>, public ThreadableLoader, private SubresourceLoaderClient { + public: + static void loadResourceSynchronously(Document*, const ResourceRequest&, ThreadableLoaderClient&, const ThreadableLoaderOptions&); + static PassRefPtr<DocumentThreadableLoader> create(Document*, ThreadableLoaderClient*, const ResourceRequest&, const ThreadableLoaderOptions&); + virtual ~DocumentThreadableLoader(); + + virtual void cancel(); + + using RefCounted<DocumentThreadableLoader>::ref; + using RefCounted<DocumentThreadableLoader>::deref; + + protected: + virtual void refThreadableLoader() { ref(); } + virtual void derefThreadableLoader() { deref(); } + + private: + enum BlockingBehavior { + LoadSynchronously, + LoadAsynchronously + }; + + DocumentThreadableLoader(Document*, ThreadableLoaderClient*, BlockingBehavior blockingBehavior, const ResourceRequest&, const ThreadableLoaderOptions& options); + + 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 lengthReceived); + virtual void didFinishLoading(SubresourceLoader*); + virtual void didFail(SubresourceLoader*, const ResourceError&); + + virtual bool getShouldUseCredentialStorage(SubresourceLoader*, bool& shouldUseCredentialStorage); + virtual void didReceiveAuthenticationChallenge(SubresourceLoader*, const AuthenticationChallenge&); + virtual void receivedCancellation(SubresourceLoader*, const AuthenticationChallenge&); + + void didFinishLoading(unsigned long identifier); + void makeSimpleCrossOriginAccessRequest(const ResourceRequest& request); + void makeCrossOriginAccessRequestWithPreflight(const ResourceRequest& request); + void preflightSuccess(); + void preflightFailure(const String& url, const String& errorDescription); + + void loadRequest(const ResourceRequest&, SecurityCheckPolicy); + bool isAllowedRedirect(const KURL&); + + RefPtr<SubresourceLoader> m_loader; + ThreadableLoaderClient* m_client; + Document* m_document; + ThreadableLoaderOptions m_options; + bool m_sameOriginRequest; + bool m_async; + OwnPtr<ResourceRequest> m_actualRequest; // non-null during Access Control preflight checks + }; + +} // namespace WebCore + +#endif // DocumentThreadableLoader_h diff --git a/Source/WebCore/loader/DocumentWriter.cpp b/Source/WebCore/loader/DocumentWriter.cpp new file mode 100644 index 0000000..5b03cd7 --- /dev/null +++ b/Source/WebCore/loader/DocumentWriter.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2010. Adam Barth. 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 "DocumentWriter.h" + +#include "DOMImplementation.h" +#include "DOMWindow.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "FrameLoaderStateMachine.h" +#include "FrameView.h" +#include "PlaceholderDocument.h" +#include "PluginDocument.h" +#include "RawDataDocumentParser.h" +#include "ScriptableDocumentParser.h" +#include "SecurityOrigin.h" +#include "SegmentedString.h" +#include "Settings.h" +#include "SinkDocument.h" +#include "TextResourceDecoder.h" + + +namespace WebCore { + +static inline bool canReferToParentFrameEncoding(const Frame* frame, const Frame* parentFrame) +{ + return parentFrame && parentFrame->document()->securityOrigin()->canAccess(frame->document()->securityOrigin()); +} + +DocumentWriter::DocumentWriter(Frame* frame) + : m_frame(frame) + , m_receivedData(false) + , m_encodingWasChosenByUser(false) +{ +} + +// This is only called by ScriptController::executeIfJavaScriptURL +// and always contains the result of evaluating a javascript: url. +// This is the <iframe src="javascript:'html'"> case. +void DocumentWriter::replaceDocument(const String& source) +{ + m_frame->loader()->stopAllLoaders(); + begin(m_frame->loader()->url(), true, m_frame->document()->securityOrigin()); + + if (!source.isNull()) { + if (!m_receivedData) { + m_receivedData = true; + m_frame->document()->setCompatibilityMode(Document::NoQuirksMode); + } + + // FIXME: This should call DocumentParser::appendBytes instead of append + // to support RawDataDocumentParsers. + if (DocumentParser* parser = m_frame->document()->parser()) + parser->append(source); + } + + end(); +} + +void DocumentWriter::clear() +{ + m_decoder = 0; + m_receivedData = false; + if (!m_encodingWasChosenByUser) + m_encoding = String(); +} + +void DocumentWriter::begin() +{ + begin(KURL()); +} + +PassRefPtr<Document> DocumentWriter::createDocument(const KURL& url) +{ + if (!m_frame->loader()->stateMachine()->isDisplayingInitialEmptyDocument() && m_frame->loader()->client()->shouldUsePluginDocument(m_mimeType)) + return PluginDocument::create(m_frame, url); + if (!m_frame->loader()->client()->hasHTMLView()) + return PlaceholderDocument::create(m_frame, url); + return DOMImplementation::createDocument(m_mimeType, m_frame, url, m_frame->inViewSourceMode()); +} + +void DocumentWriter::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; + + // Create a new document before clearing the frame, because it may need to + // inherit an aliased security context. + RefPtr<Document> document = createDocument(url); + + // If the new document is for a Plugin but we're supposed to be sandboxed from Plugins, + // then replace the document with one whose parser will ignore the incoming data (bug 39323) + if (document->isPluginDocument() && m_frame->loader()->isSandboxed(SandboxPlugins)) + document = SinkDocument::create(m_frame, url); + + bool resetScripting = !(m_frame->loader()->stateMachine()->isDisplayingInitialEmptyDocument() && m_frame->document()->securityOrigin()->isSecureTransitionTo(url)); + m_frame->loader()->clear(resetScripting, resetScripting); + if (resetScripting) + m_frame->script()->updatePlatformScriptObjects(); + + m_frame->loader()->setURL(url); + m_frame->setDocument(document); + + 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()); + + m_frame->loader()->didBeginDocument(dispatch); + + document->implicitOpen(); + + if (m_frame->view() && m_frame->loader()->client()->hasHTMLView()) + m_frame->view()->setContentsSize(IntSize()); +} + +TextResourceDecoder* DocumentWriter::createDecoderIfNeeded() +{ + if (!m_decoder) { + if (Settings* settings = m_frame->settings()) { + m_decoder = TextResourceDecoder::create(m_mimeType, + settings->defaultTextEncodingName(), + settings->usesEncodingDetector()); + Frame* parentFrame = m_frame->tree()->parent(); + // Set the hint encoding to the parent frame encoding only if + // the parent and the current frames share the security origin. + // We impose this condition because somebody can make a child frame + // containing a carefully crafted html/javascript in one encoding + // that can be mistaken for hintEncoding (or related encoding) by + // an auto detector. When interpreted in the latter, it could be + // an attack vector. + // FIXME: This might be too cautious for non-7bit-encodings and + // we may consider relaxing this later after testing. + if (canReferToParentFrameEncoding(m_frame, parentFrame)) + m_decoder->setHintEncoding(parentFrame->document()->decoder()); + } else + m_decoder = TextResourceDecoder::create(m_mimeType, String()); + Frame* parentFrame = m_frame->tree()->parent(); + if (m_encoding.isEmpty()) { + if (canReferToParentFrameEncoding(m_frame, parentFrame)) + m_decoder->setEncoding(parentFrame->document()->inputEncoding(), TextResourceDecoder::EncodingFromParentFrame); + } else { + m_decoder->setEncoding(m_encoding, + m_encodingWasChosenByUser ? TextResourceDecoder::UserChosenEncoding : TextResourceDecoder::EncodingFromHTTPHeader); + } + m_frame->document()->setDecoder(m_decoder.get()); + } + return m_decoder.get(); +} + +void DocumentWriter::reportDataReceived() +{ + ASSERT(m_decoder); + if (!m_receivedData) { + m_receivedData = true; + if (m_decoder->encoding().usesVisualOrdering()) + m_frame->document()->setVisuallyOrdered(); + m_frame->document()->recalcStyle(Node::Force); + } +} + +void DocumentWriter::addData(const char* str, int len, bool flush) +{ + if (len == -1) + len = strlen(str); + + DocumentParser* parser = m_frame->document()->parser(); + if (parser) + parser->appendBytes(this, str, len, flush); +} + +void DocumentWriter::end() +{ + m_frame->loader()->didEndDocument(); + endIfNotLoadingMainResource(); +} + +void DocumentWriter::endIfNotLoadingMainResource() +{ + if (m_frame->loader()->isLoadingMainResource() || !m_frame->page() || !m_frame->document()) + 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 + addData(0, 0, true); + m_frame->document()->finishParsing(); +} + +String DocumentWriter::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(); +} + +void DocumentWriter::setEncoding(const String& name, bool userChosen) +{ + m_frame->loader()->willSetEncoding(); + m_encoding = name; + m_encodingWasChosenByUser = userChosen; +} + +void DocumentWriter::setDecoder(TextResourceDecoder* decoder) +{ + m_decoder = decoder; +} + +String DocumentWriter::deprecatedFrameEncoding() const +{ + return m_frame->loader()->url().isEmpty() ? m_encoding : encoding(); +} + +void DocumentWriter::setDocumentWasLoadedAsPartOfNavigation() +{ + m_frame->document()->parser()->setDocumentWasLoadedAsPartOfNavigation(); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/DocumentWriter.h b/Source/WebCore/loader/DocumentWriter.h new file mode 100644 index 0000000..5fb3dc1 --- /dev/null +++ b/Source/WebCore/loader/DocumentWriter.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2010. Adam Barth. 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 Adam Barth. ("Adam Barth") 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 DocumentWriter_h +#define DocumentWriter_h + +#include "KURL.h" +#include "PlatformString.h" + +namespace WebCore { + +class Document; +class Frame; +class SecurityOrigin; +class TextResourceDecoder; + +class DocumentWriter : public Noncopyable { +public: + DocumentWriter(Frame*); + + // This is only called by ScriptController::executeIfJavaScriptURL + // and always contains the result of evaluating a javascript: url. + void replaceDocument(const String&); + + void begin(); + void begin(const KURL&, bool dispatchWindowObjectAvailable = true, SecurityOrigin* forcedSecurityOrigin = 0); + void addData(const char* string, int length = -1, bool flush = false); + void end(); + void endIfNotLoadingMainResource(); + void clear(); + + String encoding() const; + void setEncoding(const String& encoding, bool userChosen); + + // FIXME: It's really unforunate to need to expose this piece of state. + // I suspect a better design is to disentangle user-provided encodings, + // default encodings, and the decoding we're currently using. + String deprecatedFrameEncoding() const; + + const String& mimeType() const { return m_mimeType; } + void setMIMEType(const String& type) { m_mimeType = type; } + + void setDecoder(TextResourceDecoder*); + + // Exposed for DoucmentParser::appendBytes + TextResourceDecoder* createDecoderIfNeeded(); + void reportDataReceived(); + + void setDocumentWasLoadedAsPartOfNavigation(); + +private: + PassRefPtr<Document> createDocument(const KURL&); + + Frame* m_frame; + + bool m_receivedData; + String m_mimeType; + + bool m_encodingWasChosenByUser; + String m_encoding; + RefPtr<TextResourceDecoder> m_decoder; +}; + +} // namespace WebCore + +#endif // DocumentWriter_h diff --git a/Source/WebCore/loader/EmptyClients.h b/Source/WebCore/loader/EmptyClients.h new file mode 100644 index 0000000..a9541b3 --- /dev/null +++ b/Source/WebCore/loader/EmptyClients.h @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2006 Eric Seidel (eric@webkit.org) + * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2010 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. + * + * 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 "Console.h" +#include "ContextMenuClient.h" +#include "DeviceMotionClient.h" +#include "DeviceOrientationClient.h" +#include "DocumentLoader.h" +#include "DragClient.h" +#include "EditCommand.h" +#include "EditorClient.h" +#include "FloatRect.h" +#include "FocusDirection.h" +#include "FrameLoaderClient.h" +#include "FrameNetworkingContext.h" +#include "InspectorClient.h" +#include "PluginHalterClient.h" +#include "PopupMenu.h" +#include "ResourceError.h" +#include "SearchPopupMenu.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 SharedGraphicsContext3D; + +class EmptyPopupMenu : public PopupMenu { +public: + virtual void show(const IntRect&, FrameView*, int) {} + virtual void hide() {} + virtual void updateFromElement() {} + virtual void disconnectClient() {} +}; + +class EmptySearchPopupMenu : public SearchPopupMenu { +public: + virtual PopupMenu* popupMenu() { return m_popup.get(); } + virtual void saveRecentSearches(const AtomicString&, const Vector<String>&) {} + virtual void loadRecentSearches(const AtomicString&, Vector<String>&) {} + virtual bool enabled() { return false; } + +private: + RefPtr<EmptyPopupMenu> m_popup; +}; + +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; } + +#if ENABLE(ANDROID_INSTALLABLE_WEB_APPS) + virtual void webAppCanBeInstalled() { } +#endif + + virtual void focus() { } + virtual void unfocus() { } + + virtual bool canTakeFocus(FocusDirection) { return false; } + virtual void takeFocus(FocusDirection) { } + + virtual void focusedNodeChanged(Node*) { } + virtual void focusedFrameChanged(Frame*) { } + + virtual Page* createWindow(Frame*, const FrameLoadRequest&, const WindowFeatures&, const NavigationAction&) { 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(MessageSource, MessageType, MessageLevel, const String&, unsigned, const String&) { } + + virtual bool canRunBeforeUnloadConfirmPanel() { return false; } + virtual bool runBeforeUnloadConfirmPanel(const String&, 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&, const String&, String&) { return false; } + virtual bool shouldInterruptJavaScript() { return false; } + + virtual bool selectItemWritingDirectionIsNatural() { return false; } + virtual PassRefPtr<PopupMenu> createPopupMenu(PopupMenuClient*) const { return adoptRef(new EmptyPopupMenu()); } + virtual PassRefPtr<SearchPopupMenu> createSearchPopupMenu(PopupMenuClient*) const { return adoptRef(new EmptySearchPopupMenu()); } + +#if ENABLE(CONTEXT_MENUS) + virtual void showContextMenu() { } +#endif + + virtual void setStatusbarText(const String&) { } + + virtual bool tabsToLinks() const { return false; } + + virtual IntRect windowResizerRect() const { return IntRect(); } + + virtual void invalidateWindow(const IntRect&, bool) { } + virtual void invalidateContentsAndWindow(const IntRect&, bool) { } + virtual void invalidateContentsForSlowScroll(const IntRect&, bool) {}; + virtual void scroll(const IntSize&, const IntRect&, const IntRect&) { } +#if ENABLE(TILED_BACKING_STORE) + virtual void delegatedScrollRequested(const IntSize&) { } +#endif + + virtual IntPoint screenToWindow(const IntPoint& p) const { return p; } + virtual IntRect windowToScreen(const IntRect& r) const { return r; } + virtual PlatformPageClient platformPageClient() const { return 0; } + virtual void contentsSizeChanged(Frame*, const IntSize&) const { } + + virtual void scrollbarsModeDidChange() const { } + virtual void mouseDidMoveOverElement(const HitTestResult&, unsigned) { } + + virtual void setToolTip(const String&, TextDirection) { } + + virtual void print(Frame*) { } + +#if ENABLE(DATABASE) + virtual void exceededDatabaseQuota(Frame*, const String&) { } +#endif + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + virtual void reachedMaxAppCacheSize(int64_t) { } + virtual void reachedApplicationCacheOriginQuota(SecurityOrigin*) { } +#endif + +#if ENABLE(NOTIFICATIONS) + virtual NotificationPresenter* notificationPresenter() const { return 0; } +#endif + + virtual void runOpenPanel(Frame*, PassRefPtr<FileChooser>) { } + virtual void chooseIconForFiles(const Vector<String>&, FileChooser*) { } + + virtual void formStateDidChange(const Node*) { } + + virtual void formDidFocus(const Node*) { } + virtual void formDidBlur(const Node*) { } + + virtual PassOwnPtr<HTMLParserQuirks> createHTMLParserQuirks() { return 0; } + + virtual void setCursor(const Cursor&) { } + + virtual void scrollRectIntoView(const IntRect&, const ScrollView*) const {} + + virtual void requestGeolocationPermissionForFrame(Frame*, Geolocation*) {} + virtual void cancelGeolocationPermissionRequestForFrame(Frame*, Geolocation*) {} + +#if USE(ACCELERATED_COMPOSITING) + virtual void attachRootGraphicsLayer(Frame*, GraphicsLayer*) {} + virtual void setNeedsOneShotDrawingSynchronization() {} + virtual void scheduleCompositingLayerSync() {} +#endif + +#if PLATFORM(WIN) + virtual void setLastSetCursorToCurrentCursor() { } +#endif +#if ENABLE(TOUCH_EVENTS) + virtual void needTouchEvents(bool) { } +#endif +}; + +class EmptyFrameLoaderClient : public FrameLoaderClient, public Noncopyable { +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 setCopiesOnScroll() { } + + virtual void detachedFromParent2() { } + virtual void detachedFromParent3() { } + + virtual void download(ResourceHandle*, const ResourceRequest&, const ResourceRequest&, const ResourceResponse&) { } + + virtual void assignIdentifierToInitialRequest(unsigned long, DocumentLoader*, const ResourceRequest&) { } + virtual bool shouldUseCredentialStorage(DocumentLoader*, unsigned long) { return false; } + virtual void dispatchWillSendRequest(DocumentLoader*, unsigned long, ResourceRequest&, const ResourceResponse&) { } + virtual void dispatchDidReceiveAuthenticationChallenge(DocumentLoader*, unsigned long, const AuthenticationChallenge&) { } + virtual void dispatchDidCancelAuthenticationChallenge(DocumentLoader*, unsigned long, const AuthenticationChallenge&) { } +#if USE(PROTECTION_SPACE_AUTH_CALLBACK) + virtual bool canAuthenticateAgainstProtectionSpace(DocumentLoader*, unsigned long, const ProtectionSpace&) { return false; } +#endif + virtual void dispatchDidReceiveResponse(DocumentLoader*, unsigned long, const ResourceResponse&) { } + virtual void dispatchDidReceiveContentLength(DocumentLoader*, unsigned long, int) { } + virtual void dispatchDidFinishLoading(DocumentLoader*, unsigned long) { } + virtual void dispatchDidFailLoading(DocumentLoader*, unsigned long, const ResourceError&) { } + virtual bool dispatchDidLoadResourceFromMemoryCache(DocumentLoader*, const ResourceRequest&, const ResourceResponse&, int) { return false; } + + virtual void dispatchDidHandleOnloadEvents() { } + virtual void dispatchDidReceiveServerRedirectForProvisionalLoad() { } + virtual void dispatchDidCancelClientRedirect() { } + virtual void dispatchWillPerformClientRedirect(const KURL&, double, double) { } + virtual void dispatchDidChangeLocationWithinPage() { } + virtual void dispatchDidPushStateWithinPage() { } + virtual void dispatchDidReplaceStateWithinPage() { } + virtual void dispatchDidPopStateWithinPage() { } + virtual void dispatchWillClose() { } + virtual void dispatchDidReceiveIcon() { } + virtual void dispatchDidStartProvisionalLoad() { } + virtual void dispatchDidReceiveTitle(const String&) { } + virtual void dispatchDidChangeIcons() { } + virtual void dispatchDidCommitLoad() { } + virtual void dispatchDidFailProvisionalLoad(const ResourceError&) { } + virtual void dispatchDidFailLoad(const ResourceError&) { } + virtual void dispatchDidFinishDocumentLoad() { } + virtual void dispatchDidFinishLoad() { } + virtual void dispatchDidFirstLayout() { } + virtual void dispatchDidFirstVisuallyNonEmptyLayout() { } + + virtual Frame* dispatchCreatePage(const NavigationAction&) { return 0; } + virtual void dispatchShow() { } + + virtual void dispatchDecidePolicyForMIMEType(FramePolicyFunction, const String&, const ResourceRequest&) { } + virtual void dispatchDecidePolicyForNewWindowAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>, const String&) { } + virtual void dispatchDecidePolicyForNavigationAction(FramePolicyFunction, const NavigationAction&, const ResourceRequest&, PassRefPtr<FormState>) { } + virtual void cancelPolicyCheck() { } + + virtual void dispatchUnableToImplementPolicy(const ResourceError&) { } + + virtual void dispatchWillSendSubmitEvent(HTMLFormElement*) { } + 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&) { ResourceError error("", 0, "", ""); error.setIsCancellation(true); return error; } + virtual ResourceError blockedError(const ResourceRequest&) { return ResourceError("", 0, "", ""); } + virtual ResourceError cannotShowURLError(const ResourceRequest&) { return ResourceError("", 0, "", ""); } + virtual ResourceError interruptForPolicyChangeError(const ResourceRequest&) { return ResourceError("", 0, "", ""); } + + virtual ResourceError cannotShowMIMETypeError(const ResourceResponse&) { return ResourceError("", 0, "", ""); } + virtual ResourceError fileDoesNotExistError(const ResourceResponse&) { return ResourceError("", 0, "", ""); } + virtual ResourceError pluginWillHandleLoadError(const ResourceResponse&) { return ResourceError("", 0, "", ""); } + + virtual bool shouldFallBack(const ResourceError&) { return false; } + + virtual bool canHandleRequest(const ResourceRequest&) const { return false; } + virtual bool canShowMIMEType(const String&) const { return false; } + virtual bool canShowMIMETypeAsHTML(const String&) const { return false; } + virtual bool representationExistsForURLScheme(const String&) const { return false; } + virtual String generatedMIMETypeForURLScheme(const String&) const { return ""; } + + virtual void frameLoadCompleted() { } + virtual void restoreViewState() { } + virtual void provisionalLoadStarted() { } + virtual bool shouldTreatURLAsSameAsCurrent(const KURL&) const { return false; } + 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&, const KURL&) { } + + virtual String userAgent(const KURL&) { return ""; } + + virtual void savePlatformDataToCachedFrame(CachedFrame*) { } + virtual void transitionToCommittedFromCachedFrame(CachedFrame*) { } + virtual void transitionToCommittedForNewPage() { } + + virtual void didSaveToPageCache() { } + virtual void didRestoreFromPageCache() { } + + virtual void dispatchDidBecomeFrameset(bool) { } + + virtual void updateGlobalHistory() { } + virtual void updateGlobalHistoryRedirectLinks() { } + virtual bool shouldGoToHistoryItem(HistoryItem*) const { return false; } + virtual void dispatchDidAddBackForwardItem(HistoryItem*) const { } + virtual void dispatchDidRemoveBackForwardItem(HistoryItem*) const { }; + virtual void dispatchDidChangeBackForwardIndex() const { } + virtual void saveViewStateToItem(HistoryItem*) { } + virtual bool canCachePage() const { return false; } + virtual void didDisplayInsecureContent() { } + virtual void didRunInsecureContent(SecurityOrigin*) { } + virtual PassRefPtr<Frame> createFrame(const KURL&, const String&, HTMLFrameOwnerElement*, const String&, bool, int, int) { return 0; } + virtual void didTransferChildFrameToNewDocument(Page*) { } + virtual void transferLoadingResourceFromPage(unsigned long, DocumentLoader*, const ResourceRequest&, Page*) { } + virtual PassRefPtr<Widget> createPlugin(const IntSize&, HTMLPlugInElement*, const KURL&, const Vector<String>&, const Vector<String>&, const String&, bool) { return 0; } + virtual PassRefPtr<Widget> createJavaAppletWidget(const IntSize&, HTMLAppletElement*, const KURL&, const Vector<String>&, const Vector<String>&) { return 0; } +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + virtual PassRefPtr<Widget> createMediaPlayerProxyPlugin(const IntSize&, HTMLMediaElement*, const KURL&, const Vector<String>&, const Vector<String>&, const String&) { return 0; } + virtual void hideMediaPlayerProxyPlugin(Widget*) { } + virtual void showMediaPlayerProxyPlugin(Widget*) { } +#endif + + virtual ObjectContentType objectContentType(const KURL&, const String&) { return ObjectContentType(); } + virtual String overrideMediaType() const { return String(); } + + virtual void redirectDataToPlugin(Widget*) { } + virtual void dispatchDidClearWindowObjectInWorld(DOMWrapperWorld*) { } + virtual void documentElementAvailable() { } + virtual void didPerformFirstNavigation() const { } + +#if USE(V8) + virtual void didCreateScriptContextForFrame() { } + virtual void didDestroyScriptContextForFrame() { } + virtual void didCreateIsolatedScriptContext() { } + virtual bool allowScriptExtension(const String& extensionName, int extensionGroup) { return false; } +#endif + + virtual void registerForIconNotification(bool) { } + +#ifdef ANDROID_APPLE_TOUCH_ICON + virtual void dispatchDidReceiveTouchIconURL(const String& url, bool precomposed) { } +#endif + +#if PLATFORM(MAC) + virtual RemoteAXObjectRef accessibilityRemoteObject() { return 0; } + virtual NSCachedURLResponse* willCacheResponse(DocumentLoader*, unsigned long, NSCachedURLResponse* response) const { return response; } +#endif +#if USE(CFNETWORK) + virtual bool shouldCacheResponse(DocumentLoader*, unsigned long, const ResourceResponse&, const unsigned char*, unsigned long long) { return true; } +#endif + + virtual PassRefPtr<FrameNetworkingContext> createNetworkingContext() { return PassRefPtr<FrameNetworkingContext>(); } +}; + +class EmptyEditorClient : public EditorClient, public Noncopyable { +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 isSelectTrailingWhitespaceEnabled() { 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*, Range*, EAffinity, bool) { 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; } + virtual DocumentFragment* documentFragmentFromAttributedString(NSAttributedString*, Vector<RefPtr<ArchiveResource> >&) { return 0; }; + virtual void setInsertionPasteboard(NSPasteboard*) { }; +#ifdef BUILDING_ON_TIGER + virtual NSArray* pasteboardTypesForSelection(Frame*) { return 0; } +#endif +#endif +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + virtual void uppercaseWord() { } + virtual void lowercaseWord() { } + virtual void capitalizeWord() { } + virtual void showSubstitutionsPanel(bool) { } + virtual bool substitutionsPanelIsShowing() { return false; } + virtual void toggleSmartInsertDelete() { } + virtual bool isAutomaticQuoteSubstitutionEnabled() { return false; } + virtual void toggleAutomaticQuoteSubstitution() { } + virtual bool isAutomaticLinkDetectionEnabled() { return false; } + virtual void toggleAutomaticLinkDetection() { } + virtual bool isAutomaticDashSubstitutionEnabled() { return false; } + virtual void toggleAutomaticDashSubstitution() { } + virtual bool isAutomaticTextReplacementEnabled() { return false; } + virtual void toggleAutomaticTextReplacement() { } + virtual bool isAutomaticSpellingCorrectionEnabled() { return false; } + virtual void toggleAutomaticSpellingCorrection() { } +#endif + virtual void ignoreWordInSpellDocument(const String&) { } + virtual void learnWord(const String&) { } + virtual void checkSpellingOfString(const UChar*, int, int*, int*) { } + virtual String getAutoCorrectSuggestionForMisspelledWord(const String&) { return String(); } + virtual void checkGrammarOfString(const UChar*, int, Vector<GrammarDetail>&, int*, int*) { } +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + virtual void checkTextOfParagraph(const UChar*, int, uint64_t, Vector<TextCheckingResult>&) { }; +#endif +#if SUPPORT_AUTOCORRECTION_PANEL + virtual void showCorrectionPanel(CorrectionPanelInfo::PanelType, const FloatRect&, const String&, const String&, const Vector<String>&, Editor*) { } + virtual void dismissCorrectionPanel(ReasonForDismissingCorrectionPanel) { } + virtual bool isShowingCorrectionPanel() { return false; } +#endif + virtual void updateSpellingUIWithGrammarString(const String&, const GrammarDetail&) { } + virtual void updateSpellingUIWithMisspelledWord(const String&) { } + virtual void showSpellingUI(bool) { } + virtual bool spellingUIIsShowing() { return false; } + virtual void getGuessesForWord(const String&, const String&, Vector<String>&) { } + virtual void willSetInputMethodState() { } + virtual void setInputMethodState(bool) { } + virtual void requestCheckingOfString(SpellChecker*, int, const String&) { } +}; + +#if ENABLE(CONTEXT_MENUS) +class EmptyContextMenuClient : public ContextMenuClient, public Noncopyable { +public: + virtual ~EmptyContextMenuClient() { } + virtual void contextMenuDestroyed() { } + +#if USE(CROSS_PLATFORM_CONTEXT_MENUS) + virtual PassOwnPtr<ContextMenu> customizeMenu(PassOwnPtr<ContextMenu>) { return 0; } +#else + virtual PlatformMenuDescription getCustomMenuFromDefaultItems(ContextMenu*) { return 0; } +#endif + virtual void contextMenuItemSelected(ContextMenuItem*, const ContextMenu*) { } + + virtual void downloadURL(const KURL&) { } + virtual void copyImageToClipboard(const HitTestResult&) { } + virtual void searchWithGoogle(const Frame*) { } + virtual void lookUpInDictionary(Frame*) { } + virtual bool isSpeaking() { return false; } + virtual void speak(const String&) { } + virtual void stopSpeaking() { } + +#if PLATFORM(MAC) + virtual void searchWithSpotlight() { } +#endif +}; +#endif // ENABLE(CONTEXT_MENUS) + +#if ENABLE(DRAG_SUPPORT) +class EmptyDragClient : public DragClient, public Noncopyable { +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&, Frame*) { return 0; } + virtual void dragControllerDestroyed() { } +}; +#endif // ENABLE(DRAG_SUPPORT) + +class EmptyInspectorClient : public InspectorClient, public Noncopyable { +public: + virtual ~EmptyInspectorClient() { } + + virtual void inspectorDestroyed() { } + + virtual void openInspectorFrontend(InspectorController*) { } + + virtual void highlight(Node*) { } + virtual void hideHighlight() { } + + virtual void populateSetting(const String&, String*) { } + virtual void storeSetting(const String&, const String&) { } + virtual bool sendMessageToFrontend(const String&) { return false; } +}; + +class EmptyDeviceMotionClient : public DeviceMotionClient { +public: + virtual void setController(DeviceMotionController*) { } + virtual void startUpdating() { } + virtual void stopUpdating() { } + virtual DeviceMotionData* currentDeviceMotion() const { return 0; } + virtual void deviceMotionControllerDestroyed() { } +}; + +class EmptyDeviceOrientationClient : public DeviceOrientationClient { +public: + virtual void setController(DeviceOrientationController*) { } + virtual void startUpdating() { } + virtual void stopUpdating() { } + virtual DeviceOrientation* lastOrientation() const { return 0; } + virtual void deviceOrientationControllerDestroyed() { } +}; + +} + +#endif // EmptyClients_h diff --git a/Source/WebCore/loader/FTPDirectoryParser.cpp b/Source/WebCore/loader/FTPDirectoryParser.cpp new file mode 100644 index 0000000..6a05c4c --- /dev/null +++ b/Source/WebCore/loader/FTPDirectoryParser.cpp @@ -0,0 +1,1715 @@ +/* + * 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 OS(WINDOWS) && (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) + +// Replacement for gmtime_r() which is not available on MinGW. +// We use this on Win32 Qt platform for portability. +struct tm gmtimeQt(const QDateTime& input) +{ + tm result; + + 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(); + + QTime time(input.time()); + result.tm_sec = time.second(); + result.tm_min = time.minute(); + result.tm_hour = time.hour(); + + return result; +} + +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 OS(WINDOWS) && !defined(gmtime_r) +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define gmtime_r(x, y) gmtime_s((y), (x)) +#else /* !_MSC_VER */ +#define gmtime_r(x,y) (gmtime(x)?(*(y)=*gmtime(x),(y)):0) +#endif +#endif + +static inline FTPEntryType ParsingFailed(ListState& state) +{ + 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 */ +} + +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[WTF_ARRAY_LENGTH(tokens)]; + 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 < WTF_ARRAY_LENGTH(tokens)) + { + 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++; + } + } + } + + if (!numtoks) + return ParsingFailed(state); + + linelen_sans_wsp = &(tokens[numtoks-1][toklen[numtoks-1]]) - tokens[0]; + if (numtoks == WTF_ARRAY_LENGTH(tokens)) + { + 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; +#if OS(WINDOWS) + sscanf(p + 1, "%I64u", &seconds); +#else + sscanf(p + 1, "%llu", &seconds); +#endif + 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)) + { + /* ']' was found and there is at least one character after it */ + ASSERT(*p == ']'); + pos++; + p++; + tokmarker = pos; /* length of leading "[DIR1.DIR2.etc]" */ + } else { + /* not a CMU style listing */ + lstyle = 0; + } + } + 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 == 0 || 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]); /* line end */ + result.caseSensitive = true; + result.filename = tokens[3]; + result.filenameLength = p - tokens[3]; + result.type = FTPDirectoryEntry; + + if (*tokens[2] != '<') /* not <DIR> or <JUNCTION> */ + { + // try to handle correctly spaces at the beginning of the filename + // filesize (token[2]) must end at offset 38 + if (tokens[2] + toklen[2] - line == 38) { + result.filename = &(line[39]); + result.filenameLength = p - result.filename; + } + result.type = FTPFileEntry; + pos = toklen[2]; + result.fileSize = String(tokens[2], pos); + } + else { + // try to handle correctly spaces at the beginning of the filename + // token[2] must begin at offset 24, the length is 5 or 10 + // token[3] must begin at offset 39 or higher + if (tokens[2] - line == 24 && (toklen[2] == 5 || toklen[2] == 10) && + tokens[3] - line >= 39) { + result.filename = &(line[39]); + result.filenameLength = p - result.filename; + } + + 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]) + - 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 year has only two digits then assume that + 00-79 is 2000-2079 + 80-99 is 1980-1999 */ + if (result.modifiedTime.tm_year < 80) + result.modifiedTime.tm_year += 2000; + else if (result.modifiedTime.tm_year < 100) + result.modifiedTime.tm_year += 1900; + } + + 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" + */ + + bool isOldHellsoft = false; + + 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 */ + if (toklen[0] == 10) + isOldHellsoft = true; + } + } + } + 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 (unsigned int i = 0; lstyle && i < toklen[tokmarker]; ++i) + { + if (!isASCIIDigit(*p++)) + lstyle = 0; + } + if (lstyle) + { + month_num = 0; + p = tokens[tokmarker+1]; + for (unsigned int i = 0; i < (12*3); i+=3) + { + if (p[0] == month_names[i+0] && + p[1] == month_names[i+1] && + p[2] == month_names[i+2]) + break; + month_num++; + } + if (month_num >= 12) + lstyle = 0; + } + } /* relative position test */ + } /* for (pos = (numtoks-5); !lstyle && pos > 1; pos--) */ + } /* if (lstyle == 'U') */ + + 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 */ + + // there is exactly 1 space between filename and previous token in all + // outputs except old Hellsoft + if (!isOldHellsoft) + result.filename = tokens[tokmarker+3] + toklen[tokmarker+3] + 1; + else + result.filename = tokens[tokmarker+4]; + + result.filenameLength = (&(line[linelen])) + - (result.filename); + + if (result.type == FTPLinkEntry && result.filenameLength > 4) + { + /* First try to use result.fe_size to find " -> " sequence. + This can give proper result for cases like "aaa -> bbb -> ccc". */ + unsigned int fileSize = result.fileSize.toUInt(); + + if (result.filenameLength > (fileSize + 4) && + strncmp(result.filename + result.filenameLength - fileSize - 4, " -> ", 4) == 0) + { + result.linkname = result.filename + (result.filenameLength - fileSize); + result.linknameLength = (&(line[linelen])) - (result.linkname); + result.filenameLength -= fileSize + 4; + } + else + { + /* Search for sequence " -> " from the end for case when there are + more occurrences. F.e. if ftpd returns "a -> b -> c" assume + "a -> b" as a name. Powerusers can remove unnecessary parts + manually but there is no way to follow the link when some + essential part is missing. */ + p = result.filename + (result.filenameLength - 5); + for (pos = (result.filenameLength - 5); pos > 0; pos--) + { + if (strncmp(p, " -> ", 4) == 0) + { + result.linkname = p + 4; + result.linknameLength = (&(line[linelen])) + - (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) */ + + return ParsingFailed(state); +} + +} // namespace WebCore + +#endif // ENABLE(FTPDIR) diff --git a/Source/WebCore/loader/FTPDirectoryParser.h b/Source/WebCore/loader/FTPDirectoryParser.h new file mode 100644 index 0000000..023a895 --- /dev/null +++ b/Source/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/Source/WebCore/loader/FormState.cpp b/Source/WebCore/loader/FormState.cpp new file mode 100644 index 0000000..552789a --- /dev/null +++ b/Source/WebCore/loader/FormState.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2006, 2009 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 "FormState.h" + +#include "Frame.h" +#include "HTMLFormElement.h" + +namespace WebCore { + +inline FormState::FormState(PassRefPtr<HTMLFormElement> form, StringPairVector& textFieldValuesToAdopt, PassRefPtr<Frame> sourceFrame, FormSubmissionTrigger formSubmissionTrigger) + : m_form(form) + , m_sourceFrame(sourceFrame) + , m_formSubmissionTrigger(formSubmissionTrigger) +{ + m_textFieldValues.swap(textFieldValuesToAdopt); +} + +PassRefPtr<FormState> FormState::create(PassRefPtr<HTMLFormElement> form, StringPairVector& textFieldValuesToAdopt, PassRefPtr<Frame> sourceFrame, FormSubmissionTrigger formSubmissionTrigger) +{ + return adoptRef(new FormState(form, textFieldValuesToAdopt, sourceFrame, formSubmissionTrigger)); +} + +} diff --git a/Source/WebCore/loader/FormState.h b/Source/WebCore/loader/FormState.h new file mode 100644 index 0000000..8f7166e --- /dev/null +++ b/Source/WebCore/loader/FormState.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2006, 2009 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 FormState_h +#define FormState_h + +#include "PlatformString.h" + +namespace WebCore { + + class Frame; + class HTMLFormElement; + + enum FormSubmissionTrigger { + SubmittedByJavaScript, + NotSubmittedByJavaScript + }; + + typedef Vector<std::pair<String, String> > StringPairVector; + + class FormState : public RefCounted<FormState> { + public: + static PassRefPtr<FormState> create(PassRefPtr<HTMLFormElement>, StringPairVector& textFieldValuesToAdopt, PassRefPtr<Frame>, FormSubmissionTrigger); + + HTMLFormElement* form() const { return m_form.get(); } + const StringPairVector& textFieldValues() const { return m_textFieldValues; } + Frame* sourceFrame() const { return m_sourceFrame.get(); } + FormSubmissionTrigger formSubmissionTrigger() const { return m_formSubmissionTrigger; } + + private: + FormState(PassRefPtr<HTMLFormElement>, StringPairVector& textFieldValuesToAdopt, PassRefPtr<Frame>, FormSubmissionTrigger); + + RefPtr<HTMLFormElement> m_form; + StringPairVector m_textFieldValues; + RefPtr<Frame> m_sourceFrame; + FormSubmissionTrigger m_formSubmissionTrigger; + }; + +} + +#endif // FormState_h diff --git a/Source/WebCore/loader/FormSubmission.cpp b/Source/WebCore/loader/FormSubmission.cpp new file mode 100644 index 0000000..44f9ff1 --- /dev/null +++ b/Source/WebCore/loader/FormSubmission.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "FormSubmission.h" + +#include "DOMFormData.h" +#include "Document.h" +#include "Event.h" +#include "FormData.h" +#include "FormDataBuilder.h" +#include "FormState.h" +#include "Frame.h" +#include "FrameLoadRequest.h" +#include "FrameLoader.h" +#include "HTMLFormControlElement.h" +#include "HTMLFormElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" +#include "HTMLParserIdioms.h" +#include "TextEncoding.h" +#include <wtf/CurrentTime.h> +#include <wtf/RandomNumber.h> + +namespace WebCore { + +using namespace HTMLNames; + +static int64_t generateFormDataIdentifier() +{ + // Initialize to the current time to reduce the likelihood of generating + // identifiers that overlap with those from past/future browser sessions. + static int64_t nextIdentifier = static_cast<int64_t>(currentTime() * 1000000.0); + return ++nextIdentifier; +} + +static void appendMailtoPostFormDataToURL(KURL& url, const FormData& data, const String& encodingType) +{ + String body = data.flattenToString(); + + if (equalIgnoringCase(encodingType, "text/plain")) { + // Convention seems to be to decode, and s/&/\r\n/. Also, spaces are encoded as %20. + body = decodeURLEscapeSequences(body.replace('&', "\r\n").replace('+', ' ') + "\r\n"); + } + + Vector<char> bodyData; + bodyData.append("body=", 5); + FormDataBuilder::encodeStringAsFormData(bodyData, body.utf8()); + body = String(bodyData.data(), bodyData.size()).replace('+', "%20"); + + String query = url.query(); + if (!query.isEmpty()) + query.append('&'); + query.append(body); + url.setQuery(query); +} + +void FormSubmission::Attributes::parseAction(const String& action) +{ + // FIXME: Can we parse into a KURL? + m_action = stripLeadingAndTrailingHTMLSpaces(action); +} + +void FormSubmission::Attributes::parseEncodingType(const String& type) +{ + if (type.contains("multipart", false) || type.contains("form-data", false)) { + m_encodingType = "multipart/form-data"; + m_isMultiPartForm = true; + } else if (type.contains("text", false) || type.contains("plain", false)) { + m_encodingType = "text/plain"; + m_isMultiPartForm = false; + } else { + m_encodingType = "application/x-www-form-urlencoded"; + m_isMultiPartForm = false; + } +} + +void FormSubmission::Attributes::parseMethodType(const String& type) +{ + if (equalIgnoringCase(type, "post")) + m_method = FormSubmission::PostMethod; + else if (equalIgnoringCase(type, "get")) + m_method = FormSubmission::GetMethod; +} + +void FormSubmission::Attributes::copyFrom(const Attributes& other) +{ + m_method = other.m_method; + m_isMultiPartForm = other.m_isMultiPartForm; + + m_action = other.m_action; + m_target = other.m_target; + m_encodingType = other.m_encodingType; + m_acceptCharset = other.m_acceptCharset; +} + +inline FormSubmission::FormSubmission(Method method, const KURL& action, const String& target, const String& contentType, PassRefPtr<FormState> state, PassRefPtr<FormData> data, const String& boundary, bool lockHistory, PassRefPtr<Event> event) + : m_method(method) + , m_action(action) + , m_target(target) + , m_contentType(contentType) + , m_formState(state) + , m_formData(data) + , m_boundary(boundary) + , m_lockHistory(lockHistory) + , m_event(event) +{ +} + +PassRefPtr<FormSubmission> FormSubmission::create(HTMLFormElement* form, const Attributes& attributes, PassRefPtr<Event> event, bool lockHistory, FormSubmissionTrigger trigger) +{ + ASSERT(form); + + HTMLFormControlElement* submitButton = 0; + if (event && event->target() && event->target()->toNode()) + submitButton = static_cast<HTMLFormControlElement*>(event->target()->toNode()); + + FormSubmission::Attributes copiedAttributes; + copiedAttributes.copyFrom(attributes); + if (submitButton) { + String attributeValue; + if (!(attributeValue = submitButton->getAttribute(formactionAttr)).isNull()) + copiedAttributes.parseAction(attributeValue); + if (!(attributeValue = submitButton->getAttribute(formenctypeAttr)).isNull()) + copiedAttributes.parseEncodingType(attributeValue); + if (!(attributeValue = submitButton->getAttribute(formmethodAttr)).isNull()) + copiedAttributes.parseMethodType(attributeValue); + if (!(attributeValue = submitButton->getAttribute(formtargetAttr)).isNull()) + copiedAttributes.setTarget(attributeValue); + } + + Document* document = form->document(); + KURL actionURL = document->completeURL(copiedAttributes.action().isEmpty() ? document->url().string() : copiedAttributes.action()); + bool isMailtoForm = actionURL.protocolIs("mailto"); + bool isMultiPartForm = false; + String encodingType = copiedAttributes.encodingType(); + + if (copiedAttributes.method() == PostMethod) { + isMultiPartForm = copiedAttributes.isMultiPartForm(); + if (isMultiPartForm && isMailtoForm) { + encodingType = "application/x-www-form-urlencoded"; + isMultiPartForm = false; + } + } + + TextEncoding dataEncoding = isMailtoForm ? UTF8Encoding() : FormDataBuilder::encodingFromAcceptCharset(copiedAttributes.acceptCharset(), document); + RefPtr<DOMFormData> domFormData = DOMFormData::create(dataEncoding.encodingForFormSubmission()); + Vector<pair<String, String> > formValues; + + for (unsigned i = 0; i < form->associatedElements().size(); ++i) { + FormAssociatedElement* control = form->associatedElements()[i]; + HTMLElement* element = toHTMLElement(control); + if (!element->disabled()) + control->appendFormData(*domFormData, isMultiPartForm); + if (element->hasLocalName(inputTag)) { + HTMLInputElement* input = static_cast<HTMLInputElement*>(control); + if (input->isTextField()) { + formValues.append(pair<String, String>(input->name(), input->value())); + if (input->isSearchField()) + input->addSearchResult(); + } + } + } + + RefPtr<FormData> formData; + String boundary; + + if (isMultiPartForm) { + formData = FormData::createMultiPart(*(static_cast<FormDataList*>(domFormData.get())), domFormData->encoding(), document); + boundary = formData->boundary().data(); + } else { + formData = FormData::create(*(static_cast<FormDataList*>(domFormData.get())), domFormData->encoding()); + if (copiedAttributes.method() == PostMethod && isMailtoForm) { + // Convert the form data into a string that we put into the URL. + appendMailtoPostFormDataToURL(actionURL, *formData, encodingType); + formData = FormData::create(); + } + } + + formData->setIdentifier(generateFormDataIdentifier()); + String targetOrBaseTarget = copiedAttributes.target().isEmpty() ? document->baseTarget() : copiedAttributes.target(); + RefPtr<FormState> formState = FormState::create(form, formValues, document->frame(), trigger); + return adoptRef(new FormSubmission(copiedAttributes.method(), actionURL, targetOrBaseTarget, encodingType, formState.release(), formData.release(), boundary, lockHistory, event)); +} + +KURL FormSubmission::requestURL() const +{ + if (m_method == FormSubmission::PostMethod) + return m_action; + + KURL requestURL(m_action); + requestURL.setQuery(m_formData->flattenToString()); + return requestURL; +} + +void FormSubmission::populateFrameLoadRequest(FrameLoadRequest& frameRequest) +{ + if (!m_target.isEmpty()) + frameRequest.setFrameName(m_target); + + if (!m_referrer.isEmpty()) + frameRequest.resourceRequest().setHTTPReferrer(m_referrer); + + if (m_method == FormSubmission::PostMethod) { + frameRequest.resourceRequest().setHTTPMethod("POST"); + frameRequest.resourceRequest().setHTTPBody(m_formData); + + // construct some user headers if necessary + if (m_contentType.isNull() || m_contentType == "application/x-www-form-urlencoded") + frameRequest.resourceRequest().setHTTPContentType(m_contentType); + else // contentType must be "multipart/form-data" + frameRequest.resourceRequest().setHTTPContentType(m_contentType + "; boundary=" + m_boundary); + } + + frameRequest.resourceRequest().setURL(requestURL()); + FrameLoader::addHTTPOriginIfNeeded(frameRequest.resourceRequest(), m_origin); +} + +} diff --git a/Source/WebCore/loader/FormSubmission.h b/Source/WebCore/loader/FormSubmission.h new file mode 100644 index 0000000..d724835 --- /dev/null +++ b/Source/WebCore/loader/FormSubmission.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FormSubmission_h +#define FormSubmission_h + +#include "FormState.h" +#include "KURL.h" + +namespace WebCore { + +class Document; +class Event; +class FormData; +struct FrameLoadRequest; +class HTMLFormElement; +class TextEncoding; + +class FormSubmission : public RefCounted<FormSubmission> { +public: + enum Method { GetMethod, PostMethod }; + + class Attributes : public Noncopyable { + public: + Attributes() + : m_method(GetMethod) + , m_isMultiPartForm(false) + , m_encodingType("application/x-www-form-urlencoded") + { + } + + Method method() const { return m_method; } + void parseMethodType(const String&); + + const String& action() const { return m_action; } + void parseAction(const String&); + + const String& target() const { return m_target; } + void setTarget(const String& target) { m_target = target; } + + const String& encodingType() const { return m_encodingType; } + void parseEncodingType(const String&); + bool isMultiPartForm() const { return m_isMultiPartForm; } + + const String& acceptCharset() const { return m_acceptCharset; } + void setAcceptCharset(const String& value) { m_acceptCharset = value; } + + void copyFrom(const Attributes&); + + private: + Method m_method; + bool m_isMultiPartForm; + + String m_action; + String m_target; + String m_encodingType; + String m_acceptCharset; + }; + + static PassRefPtr<FormSubmission> create(HTMLFormElement*, const Attributes&, PassRefPtr<Event> event, bool lockHistory, FormSubmissionTrigger); + + void populateFrameLoadRequest(FrameLoadRequest&); + + KURL requestURL() const; + + Method method() const { return m_method; } + const KURL& action() const { return m_action; } + const String& target() const { return m_target; } + void clearTarget() { m_target = String(); } + const String& contentType() const { return m_contentType; } + FormState* state() const { return m_formState.get(); } + FormData* data() const { return m_formData.get(); } + const String boundary() const { return m_boundary; } + bool lockHistory() const { return m_lockHistory; } + Event* event() const { return m_event.get(); } + + const String& referrer() const { return m_referrer; } + void setReferrer(const String& referrer) { m_referrer = referrer; } + const String& origin() const { return m_origin; } + void setOrigin(const String& origin) { m_origin = origin; } + +private: + FormSubmission(Method, const KURL& action, const String& target, const String& contentType, PassRefPtr<FormState>, PassRefPtr<FormData>, const String& boundary, bool lockHistory, PassRefPtr<Event>); + + // FIXME: Hold an instance of Attributes instead of individual members. + Method m_method; + KURL m_action; + String m_target; + String m_contentType; + RefPtr<FormState> m_formState; + RefPtr<FormData> m_formData; + String m_boundary; + bool m_lockHistory; + RefPtr<Event> m_event; + String m_referrer; + String m_origin; +}; + +} + +#endif // FormSubmission_h diff --git a/Source/WebCore/loader/FrameLoader.cpp b/Source/WebCore/loader/FrameLoader.cpp new file mode 100644 index 0000000..d11399c --- /dev/null +++ b/Source/WebCore/loader/FrameLoader.cpp @@ -0,0 +1,3566 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) 2008 Alp Toker <alp@atoker.com> + * Copyright (C) Research In Motion Limited 2009. 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 "FrameLoader.h" + +#include "ApplicationCacheHost.h" +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size +#include "Archive.h" +#include "ArchiveFactory.h" +#endif +#include "BackForwardController.h" +#include "BeforeUnloadEvent.h" +#include "MemoryCache.h" +#include "CachedPage.h" +#include "CachedResourceLoader.h" +#include "Chrome.h" +#include "DOMImplementation.h" +#include "DOMWindow.h" +#include "Document.h" +#include "DocumentLoadTiming.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 "FormSubmission.h" +#include "Frame.h" +#include "FrameLoadRequest.h" +#include "FrameLoaderClient.h" +#include "FrameNetworkingContext.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HTMLAnchorElement.h" +#include "HTMLFormElement.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 "PageTransitionEvent.h" +#include "PluginData.h" +#include "PluginDatabase.h" +#include "PluginDocument.h" +#include "ProgressTracker.h" +#include "ResourceHandle.h" +#include "ResourceRequest.h" +#include "SchemeRegistry.h" +#include "ScriptController.h" +#include "ScriptSourceCode.h" +#include "SecurityOrigin.h" +#include "SegmentedString.h" +#include "SerializedScriptValue.h" +#include "Settings.h" +#include "TextResourceDecoder.h" +#include "WindowFeatures.h" +#include "XMLDocumentParser.h" +#include <wtf/CurrentTime.h> +#include <wtf/StdLibExtras.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringConcatenate.h> + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#include "HTMLMediaElement.h" +#endif + +#if ENABLE(SHARED_WORKERS) +#include "SharedWorkerRepository.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" +#include "RenderArena.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +#if ENABLE(SVG) +using namespace SVGNames; +#endif + +#if ENABLE(XHTMLMP) +static const char defaultAcceptHeader[] = "application/xml,application/vnd.wap.xhtml+xml,application/xhtml+xml;profile='http://www.wapforum.org/xhtml',text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; +#else +static const char defaultAcceptHeader[] = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; +#endif + +static double storedTimeOfLastCompletedLoad; + +bool isBackForwardLoadType(FrameLoadType type) +{ + switch (type) { + case FrameLoadTypeStandard: + case FrameLoadTypeReload: + case FrameLoadTypeReloadFromOrigin: + case FrameLoadTypeSame: + case FrameLoadTypeRedirectWithLockedBackForwardList: + case FrameLoadTypeReplace: + return false; + case FrameLoadTypeBack: + case FrameLoadTypeBackWMLDeckNotAccessible: + case FrameLoadTypeForward: + case FrameLoadTypeIndexedBackForward: + return true; + } + ASSERT_NOT_REACHED(); + return false; +} + +static int numRequests(Document* document) +{ + if (!document) + return 0; + + return document->cachedResourceLoader()->requestCount(); +} + +// This is not in the FrameLoader class to emphasize that it does not depend on +// private FrameLoader data, and to avoid increasing the number of public functions +// with access to private data. Since only this .cpp file needs it, making it +// non-member lets us exclude it from the header file, thus keeping FrameLoader.h's +// API simpler. +// +// FIXME: isDocumentSandboxed should eventually replace isSandboxed. +static bool isDocumentSandboxed(Frame* frame, SandboxFlags mask) +{ + return frame->document() && frame->document()->securityOrigin()->isSandboxed(mask); +} + +FrameLoader::FrameLoader(Frame* frame, FrameLoaderClient* client) + : m_frame(frame) + , m_client(client) + , m_policyChecker(frame) + , m_history(frame) + , m_notifer(frame) + , m_writer(frame) + , m_subframeLoader(frame) + , m_state(FrameStateCommittedPage) + , m_loadType(FrameLoadTypeStandard) + , m_delegateIsHandlingProvisionalLoadError(false) + , m_quickRedirectComing(false) + , m_sentRedirectNotification(false) + , m_inStopAllLoaders(false) + , m_isExecutingJavaScriptFormAction(false) + , m_didCallImplicitClose(false) + , m_wasUnloadEventEmitted(false) + , m_pageDismissalEventBeingDispatched(false) + , m_isComplete(false) + , m_isLoadingMainResource(false) + , m_needsClear(false) + , m_checkTimer(this, &FrameLoader::checkTimerFired) + , m_shouldCallCheckCompleted(false) + , m_shouldCallCheckLoadComplete(false) + , m_opener(0) + , m_didPerformFirstNavigation(false) + , m_loadingFromCachedPage(false) + , m_suppressOpenerInNewFrame(false) + , m_sandboxFlags(SandboxAll) + , m_forcedSandboxFlags(SandboxNone) +{ +} + +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(); + + if (m_networkingContext) + m_networkingContext->invalidate(); +} + +void FrameLoader::init() +{ + // Propagate sandbox attributes to this Frameloader and its descendants. + // This needs to be done early, so that an initial document gets correct sandbox flags in its SecurityOrigin. + updateSandboxFlags(); + + // this somewhat odd set of steps is needed to give the frame an initial empty document + m_stateMachine.advanceTo(FrameLoaderStateMachine::CreatingInitialEmptyDocument); + setPolicyDocumentLoader(m_client->createDocumentLoader(ResourceRequest(KURL(ParsedURLString, "")), SubstituteData()).get()); + setProvisionalDocumentLoader(m_policyDocumentLoader.get()); + setState(FrameStateProvisional); + m_provisionalDocumentLoader->setResponse(ResourceResponse(KURL(), "text/html", 0, String(), String())); + m_provisionalDocumentLoader->finishedLoading(); + writer()->begin(KURL(), false); + writer()->end(); + m_frame->document()->cancelParsing(); + m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocument); + m_didCallImplicitClose = true; + + m_networkingContext = m_client->createNetworkingContext(); +} + +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); + + if (!defers) { + m_frame->navigationScheduler()->startTimer(); + startCheckCompleteTimer(); + } +} + +bool FrameLoader::canHandleRequest(const ResourceRequest& request) +{ + return m_client->canHandleRequest(request); +} + +void FrameLoader::changeLocation(PassRefPtr<SecurityOrigin> securityOrigin, const KURL& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool refresh) +{ + RefPtr<Frame> protect(m_frame); + urlSelected(FrameLoadRequest(securityOrigin, ResourceRequest(url, referrer, refresh ? ReloadIgnoringCacheData : UseProtocolCachePolicy), "_self"), + 0, lockHistory, lockBackForwardList, SendReferrer, ReplaceDocumentIfJavaScriptURL); +} + +void FrameLoader::urlSelected(const KURL& url, const String& passedTarget, PassRefPtr<Event> triggeringEvent, bool lockHistory, bool lockBackForwardList, ReferrerPolicy referrerPolicy) +{ + urlSelected(FrameLoadRequest(m_frame->document()->securityOrigin(), ResourceRequest(url), passedTarget), + triggeringEvent, lockHistory, lockBackForwardList, referrerPolicy, DoNotReplaceDocumentIfJavaScriptURL); +} + +// The shouldReplaceDocumentIfJavaScriptURL parameter will go away when the FIXME to eliminate the +// corresponding parameter from ScriptController::executeIfJavaScriptURL() is addressed. +void FrameLoader::urlSelected(const FrameLoadRequest& passedRequest, PassRefPtr<Event> triggeringEvent, bool lockHistory, bool lockBackForwardList, ReferrerPolicy referrerPolicy, ShouldReplaceDocumentIfJavaScriptURL shouldReplaceDocumentIfJavaScriptURL) +{ + ASSERT(!m_suppressOpenerInNewFrame); + + FrameLoadRequest frameRequest(passedRequest); + + if (m_frame->script()->executeIfJavaScriptURL(frameRequest.resourceRequest().url(), shouldReplaceDocumentIfJavaScriptURL)) + return; + + if (frameRequest.frameName().isEmpty()) + frameRequest.setFrameName(m_frame->document()->baseTarget()); + + if (referrerPolicy == NoReferrer) + m_suppressOpenerInNewFrame = true; + if (frameRequest.resourceRequest().httpReferrer().isEmpty()) + frameRequest.resourceRequest().setHTTPReferrer(m_outgoingReferrer); + addHTTPOriginIfNeeded(frameRequest.resourceRequest(), outgoingOrigin()); + + loadFrameRequest(frameRequest, lockHistory, lockBackForwardList, triggeringEvent, 0, referrerPolicy); + + m_suppressOpenerInNewFrame = false; +} + +void FrameLoader::submitForm(PassRefPtr<FormSubmission> submission) +{ + ASSERT(submission->method() == FormSubmission::PostMethod || submission->method() == FormSubmission::GetMethod); + + // FIXME: Find a good spot for these. + ASSERT(submission->data()); + ASSERT(submission->state()); + ASSERT(submission->state()->sourceFrame() == m_frame); + + if (!m_frame->page()) + return; + + if (submission->action().isEmpty()) + return; + + if (isDocumentSandboxed(m_frame, SandboxForms)) + return; + + if (protocolIsJavaScript(submission->action())) { + m_isExecutingJavaScriptFormAction = true; + m_frame->script()->executeIfJavaScriptURL(submission->action(), DoNotReplaceDocumentIfJavaScriptURL); + m_isExecutingJavaScriptFormAction = false; + return; + } + + Frame* targetFrame = m_frame->tree()->find(submission->target()); + if (!shouldAllowNavigation(targetFrame)) + return; + if (!targetFrame) { + if (!DOMWindow::allowPopUp(m_frame) && !isProcessingUserGesture()) + return; + + targetFrame = m_frame; + } else + submission->clearTarget(); + + if (!targetFrame->page()) + return; + + // 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. + + if (m_frame->tree()->isDescendantOf(targetFrame)) { + if (m_submittedFormURL == submission->action()) + return; + m_submittedFormURL = submission->action(); + } + + submission->data()->generateFiles(m_frame->document()); + submission->setReferrer(m_outgoingReferrer); + submission->setOrigin(outgoingOrigin()); + + targetFrame->navigationScheduler()->scheduleFormSubmission(submission); +} + +void FrameLoader::stopLoading(UnloadEventPolicy unloadEventPolicy, DatabasePolicy databasePolicy) +{ + if (m_frame->document() && m_frame->document()->parser()) + m_frame->document()->parser()->stopParsing(); + + if (unloadEventPolicy != UnloadEventPolicyNone) { + if (m_frame->document()) { + if (m_didCallImplicitClose && !m_wasUnloadEventEmitted) { + Node* currentFocusedNode = m_frame->document()->focusedNode(); + if (currentFocusedNode) + currentFocusedNode->aboutToUnload(); + m_pageDismissalEventBeingDispatched = true; + if (m_frame->domWindow()) { + if (unloadEventPolicy == UnloadEventPolicyUnloadAndPageHide) + m_frame->domWindow()->dispatchEvent(PageTransitionEvent::create(eventNames().pagehideEvent, m_frame->document()->inPageCache()), m_frame->document()); + if (!m_frame->document()->inPageCache()) { + RefPtr<Event> unloadEvent(Event::create(eventNames().unloadEvent, false, false)); + // The DocumentLoader (and thus its DocumentLoadTiming) might get destroyed + // while dispatching the event, so protect it to prevent writing the end + // time into freed memory. + RefPtr<DocumentLoader> documentLoader = m_provisionalDocumentLoader; + if (documentLoader && !documentLoader->timing()->unloadEventStart && !documentLoader->timing()->unloadEventEnd) { + DocumentLoadTiming* timing = documentLoader->timing(); + ASSERT(timing->navigationStart); + m_frame->domWindow()->dispatchTimedEvent(unloadEvent, m_frame->domWindow()->document(), &timing->unloadEventStart, &timing->unloadEventEnd); + ASSERT(timing->unloadEventStart >= timing->navigationStart); + } else + m_frame->domWindow()->dispatchEvent(unloadEvent, m_frame->domWindow()->document()); + } + } + m_pageDismissalEventBeingDispatched = false; + if (m_frame->document()) + m_frame->document()->updateStyleIfNeeded(); + m_wasUnloadEventEmitted = true; + } + } + + // Dispatching the unload event could have made m_frame->document() null. + if (m_frame->document() && !m_frame->document()->inPageCache()) { + // Don't remove event listeners from a transitional empty document (see bug 28716 for more information). + bool keepEventListeners = m_stateMachine.isDisplayingInitialEmptyDocument() && m_provisionalDocumentLoader + && m_frame->document()->securityOrigin()->isSecureTransitionTo(m_provisionalDocumentLoader->url()); + + if (!keepEventListeners) + m_frame->document()->removeAllEventListeners(); + } + } + + m_isComplete = true; // to avoid calling completed() in finishedParsing() + m_isLoadingMainResource = false; + m_didCallImplicitClose = true; // don't want that one either + + if (m_frame->document() && m_frame->document()->parsing()) { + finishedParsing(); + m_frame->document()->setParsing(false); + } + + m_workingURL = KURL(); + + if (Document* doc = m_frame->document()) { + // FIXME: HTML5 doesn't tell us to set the state to complete when aborting, but we do anyway to match legacy behavior. + // http://www.w3.org/Bugs/Public/show_bug.cgi?id=10537 + doc->setReadyState(Document::Complete); + + if (CachedResourceLoader* cachedResourceLoader = doc->cachedResourceLoader()) + cachedResourceLoader->cancelRequests(); + +#if ENABLE(DATABASE) + if (databasePolicy == DatabasePolicyStop) + doc->stopDatabases(0); +#else + UNUSED_PARAM(databasePolicy); +#endif + } + + // FIXME: This will cancel redirection timer, which really needs to be restarted when restoring the frame from b/f cache. + m_frame->navigationScheduler()->cancel(); +} + +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()->parser()) + m_frame->document()->parser()->stopParsing(); + m_frame->document()->finishParsing(); + + if (m_iconLoader) + m_iconLoader->stopLoading(); +} + +bool FrameLoader::closeURL() +{ + history()->saveDocumentState(); + + // Should only send the pagehide event here if the current document exists and has not been placed in the page cache. + Document* currentDocument = m_frame->document(); + stopLoading(currentDocument && !currentDocument->inPageCache() ? UnloadEventPolicyUnloadAndPageHide : UnloadEventPolicyUnloadOnly); + + m_frame->editor()->clearUndoRedoOperations(); + return true; +} + +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()->iconURL().isEmpty()) + return KURL(ParsedURLString, m_frame->document()->iconURL()); + + // Don't return a favicon iconURL unless we're http or https + if (!m_URL.protocolInHTTPFamily()) + return KURL(); + + KURL url; + bool couldSetProtocol = url.setProtocol(m_URL.protocol()); + ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); + url.setHost(m_URL.host()); + if (m_URL.hasPort()) + url.setPort(m_URL.port()); + url.setPath("/favicon.ico"); + return url; +} + +bool FrameLoader::didOpenURL(const KURL& url) +{ + if (m_frame->navigationScheduler()->redirectScheduledDuringLoad()) { + // A redirect was scheduled before the document was created. + // This can happen when one frame changes another frame's location. + return false; + } + + m_frame->navigationScheduler()->cancel(); + m_frame->editor()->clearLastEditCommand(); + + m_isComplete = false; + m_isLoadingMainResource = true; + m_didCallImplicitClose = false; + + // If we are still in the process of initializing an empty document then + // its frame is not in a consistent state for rendering, so avoid setJSStatusBarText + // since it may cause clients to attempt to render the frame. + if (!m_stateMachine.creatingInitialEmptyDocument()) { + if (DOMWindow* window = m_frame->existingDOMWindow()) { + window->setStatus(String()); + window->setDefaultStatus(String()); + } + } + m_URL = url; + if (m_URL.protocolInHTTPFamily() && !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. + if (!m_stateMachine.committedFirstRealDocumentLoad()) + m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); + + // Prevent window.open(url) -- eg window.open("about:blank") -- from blowing away results + // from a subsequent window.document.open / window.document.write call. + // Canceling redirection here works for all cases because document.open + // implicitly precedes document.write. + m_frame->navigationScheduler()->cancel(); + if (m_frame->document()->url() != blankURL()) + m_URL = m_frame->document()->url(); +} + + +void FrameLoader::cancelAndClear() +{ + m_frame->navigationScheduler()->cancel(); + + if (!m_isComplete) + closeURL(); + + clear(false); + m_frame->script()->updatePlatformScriptObjects(); +} + +void FrameLoader::clear(bool clearWindowProperties, bool clearScriptObjects, bool clearFrameView) +{ + m_frame->editor()->clear(); + + if (!m_needsClear) + return; + m_needsClear = false; + + if (!m_frame->document()->inPageCache()) { + m_frame->document()->cancelParsing(); + m_frame->document()->stopActiveDOMObjects(); + 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->document()->inPageCache()); + } + + m_frame->selection()->clear(); + m_frame->eventHandler()->clear(); + if (clearFrameView && m_frame->view()) + m_frame->view()->clear(); + + // 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); + writer()->clear(); + + m_subframeLoader.clear(); + + if (clearScriptObjects) + m_frame->script()->clearScriptObjects(); + + m_frame->navigationScheduler()->clear(); + + m_checkTimer.stop(); + m_shouldCallCheckCompleted = false; + m_shouldCallCheckLoadComplete = false; + + if (m_stateMachine.isDisplayingInitialEmptyDocument() && m_stateMachine.committedFirstRealDocumentLoad()) + m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); +} + +void FrameLoader::receivedFirstData() +{ + writer()->begin(m_workingURL, false); + writer()->setDocumentWasLoadedAsPartOfNavigation(); + + dispatchDidCommitLoad(); + dispatchDidClearWindowObjectsInAllWorlds(); + + if (m_documentLoader) { + String ptitle = m_documentLoader->title(); + // If we have a title let the WebView know about it. + if (!ptitle.isNull()) + m_client->dispatchDidReceiveTitle(ptitle); + } + + m_workingURL = KURL(); + + double delay; + String url; + if (!m_documentLoader) + return; + if (m_frame->inViewSourceMode()) + 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(); + + m_frame->navigationScheduler()->scheduleRedirect(delay, url); +} + +void FrameLoader::setURL(const KURL& url) +{ + KURL ref(url); + ref.setUser(String()); + ref.setPass(String()); + ref.removeFragmentIdentifier(); + m_outgoingReferrer = ref.string(); + m_URL = url; +} + +void FrameLoader::didBeginDocument(bool dispatch) +{ + m_needsClear = true; + m_isComplete = false; + m_didCallImplicitClose = false; + m_isLoadingMainResource = true; + m_frame->document()->setReadyState(Document::Loading); + + if (m_pendingStateObject) { + m_frame->document()->statePopped(m_pendingStateObject.get()); + m_pendingStateObject.clear(); + } + + if (dispatch) + dispatchDidClearWindowObjectsInAllWorlds(); + + updateFirstPartyForCookies(); + + Settings* settings = m_frame->document()->settings(); + m_frame->document()->cachedResourceLoader()->setAutoLoadImages(settings && settings->loadsImagesAutomatically()); +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + m_frame->document()->cachedResourceLoader()->setBlockNetworkImage(settings && settings->blockNetworkImage()); +#endif + + if (m_documentLoader) { + String dnsPrefetchControl = m_documentLoader->response().httpHeaderField("X-DNS-Prefetch-Control"); + if (!dnsPrefetchControl.isEmpty()) + m_frame->document()->parseDNSPrefetchControlHeader(dnsPrefetchControl); + } + + history()->restoreDocumentState(); +} + +void FrameLoader::didEndDocument() +{ + m_isLoadingMainResource = false; +} + +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 && loadType() != FrameLoadTypeReloadFromOrigin) { + 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; + } + } + + // People who want to avoid loading images generally want to avoid loading all images. + // Now that we've accounted for URL mapping, avoid starting the network load if images aren't set to display automatically. + Settings* settings = m_frame->settings(); + if (settings && !settings->loadsImagesAutomatically()) + 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 = IconLoader::create(m_frame); + + m_iconLoader->startLoading(); +} + +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::finishedParsing() +{ + if (m_stateMachine.creatingInitialEmptyDocument()) + return; + + m_frame->injectUserScripts(InjectAtDocumentEnd); + + // 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; + + m_client->dispatchDidFinishDocumentLoad(); + + 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_frame->view()->scrollToFragment(m_URL); +} + +void FrameLoader::loadDone() +{ + checkCompleted(); +} + +bool FrameLoader::allChildrenAreComplete() const +{ + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) { + if (!child->loader()->m_isComplete) + return false; + } + return true; +} + +bool FrameLoader::allAncestorsAreComplete() const +{ + for (Frame* ancestor = m_frame; ancestor; ancestor = ancestor->tree()->parent()) { + if (!ancestor->loader()->m_isComplete) + return false; + } + return true; +} + +void FrameLoader::checkCompleted() +{ + m_shouldCallCheckCompleted = false; + + if (m_frame->view()) + m_frame->view()->checkStopDelayingDeferredRepaints(); + + // Any frame that hasn't completed yet? + if (!allChildrenAreComplete()) + return; + + // Have we completed before? + if (m_isComplete) + return; + + // Are we still parsing? + if (m_frame->document()->parsing()) + return; + + // Still waiting for images/scripts? + if (numRequests(m_frame->document())) + return; + + // Still waiting for elements that don't go through a FrameLoader? + if (m_frame->document()->isDelayingLoadEvent()) + return; + + // OK, completed. + m_isComplete = true; + m_frame->document()->setReadyState(Document::Complete); + + RefPtr<Frame> protect(m_frame); + checkCallImplicitClose(); // if we didn't do it before + + m_frame->navigationScheduler()->startTimer(); + + completed(); + if (m_frame->page()) + checkLoadComplete(); +} + +void FrameLoader::checkTimerFired(Timer<FrameLoader>*) +{ + if (Page* page = m_frame->page()) { + if (page->defersLoading()) + return; + } + if (m_shouldCallCheckCompleted) + checkCompleted(); + if (m_shouldCallCheckLoadComplete) + checkLoadComplete(); +} + +void FrameLoader::startCheckCompleteTimer() +{ + if (!(m_shouldCallCheckCompleted || m_shouldCallCheckLoadComplete)) + return; + if (m_checkTimer.isActive()) + return; + m_checkTimer.startOneShot(0); +} + +void FrameLoader::scheduleCheckCompleted() +{ + m_shouldCallCheckCompleted = true; + startCheckCompleteTimer(); +} + +void FrameLoader::scheduleCheckLoadComplete() +{ + m_shouldCallCheckLoadComplete = true; + startCheckCompleteTimer(); +} + +void FrameLoader::checkCallImplicitClose() +{ + if (m_didCallImplicitClose || m_frame->document()->parsing() || m_frame->document()->isDelayingLoadEvent()) + return; + + if (!allChildrenAreComplete()) + return; // still got a frame running -> too early + + m_didCallImplicitClose = true; + m_wasUnloadEventEmitted = false; + m_frame->document()->implicitClose(); +} + +KURL FrameLoader::baseURL() const +{ + ASSERT(m_frame->document()); + return m_frame->document()->baseURL(); +} + +KURL FrameLoader::completeURL(const String& url) +{ + ASSERT(m_frame->document()); + return m_frame->document()->completeURL(url); +} + +void FrameLoader::loadURLIntoChildFrame(const KURL& url, const String& referer, Frame* childFrame) +{ + ASSERT(childFrame); + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + RefPtr<Archive> subframeArchive = activeDocumentLoader()->popArchiveForSubframe(childFrame->tree()->uniqueName()); + if (subframeArchive) { + childFrame->loader()->loadArchive(subframeArchive.release()); + return; + } +#endif + + HistoryItem* parentItem = history()->currentItem(); + // If we're moving in the back/forward list, we might want to replace the content + // of this child frame with whatever was there at that point. + if (parentItem && parentItem->children().size() && isBackForwardLoadType(loadType())) { + HistoryItem* childItem = parentItem->childItemWithTarget(childFrame->tree()->uniqueName()); + if (childItem) { + childFrame->loader()->loadDifferentDocumentItem(childItem, loadType()); + return; + } + } + + childFrame->loader()->loadURL(url, referer, String(), false, FrameLoadTypeRedirectWithLockedBackForwardList, 0, 0); +} + +#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 + +ObjectContentType FrameLoader::defaultObjectContentType(const KURL& url, const String& mimeTypeIn) +{ + String mimeType = mimeTypeIn; + // We don't use MIMETypeRegistry::getMIMETypeForPath() because it returns "application/octet-stream" upon failure + if (mimeType.isEmpty()) + mimeType = MIMETypeRegistry::getMIMETypeForExtension(url.path().substring(url.path().reverseFind('.') + 1)); + + if (mimeType.isEmpty()) + return ObjectContentFrame; // Go ahead and hope that we can display the content. + + if (MIMETypeRegistry::isSupportedImageMIMEType(mimeType)) + return WebCore::ObjectContentImage; + +#if !PLATFORM(MAC) && !PLATFORM(CHROMIUM) && !PLATFORM(EFL) // Mac has no PluginDatabase, nor does Chromium or EFL + if (PluginDatabase::installedPlugins()->isMIMETypeRegistered(mimeType)) + return WebCore::ObjectContentNetscapePlugin; +#endif + + if (MIMETypeRegistry::isSupportedNonImageMIMEType(mimeType)) + return WebCore::ObjectContentFrame; + + return WebCore::ObjectContentNone; +} + +String FrameLoader::outgoingReferrer() const +{ + return m_outgoingReferrer; +} + +String FrameLoader::outgoingOrigin() const +{ + return m_frame->document()->securityOrigin()->toString(); +} + +bool FrameLoader::isMixedContent(SecurityOrigin* context, const KURL& url) +{ + if (context->protocol() != "https") + return false; // We only care about HTTPS security origins. + + if (!url.isValid() || SchemeRegistry::shouldTreatURLSchemeAsSecure(url.protocol())) + return false; // Loading these protocols is secure. + + return true; +} + +void FrameLoader::checkIfDisplayInsecureContent(SecurityOrigin* context, const KURL& url) +{ + if (!isMixedContent(context, url)) + return; + + String message = makeString("The page at ", m_URL.string(), " displayed insecure content from ", url.string(), ".\n"); + m_frame->domWindow()->console()->addMessage(HTMLMessageSource, LogMessageType, WarningMessageLevel, message, 1, String()); + + m_client->didDisplayInsecureContent(); +} + +void FrameLoader::checkIfRunInsecureContent(SecurityOrigin* context, const KURL& url) +{ + if (!isMixedContent(context, url)) + return; + + String message = makeString("The page at ", m_URL.string(), " ran insecure content from ", url.string(), ".\n"); + m_frame->domWindow()->console()->addMessage(HTMLMessageSource, LogMessageType, WarningMessageLevel, message, 1, String()); + + m_client->didRunInsecureContent(context); +} + +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()); + } +} + +// FIXME: This does not belong in FrameLoader! +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 + if (m_stateMachine.firstLayoutDone()) + m_stateMachine.advanceTo(FrameLoaderStateMachine::CommittedFirstRealLoad); + m_frame->navigationScheduler()->cancel(true); + m_client->provisionalLoadStarted(); +} + +bool FrameLoader::isProcessingUserGesture() +{ + Frame* frame = m_frame->tree()->top(); + if (!frame->script()->canExecuteScripts(NotAboutToExecuteScript)) + return true; // If JavaScript is disabled, a user gesture must have initiated the navigation. + return ScriptController::processingUserGesture(); // FIXME: Use pageIsProcessingUserGesture. +} + +void FrameLoader::resetMultipleFormSubmissionProtection() +{ + m_submittedFormURL = KURL(); +} + +void FrameLoader::willSetEncoding() +{ + if (!m_workingURL.isEmpty()) + receivedFirstData(); +} + +#if ENABLE(WML) +static inline bool frameContainsWMLContent(Frame* frame) +{ + Document* document = frame ? frame->document() : 0; + if (!document) + return false; + + return document->containsWMLContent() || document->isWMLDocument(); +} +#endif + +void FrameLoader::updateFirstPartyForCookies() +{ + if (m_frame->tree()->parent()) + setFirstPartyForCookies(m_frame->tree()->parent()->document()->firstPartyForCookies()); + else + setFirstPartyForCookies(m_URL); +} + +void FrameLoader::setFirstPartyForCookies(const KURL& url) +{ + m_frame->document()->setFirstPartyForCookies(url); + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->setFirstPartyForCookies(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::loadInSameDocument(const KURL& url, SerializedScriptValue* stateObject, bool isNewNavigation) +{ + // If we have a state object, we cannot also be a new navigation. + ASSERT(!stateObject || (stateObject && !isNewNavigation)); + + // Update the data source's request with the new URL to fake the URL change + m_frame->document()->setURL(url); + documentLoader()->replaceRequestURLForSameDocumentNavigation(url); + if (isNewNavigation && !shouldTreatURLAsSameAsCurrent(url) && !stateObject) { + // NB: must happen after replaceRequestURLForSameDocumentNavigation(), 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. + + history()->updateBackForwardListForFragmentScroll(); + } + + String oldURL; + bool hashChange = equalIgnoringFragmentIdentifier(url, m_URL) && url.fragmentIdentifier() != m_URL.fragmentIdentifier(); + oldURL = m_URL; + + m_URL = url; + history()->updateForSameDocumentNavigation(); + + // If we were in the autoscroll/panScroll mode we want to stop it before following the link to the anchor + if (hashChange) + m_frame->eventHandler()->stopAutoscrollTimer(); + + // It's important to model this as a load that starts and immediately finishes. + // Otherwise, the parent frame may think we never finished loading. + started(); + + // We need to scroll to the fragment whether or not a hash change occurred, since + // the user might have scrolled since the previous navigation. + if (FrameView* view = m_frame->view()) + view->scrollToFragment(m_URL); + + m_isComplete = false; + checkCompleted(); + + if (isNewNavigation) { + // 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->dispatchDidNavigateWithinPage(); + + m_frame->document()->statePopped(stateObject ? stateObject : SerializedScriptValue::nullValue()); + m_client->dispatchDidPopStateWithinPage(); + + if (hashChange) { + m_frame->document()->enqueueHashchangeEvent(oldURL, url); + m_client->dispatchDidChangeLocationWithinPage(); + } + + // FrameLoaderClient::didFinishLoad() tells the internal load delegate the load finished with no error + m_client->didFinishLoad(); +} + +bool FrameLoader::isComplete() const +{ + return m_isComplete; +} + +void FrameLoader::completed() +{ + RefPtr<Frame> protect(m_frame); + + for (Frame* descendant = m_frame->tree()->traverseNext(m_frame); descendant; descendant = descendant->tree()->traverseNext(m_frame)) + descendant->navigationScheduler()->startTimer(); + + if (Frame* parent = m_frame->tree()->parent()) + parent->loader()->checkCompleted(); + + if (m_frame->view()) + m_frame->view()->maintainScrollPositionAtAnchor(0); +} + +void FrameLoader::started() +{ + for (Frame* frame = m_frame; frame; frame = frame->tree()->parent()) + frame->loader()->m_isComplete = false; +} + +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); +} + +// This is a hack to allow keep navigation to http/https feeds working. To remove this +// we need to introduce new API akin to registerURLSchemeAsLocal, that registers a +// protocols navigation policy. +static bool isFeedWithNestedProtocolInHTTPFamily(const KURL& url) +{ + const String& urlString = url.string(); + if (!urlString.startsWith("feed", false)) + return false; + + return urlString.startsWith("feed://", false) + || urlString.startsWith("feed:http:", false) || urlString.startsWith("feed:https:", false) + || urlString.startsWith("feeds:http:", false) || urlString.startsWith("feeds:https:", false) + || urlString.startsWith("feedsearch:http:", false) || urlString.startsWith("feedsearch:https:", false); +} + +void FrameLoader::loadFrameRequest(const FrameLoadRequest& request, bool lockHistory, bool lockBackForwardList, + PassRefPtr<Event> event, PassRefPtr<FormState> formState, ReferrerPolicy referrerPolicy) +{ + KURL url = request.resourceRequest().url(); + + ASSERT(m_frame->document()); + // FIXME: Should we move the isFeedWithNestedProtocolInHTTPFamily logic inside SecurityOrigin::canDisplay? + if (!isFeedWithNestedProtocolInHTTPFamily(url) && !request.requester()->canDisplay(url)) { + reportLocalLoadFailed(m_frame, url.string()); + return; + } + + String referrer; + String argsReferrer = request.resourceRequest().httpReferrer(); + if (!argsReferrer.isEmpty()) + referrer = argsReferrer; + else + referrer = m_outgoingReferrer; + + if (SecurityOrigin::shouldHideReferrer(url, referrer) || referrerPolicy == NoReferrer) + referrer = String(); + + FrameLoadType loadType; + if (request.resourceRequest().cachePolicy() == ReloadIgnoringCacheData) + loadType = FrameLoadTypeReload; + else if (lockBackForwardList) + loadType = FrameLoadTypeRedirectWithLockedBackForwardList; + else + loadType = FrameLoadTypeStandard; + + if (request.resourceRequest().httpMethod() == "POST") + loadPostRequest(request.resourceRequest(), referrer, request.frameName(), lockHistory, loadType, event, formState.get()); + else + loadURL(request.resourceRequest().url(), referrer, request.frameName(), lockHistory, loadType, event, formState.get()); + + // FIXME: It's possible this targetFrame will not be the same frame that was targeted by the actual + // load if frame names have changed. + Frame* sourceFrame = formState ? formState->sourceFrame() : m_frame; + Frame* targetFrame = sourceFrame->loader()->findFrameForNavigation(request.frameName()); + if (targetFrame && targetFrame != sourceFrame) { + if (Page* page = targetFrame->page()) + page->chrome()->focus(); + } +} + +void FrameLoader::loadURL(const KURL& newURL, const String& referrer, const String& frameName, bool lockHistory, FrameLoadType newLoadType, + PassRefPtr<Event> event, PassRefPtr<FormState> prpFormState) +{ + RefPtr<FormState> formState = prpFormState; + bool isFormSubmission = formState; + + ResourceRequest request(newURL); + if (!referrer.isEmpty()) { + request.setHTTPReferrer(referrer); + RefPtr<SecurityOrigin> referrerOrigin = SecurityOrigin::createFromString(referrer); + addHTTPOriginIfNeeded(request, referrerOrigin->toString()); + } + addExtraFieldsToRequest(request, newLoadType, true, event || isFormSubmission); + if (newLoadType == FrameLoadTypeReload || newLoadType == FrameLoadTypeReloadFromOrigin) + request.setCachePolicy(ReloadIgnoringCacheData); + + ASSERT(newLoadType != FrameLoadTypeSame); + + // The search for a target frame is done earlier in the case of form submission. + Frame* targetFrame = isFormSubmission ? 0 : findFrameForNavigation(frameName); + if (targetFrame && targetFrame != m_frame) { + targetFrame->loader()->loadURL(newURL, referrer, String(), lockHistory, newLoadType, event, formState.release()); + return; + } + + if (m_pageDismissalEventBeingDispatched) + return; + + NavigationAction action(newURL, newLoadType, isFormSubmission, event); + + if (!targetFrame && !frameName.isEmpty()) { + policyChecker()->checkNewWindowPolicy(action, FrameLoader::callContinueLoadAfterNewWindowPolicy, + request, formState.release(), frameName, this); + return; + } + + RefPtr<DocumentLoader> oldDocumentLoader = m_documentLoader; + + bool sameURL = shouldTreatURLAsSameAsCurrent(newURL); + const String& httpMethod = request.httpMethod(); + + // 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, httpMethod, newLoadType, newURL)) { + oldDocumentLoader->setTriggeringAction(action); + policyChecker()->stopCheck(); + policyChecker()->setLoadType(newLoadType); + policyChecker()->checkNavigationPolicy(request, oldDocumentLoader.get(), formState.release(), + 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, lockHistory, newLoadType, formState.release()); + if (isRedirect) { + m_quickRedirectComing = false; + if (m_provisionalDocumentLoader) + m_provisionalDocumentLoader->setIsClientRedirect(true); + } else if (sameURL) + // 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, bool lockHistory) +{ + load(request, SubstituteData(), lockHistory); +} + +void FrameLoader::load(const ResourceRequest& request, const SubstituteData& substituteData, bool lockHistory) +{ + 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; + RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(request, substituteData); + if (lockHistory && m_documentLoader) + loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory() : m_documentLoader->clientRedirectSourceForHistory()); + load(loader.get()); +} + +void FrameLoader::load(const ResourceRequest& request, const String& frameName, bool lockHistory) +{ + if (frameName.isEmpty()) { + load(request, lockHistory); + return; + } + + Frame* frame = findFrameForNavigation(frameName); + if (frame) { + frame->loader()->load(request, lockHistory); + return; + } + + policyChecker()->checkNewWindowPolicy(NavigationAction(request.url(), NavigationTypeOther), FrameLoader::callContinueLoadAfterNewWindowPolicy, request, 0, frameName, this); +} + +void FrameLoader::loadWithNavigationAction(const ResourceRequest& request, const NavigationAction& action, bool lockHistory, FrameLoadType type, PassRefPtr<FormState> formState) +{ + RefPtr<DocumentLoader> loader = m_client->createDocumentLoader(request, SubstituteData()); + if (lockHistory && m_documentLoader) + loader->setClientRedirectSourceForHistory(m_documentLoader->didCreateGlobalHistoryEntry() ? m_documentLoader->urlForHistory() : m_documentLoader->clientRedirectSourceForHistory()); + + 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(); + addExtraFieldsToMainResourceRequest(r); + 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) +{ + // Retain because dispatchBeforeLoadEvent may release the last reference to it. + RefPtr<Frame> protect(m_frame); + + 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()); + + if (m_pageDismissalEventBeingDispatched) + return; + + if (m_frame->document()) + m_previousUrl = m_frame->document()->url(); + + policyChecker()->setLoadType(type); + RefPtr<FormState> formState = prpFormState; + bool isFormSubmission = formState; + + const KURL& newURL = loader->request().url(); + const String& httpMethod = loader->request().httpMethod(); + + if (shouldScrollToAnchor(isFormSubmission, httpMethod, policyChecker()->loadType(), newURL)) { + RefPtr<DocumentLoader> oldDocumentLoader = m_documentLoader; + NavigationAction action(newURL, policyChecker()->loadType(), isFormSubmission); + + oldDocumentLoader->setTriggeringAction(action); + policyChecker()->stopCheck(); + policyChecker()->checkNavigationPolicy(loader->request(), oldDocumentLoader.get(), formState, + callContinueFragmentScrollAfterNavigationPolicy, this); + } else { + if (Frame* parent = m_frame->tree()->parent()) + loader->setOverrideEncoding(parent->loader()->documentLoader()->overrideEncoding()); + + policyChecker()->stopCheck(); + setPolicyDocumentLoader(loader); + if (loader->triggeringAction().isEmpty()) + loader->setTriggeringAction(NavigationAction(newURL, policyChecker()->loadType(), isFormSubmission)); + + if (Element* ownerElement = m_frame->document()->ownerElement()) { + if (!ownerElement->dispatchBeforeLoadEvent(loader->request().url().string())) { + continueLoadAfterNavigationPolicy(loader->request(), formState, false); + return; + } + } + + policyChecker()->checkNavigationPolicy(loader->request(), loader, formState, + callContinueLoadAfterNavigationPolicy, this); + } +} + +void FrameLoader::reportLocalLoadFailed(Frame* frame, const String& url) +{ + ASSERT(!url.isEmpty()); + if (!frame) + return; + + frame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Not allowed to load local resource: " + url, 0, String()); +} + +const ResourceRequest& FrameLoader::initialRequest() const +{ + return activeDocumentLoader()->originalRequest(); +} + +bool FrameLoader::willLoadMediaElementURL(KURL& url) +{ + ResourceRequest request(url); + + unsigned long identifier; + ResourceError error; + requestFromDelegate(request, identifier, error); + notifier()->sendRemainingDelegateMessages(m_documentLoader.get(), identifier, ResourceResponse(url, String(), -1, String(), String()), -1, error); + + url = request.url(); + + return error.isNull(); +} + +bool FrameLoader::shouldReloadToHandleUnreachableURL(DocumentLoader* docLoader) +{ + KURL unreachableURL = docLoader->unreachableURL(); + + if (unreachableURL.isEmpty()) + return false; + + if (!isBackForwardLoadType(policyChecker()->loadType())) + 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 (policyChecker()->delegateIsDecidingNavigationPolicy() || policyChecker()->delegateIsHandlingUnimplementablePolicy()) + compareDocumentLoader = m_policyDocumentLoader.get(); + else if (m_delegateIsHandlingProvisionalLoadError) + compareDocumentLoader = m_provisionalDocumentLoader.get(); + + return compareDocumentLoader && unreachableURL == compareDocumentLoader->request().url(); +} + +void FrameLoader::reloadWithOverrideEncoding(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(), FrameLoadTypeReload, 0); +} + +void FrameLoader::reload(bool endToEndReload) +{ + if (!m_documentLoader) + return; + + // 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 (m_documentLoader->request().url().isEmpty()) + return; + + ResourceRequest initialRequest = m_documentLoader->request(); + + // Replace error-page URL with the URL we were trying to reach. + KURL unreachableURL = m_documentLoader->unreachableURL(); + if (!unreachableURL.isEmpty()) + initialRequest.setURL(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(); + + // FIXME: We don't have a mechanism to revalidate the main resource without reloading at the moment. + request.setCachePolicy(ReloadIgnoringCacheData); + + // 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(), endToEndReload ? FrameLoadTypeReloadFromOrigin : 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; + + const bool isLocalActiveOrigin = activeSecurityOrigin->isLocal(); + 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; + + // Allow file URL descendant navigation even when allowFileAccessFromFileURLs is false. + if (isLocalActiveOrigin && ancestorSecurityOrigin->isLocal()) + 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 or it is the opener of + // the target frame. + + 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 (!isDocumentSandboxed(m_frame, SandboxTopNavigation) && targetFrame == m_frame->tree()->top()) + return true; + + // A sandboxed frame can only navigate itself and its descendants. + if (isDocumentSandboxed(m_frame, SandboxNavigation) && !targetFrame->tree()->isDescendantOf(m_frame)) + return false; + + // Let a frame navigate its opener if the opener is a top-level window. + if (!targetFrame->tree()->parent() && m_frame->loader()->opener() == targetFrame) + 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 = makeString("Unsafe JavaScript attempt to initiate a navigation change for frame with URL ", + targetDocument->url().string(), " from frame with URL ", activeDocument->url().string(), ".\n"); + + // FIXME: should we print to the console of the activeFrame as well? + targetFrame->domWindow()->console()->addMessage(JSMessageSource, LogMessageType, 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(DatabasePolicy databasePolicy) +{ + ASSERT(!m_frame->document() || !m_frame->document()->inPageCache()); + if (m_pageDismissalEventBeingDispatched) + return; + + // If this method is called from within this method, infinite recursion can occur (3442218). Avoid this. + if (m_inStopAllLoaders) + return; + + m_inStopAllLoaders = true; + + policyChecker()->stopCheck(); + + stopLoadingSubframes(); + if (m_provisionalDocumentLoader) + m_provisionalDocumentLoader->stopLoading(databasePolicy); + if (m_documentLoader) + m_documentLoader->stopLoading(databasePolicy); + + setProvisionalDocumentLoader(0); + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + if (m_documentLoader) + m_documentLoader->clearArchiveResources(); +#endif + + m_checkTimer.stop(); + + 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_stateMachine.committedFirstRealDocumentLoad() || (m_provisionalDocumentLoader && !m_stateMachine.creatingInitialEmptyDocument()); +} + +void FrameLoader::transferLoadingResourcesFromPage(Page* oldPage) +{ + ASSERT(oldPage != m_frame->page()); + if (isLoading()) { + activeDocumentLoader()->transferLoadingResourcesFromPage(oldPage); + oldPage->progress()->progressCompleted(m_frame); + if (m_frame->page()) + m_frame->page()->progress()->progressStarted(m_frame); + } +} + +void FrameLoader::dispatchTransferLoadingResourceFromPage(unsigned long identifier, DocumentLoader* docLoader, const ResourceRequest& request, Page* oldPage) +{ + notifier()->dispatchTransferLoadingResourceFromPage(identifier, docLoader, request, oldPage); +} + +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; +} + +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; +} + +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; +} + +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() +{ + RefPtr<CachedPage> cachedPage = m_loadingFromCachedPage ? pageCache()->get(history()->provisionalItem()) : 0; + RefPtr<DocumentLoader> pdl = m_provisionalDocumentLoader; + + LOG(PageCache, "WebCoreLoading %s: About to commit provisional load from previous URL '%s' to new URL '%s'", m_frame->tree()->uniqueName().string().utf8().data(), m_URL.string().utf8().data(), + pdl ? pdl->url().string().utf8().data() : "<no provisional DocumentLoader>"); + + // 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. + HistoryItem* item = history()->currentItem(); + if (!m_frame->tree()->parent() && PageCache::canCache(m_frame->page()) && !item->isInPageCache()) + pageCache()->add(item, m_frame->page()); + + if (m_loadType != FrameLoadTypeReplace) + closeOldDataSources(); + + if (!cachedPage && !m_stateMachine.creatingInitialEmptyDocument()) + m_client->makeRepresentation(pdl.get()); + + transitionToCommitted(cachedPage); + + if (pdl) { + // Check if the destination page is allowed to access the previous page's timing information. + RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::create(pdl->request().url()); + m_documentLoader->timing()->hasSameOriginAsPreviousDocument = securityOrigin->canRequest(m_previousUrl); + } + + // 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()) { + prepareForCachedPageRestore(); + cachedPage->restore(m_frame->page()); + + dispatchDidCommitLoad(); + + // If we have a title let the WebView know about it. + String title = m_documentLoader->title(); + if (!title.isNull()) + m_client->dispatchDidReceiveTitle(title); + + checkCompleted(); + } 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); + } + + LOG(Loading, "WebCoreLoading %s: Finished committing provisional load to URL %s", m_frame->tree()->uniqueName().string().utf8().data(), m_URL.string().utf8().data()); + + if (m_loadType == FrameLoadTypeStandard && m_documentLoader->isClientRedirect()) + history()->updateForClientRedirect(); + + if (m_loadingFromCachedPage) { + m_frame->document()->documentDidBecomeActive(); + + // Force a layout to update view size and thereby update scrollbars. + m_frame->view()->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. + notifier()->sendRemainingDelegateMessages(m_documentLoader.get(), identifier, response, static_cast<int>(response.expectedContentLength()), error); + } + + pageCache()->remove(history()->currentItem()); + + m_documentLoader->setPrimaryLoadComplete(true); + + // FIXME: Why only this frame and not parent frames? + checkLoadCompleteForThisFrame(); + } +} + +void FrameLoader::transitionToCommitted(PassRefPtr<CachedPage> cachedPage) +{ + ASSERT(m_client->hasWebView()); + ASSERT(m_state == FrameStateProvisional); + + if (m_state != FrameStateProvisional) + return; + + m_client->setCopiesOnScroll(); + history()->updateForCommit(); + + // 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(); + + switch (m_loadType) { + case FrameLoadTypeForward: + case FrameLoadTypeBack: + case FrameLoadTypeBackWMLDeckNotAccessible: + case FrameLoadTypeIndexedBackForward: + if (m_frame->page()) { + // If the first load within a frame is a navigation within a back/forward list that was attached + // without any of the items being loaded then we need to update the history in a similar manner as + // for a standard load with the exception of updating the back/forward list (<rdar://problem/8091103>). + if (!m_stateMachine.committedFirstRealDocumentLoad()) + history()->updateForStandardLoad(HistoryController::UpdateAllExceptBackForwardList); + + history()->updateForBackForwardNavigation(); + + // For cached pages, CachedFrame::restore will take care of firing the popstate event with the history item's state object + if (history()->currentItem() && !cachedPage) + m_pendingStateObject = history()->currentItem()->stateObject(); + + // 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->transitionToCommittedFromCachedFrame(cachedPage->cachedMainFrame()); + + } else + m_client->transitionToCommittedForNewPage(); + } + break; + + case FrameLoadTypeReload: + case FrameLoadTypeReloadFromOrigin: + case FrameLoadTypeSame: + case FrameLoadTypeReplace: + history()->updateForReload(); + m_client->transitionToCommittedForNewPage(); + break; + + case FrameLoadTypeStandard: + history()->updateForStandardLoad(); +#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 FrameLoadTypeRedirectWithLockedBackForwardList: + history()->updateForRedirectWithLockedBackForwardList(); + 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(); + } + + writer()->setMIMEType(dl->responseMIMEType()); + + // Tell the client we've committed this URL. + ASSERT(m_frame->view()); + + if (m_stateMachine.creatingInitialEmptyDocument()) + return; + + if (!m_stateMachine.committedFirstRealDocumentLoad()) + m_stateMachine.advanceTo(FrameLoaderStateMachine::DisplayingInitialEmptyDocumentPostCommit); + + if (!m_client->hasHTMLView()) + receivedFirstData(); +} + +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 lockBackForwardList) +{ + 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, we set a special mode so we treat the next + // load as part of the original 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. + // Loads triggered by JavaScript form submissions never count as quick redirects. + m_quickRedirectComing = (lockBackForwardList || history()->currentItemShouldBeReplaced()) && m_documentLoader && !m_isExecutingJavaScriptFormAction; +} + +bool FrameLoader::shouldReload(const KURL& currentURL, const KURL& destinationURL) +{ +#if ENABLE(WML) + // All WML decks are supposed to be reloaded, even within the same URL fragment + if (frameContainsWMLContent(m_frame)) + return true; +#endif + + // 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.hasFragmentIdentifier()) + return true; + return !equalIgnoringFragmentIdentifier(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::prepareForCachedPageRestore() +{ + ASSERT(!m_frame->tree()->parent()); + ASSERT(m_frame->page()); + ASSERT(m_frame->page()->mainFrame() == m_frame); + + m_frame->navigationScheduler()->cancel(); + + // We still have to close the previous part page. + closeURL(); + + // Delete old status bar messages (if it _was_ activated on last URL). + if (m_frame->script()->canExecuteScripts(NotAboutToExecuteScript)) { + if (DOMWindow* window = m_frame->existingDOMWindow()) { + window->setStatus(String()); + window->setDefaultStatus(String()); + } + } +} + +void FrameLoader::open(CachedFrameBase& cachedFrame) +{ + m_isComplete = false; + + // Don't re-emit the load event. + m_didCallImplicitClose = true; + + KURL url = cachedFrame.url(); + + if (url.protocolInHTTPFamily() && !url.host().isEmpty() && url.path().isEmpty()) + url.setPath("/"); + + m_URL = url; + m_workingURL = url; + + started(); + clear(true, true, cachedFrame.isMainFrame()); + + Document* document = cachedFrame.document(); + ASSERT(document); + document->setInPageCache(false); + + m_needsClear = true; + m_isComplete = false; + m_didCallImplicitClose = false; + m_outgoingReferrer = url.string(); + + FrameView* view = cachedFrame.view(); + + // When navigating to a CachedFrame its FrameView should never be null. If it is we'll crash in creative ways downstream. + ASSERT(view); + view->setWasScrolledByUser(false); + + // Use the current ScrollView's frame rect. + if (m_frame->view()) + view->setFrameRect(m_frame->view()->frameRect()); + m_frame->setView(view); + + m_frame->setDocument(document); + m_frame->setDOMWindow(cachedFrame.domWindow()); + m_frame->domWindow()->setURL(document->url()); + m_frame->domWindow()->setSecurityOrigin(document->securityOrigin()); + + writer()->setDecoder(document->decoder()); + + updateFirstPartyForCookies(); + + cachedFrame.restore(); +} + +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::didReceiveServerRedirectForProvisionalLoadForFrame() +{ + m_client->dispatchDidReceiveServerRedirectForProvisionalLoad(); +} + +void FrameLoader::finishedLoadingDocument(DocumentLoader* loader) +{ + // FIXME: Platforms shouldn't differ here! +#if PLATFORM(WIN) || PLATFORM(CHROMIUM) || defined(ANDROID) + if (m_stateMachine.creatingInitialEmptyDocument()) + return; +#endif + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + + // Give archive machinery a crack at this document. If the MIME type is not an archive type, it will return 0. + RefPtr<Archive> archive = ArchiveFactory::create(loader->mainResourceData().get(), loader->responseMIMEType()); + if (!archive) { + m_client->finishedLoading(loader); + return; + } + + // FIXME: The remainder of this function should be moved to DocumentLoader. + + loader->addAllArchiveResources(archive.get()); + + ArchiveResource* mainResource = archive->mainResource(); + loader->setParsedArchiveData(mainResource->data()); + + writer()->setMIMEType(mainResource->mimeType()); + + closeURL(); + didOpenURL(mainResource->url()); + + ASSERT(m_frame->document()); + String userChosenEncoding = documentLoader()->overrideEncoding(); + bool encodingIsUserChosen = !userChosenEncoding.isNull(); + writer()->setEncoding(encodingIsUserChosen ? userChosenEncoding : mainResource->textEncoding(), encodingIsUserChosen); + writer()->addData(mainResource->data()->data(), mainResource->data()->size()); +#else + m_client->finishedLoading(loader); +#endif // ARCHIVE +} + +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; + documentLoader = childLoader->policyDocumentLoader(); + if (documentLoader) + return true; + } + return false; +} + +void FrameLoader::willChangeTitle(DocumentLoader* loader) +{ + m_client->willChangeTitle(loader); +} + +FrameLoadType FrameLoader::loadType() const +{ + return m_loadType; +} + +CachePolicy FrameLoader::subresourceCachePolicy() const +{ + if (m_isComplete) + return CachePolicyVerify; + + if (m_loadType == FrameLoadTypeReloadFromOrigin) + return CachePolicyReload; + + if (Frame* parentFrame = m_frame->tree()->parent()) { + CachePolicy parentCachePolicy = parentFrame->loader()->subresourceCachePolicy(); + if (parentCachePolicy != CachePolicyVerify) + return parentCachePolicy; + } + + // FIXME: POST documents are always Reloads, but their subresources should still be Revalidate. + // If we bring the CachePolicy.h and ResourceRequest cache policy enums in sync with each other and + // remember "Revalidate" in ResourceRequests, we can remove this "POST" check and return either "Reload" + // or "Revalidate" if the DocumentLoader was requested with either. + const ResourceRequest& request(documentLoader()->request()); + if (request.cachePolicy() == ReloadIgnoringCacheData && !equalIgnoringCase(request.httpMethod(), "post")) + return CachePolicyRevalidate; + + if (m_loadType == FrameLoadTypeReload) + return CachePolicyRevalidate; + + if (request.cachePolicy() == ReturnCacheDataElseLoad) + return CachePolicyHistoryBuffer; + + return CachePolicyVerify; +} + +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 = history()->currentItem(); + + bool shouldReset = true; + if (!(pdl->isLoadingInAPISense() && !pdl->isStopping())) { + 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(); + + // If we're in the middle of loading multipart data, we need to restore the document loader. + if (isReplacing() && !m_documentLoader.get()) + setDocumentLoader(m_provisionalDocumentLoader.get()); + + // 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 (activeDocumentLoader()) { + KURL unreachableURL = activeDocumentLoader()->unreachableURL(); + if (!unreachableURL.isEmpty() && unreachableURL == pdl->request().url()) + shouldReset = false; + } + } + if (shouldReset && item) + if (Page* page = m_frame->page()) { + page->backForward()->setCurrentItem(item.get()); + Settings* settings = m_frame->settings(); + page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : item.get()); + } + return; + } + + case FrameStateCommittedPage: { + DocumentLoader* dl = m_documentLoader.get(); + if (!dl || (dl->isLoadingInAPISense() && !dl->isStopping())) + 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 (m_frame->page()) { + if (isBackForwardLoadType(m_loadType) || m_loadType == FrameLoadTypeReload || m_loadType == FrameLoadTypeReloadFromOrigin) + history()->restoreScrollPositionAndViewState(); + } + + if (m_stateMachine.creatingInitialEmptyDocument() || !m_stateMachine.committedFirstRealDocumentLoad()) + return; + + const ResourceError& error = dl->mainDocumentError(); + 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() && m_frame->document()->renderArena()) + android::TimeCounter::report(m_URL, cache()->getLiveSize(), cache()->getDeadSize(), + m_frame->document()->renderArena()->reportPoolSize()); +#endif + return; + } + + case FrameStateComplete: + frameLoadCompleted(); + return; + } + + ASSERT_NOT_REACHED(); +} + +void FrameLoader::continueLoadAfterWillSubmitForm() +{ + 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_loadingFromCachedPage = false; + + unsigned long identifier = 0; + + if (Page* page = m_frame->page()) { + identifier = page->progress()->createUniqueIdentifier(); + notifier()->assignIdentifierToInitialRequest(identifier, m_provisionalDocumentLoader.get(), m_provisionalDocumentLoader->originalRequest()); + } + + ASSERT(!m_provisionalDocumentLoader->timing()->navigationStart); + m_provisionalDocumentLoader->timing()->navigationStart = currentTime(); + + if (!m_provisionalDocumentLoader->startLoadingMainResource(identifier)) + m_provisionalDocumentLoader->updateLoading(); +} + +void FrameLoader::didFirstLayout() +{ + if (m_frame->page() && isBackForwardLoadType(m_loadType)) + history()->restoreScrollPositionAndViewState(); + + if (m_stateMachine.committedFirstRealDocumentLoad() && !m_stateMachine.isDisplayingInitialEmptyDocument() && !m_stateMachine.firstLayoutDone()) + m_stateMachine.advanceTo(FrameLoaderStateMachine::FirstLayoutDone); + m_client->dispatchDidFirstLayout(); +} + +void FrameLoader::didFirstVisuallyNonEmptyLayout() +{ + m_client->dispatchDidFirstVisuallyNonEmptyLayout(); +} + +void FrameLoader::frameLoadCompleted() +{ + // Note: Can be called multiple times. + + m_client->frameLoadCompleted(); + + history()->updateForFrameLoadCompleted(); + + // After a canceled provisional load, firstLayoutDone is false. + // Reset it to true if we're displaying a page. + if (m_documentLoader && m_stateMachine.committedFirstRealDocumentLoad() && !m_stateMachine.isDisplayingInitialEmptyDocument() && !m_stateMachine.firstLayoutDone()) + m_stateMachine.advanceTo(FrameLoaderStateMachine::FirstLayoutDone); +} + +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::closeAndRemoveChild(Frame* child) +{ + child->tree()->detachFromParent(); + + child->setView(0); + if (child->ownerElement() && child->page()) + child->page()->decrementFrameCount(); + // FIXME: The page isn't being destroyed, so it's not right to call a function named pageDestroyed(). + child->pageDestroyed(); + + m_frame->tree()->removeChild(child); +} + +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()); + + m_shouldCallCheckLoadComplete = false; + + // 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; +} + +String FrameLoader::userAgent(const KURL& url) const +{ + return m_client->userAgent(url); +} + +void FrameLoader::handledOnloadEvents() +{ + m_client->dispatchDidHandleOnloadEvents(); + + if (documentLoader()) { + documentLoader()->handledOnloadEvents(); +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + documentLoader()->applicationCacheHost()->stopDeferringEvents(); +#endif + } +} + +void FrameLoader::frameDetached() +{ + stopAllLoaders(); + m_frame->document()->stopActiveDOMObjects(); + detachFromParent(); +} + +void FrameLoader::detachFromParent() +{ + RefPtr<Frame> protect(m_frame); + + closeURL(); + history()->saveScrollPositionAndViewStateToItem(history()->currentItem()); + detachChildren(); + // stopAllLoaders() needs to be called after detachChildren(), because detachedChildren() + // will trigger the unload event handlers of any child frames, and those event + // handlers might start a new subresource load in this frame. + stopAllLoaders(); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->frameDetachedFromParent(m_frame); +#endif + + detachViewsAndDocumentLoader(); + + if (Frame* parent = m_frame->tree()->parent()) { + parent->loader()->closeAndRemoveChild(m_frame); + parent->loader()->scheduleCheckCompleted(); + } else { + m_frame->setView(0); + // FIXME: The page isn't being destroyed, so it's not right to call a function named pageDestroyed(). + m_frame->pageDestroyed(); + } +} + +void FrameLoader::detachViewsAndDocumentLoader() +{ + m_client->detachedFromParent2(); + setDocumentLoader(0); + m_client->detachedFromParent3(); +} + +void FrameLoader::addExtraFieldsToSubresourceRequest(ResourceRequest& request) +{ + addExtraFieldsToRequest(request, m_loadType, false, false); +} + +void FrameLoader::addExtraFieldsToMainResourceRequest(ResourceRequest& request) +{ + addExtraFieldsToRequest(request, m_loadType, true, false); +} + +void FrameLoader::addExtraFieldsToRequest(ResourceRequest& request, FrameLoadType loadType, bool mainResource, bool cookiePolicyURLFromRequest) +{ + // Don't set the cookie policy URL if it's already been set. + // But make sure to set it on all requests, as it has significance beyond the cookie policy for all protocols (<rdar://problem/6616664>). + if (request.firstPartyForCookies().isEmpty()) { + if (mainResource && (isLoadingMainFrame() || cookiePolicyURLFromRequest)) + request.setFirstPartyForCookies(request.url()); + else if (Document* document = m_frame->document()) + request.setFirstPartyForCookies(document->firstPartyForCookies()); + } + + // The remaining modifications are only necessary for HTTP and HTTPS. + if (!request.url().isEmpty() && !request.url().protocolInHTTPFamily()) + return; + + applyUserAgent(request); + + // If we inherit cache policy from a main resource, we use the DocumentLoader's + // original request 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 (!mainResource) { + if (request.isConditional()) + request.setCachePolicy(ReloadIgnoringCacheData); + else if (documentLoader()->isLoadingInAPISense()) + request.setCachePolicy(documentLoader()->originalRequest().cachePolicy()); + else + request.setCachePolicy(UseProtocolCachePolicy); + } else if (loadType == FrameLoadTypeReload || loadType == FrameLoadTypeReloadFromOrigin || request.isConditional()) + request.setCachePolicy(ReloadIgnoringCacheData); + else if (isBackForwardLoadType(loadType) && m_stateMachine.committedFirstRealDocumentLoad() && !request.url().protocolIs("https")) + request.setCachePolicy(ReturnCacheDataElseLoad); + + if (request.cachePolicy() == ReloadIgnoringCacheData) { + if (loadType == FrameLoadTypeReload) + request.setHTTPHeaderField("Cache-Control", "max-age=0"); + else if (loadType == FrameLoadTypeReloadFromOrigin) { + request.setHTTPHeaderField("Cache-Control", "no-cache"); + request.setHTTPHeaderField("Pragma", "no-cache"); + } + } + + if (mainResource) + request.setHTTPAccept(defaultAcceptHeader); + + // Make sure we send the Origin header. + addHTTPOriginIfNeeded(request, String()); + + // Always try UTF-8. If that fails, try frame encoding (if any) and then the default. + // For a newly opened frame with an empty URL, encoding() should not be used, because this methods asks decoder, which uses ISO-8859-1. + Settings* settings = m_frame->settings(); + request.setResponseContentDispositionEncodingFallbackArray("UTF-8", writer()->deprecatedFrameEncoding(), settings ? settings->defaultTextEncodingName() : 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::loadPostRequest(const ResourceRequest& inRequest, const String& referrer, const String& frameName, bool lockHistory, FrameLoadType loadType, PassRefPtr<Event> event, PassRefPtr<FormState> prpFormState) +{ + 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); + + if (!referrer.isEmpty()) + workingResourceRequest.setHTTPReferrer(referrer); + workingResourceRequest.setHTTPOrigin(origin); + workingResourceRequest.setHTTPMethod("POST"); + workingResourceRequest.setHTTPBody(formData); + workingResourceRequest.setHTTPContentType(contentType); + addExtraFieldsToRequest(workingResourceRequest, loadType, true, true); + + NavigationAction action(url, loadType, true, event); + + if (!frameName.isEmpty()) { + // The search for a target frame is done earlier in the case of form submission. + if (Frame* targetFrame = formState ? 0 : findFrameForNavigation(frameName)) + targetFrame->loader()->loadWithNavigationAction(workingResourceRequest, action, lockHistory, loadType, formState.release()); + else + policyChecker()->checkNewWindowPolicy(action, FrameLoader::callContinueLoadAfterNewWindowPolicy, workingResourceRequest, formState.release(), frameName, this); + } else + loadWithNavigationAction(workingResourceRequest, action, lockHistory, loadType, formState.release()); +} + +unsigned long FrameLoader::loadResourceSynchronously(const ResourceRequest& request, StoredCredentials storedCredentials, ResourceError& error, ResourceResponse& response, Vector<char>& data) +{ + String referrer = m_outgoingReferrer; + if (SecurityOrigin::shouldHideReferrer(request.url(), referrer)) + referrer = String(); + + ResourceRequest initialRequest = request; + initialRequest.setTimeoutInterval(10); + + if (!referrer.isEmpty()) + initialRequest.setHTTPReferrer(referrer); + addHTTPOriginIfNeeded(initialRequest, outgoingOrigin()); + + if (Page* page = m_frame->page()) + initialRequest.setFirstPartyForCookies(page->mainFrame()->loader()->documentLoader()->request().url()); + initialRequest.setHTTPUserAgent(client()->userAgent(request.url())); + + addExtraFieldsToSubresourceRequest(initialRequest); + + unsigned long identifier = 0; + ResourceRequest newRequest(initialRequest); + requestFromDelegate(newRequest, identifier, error); + + if (error.isNull()) { + ASSERT(!newRequest.isNull()); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (!documentLoader()->applicationCacheHost()->maybeLoadSynchronously(newRequest, error, response, data)) { +#endif + ResourceHandle::loadResourceSynchronously(networkingContext(), newRequest, storedCredentials, error, response, data); +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + documentLoader()->applicationCacheHost()->maybeLoadFallbackSynchronously(newRequest, error, response, data); + } +#endif + } + + notifier()->sendRemainingDelegateMessages(m_documentLoader.get(), identifier, response, data.size(), error); + return identifier; +} + +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) { + if (m_submittedFormURL == m_provisionalDocumentLoader->originalRequestCopy().url()) + m_submittedFormURL = KURL(); + + // 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. + history()->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) +{ + m_quickRedirectComing = false; + + if (!shouldContinue) + return; + + bool isRedirect = m_quickRedirectComing || policyChecker()->loadType() == FrameLoadTypeRedirectWithLockedBackForwardList; + loadInSameDocument(request.url(), 0, !isRedirect); +} + +bool FrameLoader::shouldScrollToAnchor(bool isFormSubmission, const String& httpMethod, 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 with method other than "GET", 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 || equalIgnoringCase(httpMethod, "GET")) + && loadType != FrameLoadTypeReload + && loadType != FrameLoadTypeReloadFromOrigin + && 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->document()->isFrameSet(); +} + +void FrameLoader::callContinueLoadAfterNavigationPolicy(void* argument, + const ResourceRequest& request, PassRefPtr<FormState> formState, bool shouldContinue) +{ + FrameLoader* loader = static_cast<FrameLoader*>(argument); + loader->continueLoadAfterNavigationPolicy(request, formState, shouldContinue); +} + +bool FrameLoader::shouldClose() +{ + Page* page = m_frame->page(); + Chrome* chrome = page ? page->chrome() : 0; + if (!chrome || !chrome->canRunBeforeUnloadConfirmPanel()) + return true; + + // Store all references to each subframe in advance since beforeunload's event handler may modify frame + Vector<RefPtr<Frame> > targetFrames; + targetFrames.append(m_frame); + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->traverseNext(m_frame)) + targetFrames.append(child); + + bool shouldClose = false; + { + NavigationDisablerForBeforeUnload navigationDisabler; + size_t i; + + for (i = 0; i < targetFrames.size(); i++) { + if (!targetFrames[i]->tree()->isDescendantOf(m_frame)) + continue; + if (!targetFrames[i]->loader()->fireBeforeUnloadEvent(chrome)) + break; + } + + if (i == targetFrames.size()) + shouldClose = true; + } + + return shouldClose; +} + +bool FrameLoader::fireBeforeUnloadEvent(Chrome* chrome) +{ + DOMWindow* domWindow = m_frame->existingDOMWindow(); + if (!domWindow) + return true; + + RefPtr<Document> document = m_frame->document(); + if (!document->body()) + return true; + + RefPtr<BeforeUnloadEvent> beforeUnloadEvent = BeforeUnloadEvent::create(); + m_pageDismissalEventBeingDispatched = true; + domWindow->dispatchEvent(beforeUnloadEvent.get(), domWindow->document()); + m_pageDismissalEventBeingDispatched = false; + + if (!beforeUnloadEvent->defaultPrevented()) + document->defaultEventHandler(beforeUnloadEvent.get()); + if (beforeUnloadEvent->result().isNull()) + return true; + + String text = document->displayStringModifiedByEncoding(beforeUnloadEvent->result()); + return chrome->runBeforeUnloadConfirmPanel(text, m_frame); +} + +void FrameLoader::continueLoadAfterNavigationPolicy(const ResourceRequest&, 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 = history()->provisionalItem() ? history()->provisionalItem()->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. + bool canContinue = shouldContinue && 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(policyChecker()->loadType())) + if (Page* page = m_frame->page()) { + Frame* mainFrame = page->mainFrame(); + if (HistoryItem* resetItem = mainFrame->loader()->history()->currentItem()) { + page->backForward()->setCurrentItem(resetItem); + Settings* settings = m_frame->settings(); + page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : resetItem); + } + } + return; + } + + FrameLoadType type = policyChecker()->loadType(); + 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; + +#if ENABLE(JAVASCRIPT_DEBUGGER) && ENABLE(INSPECTOR) && USE(JSC) + if (Page* page = m_frame->page()) { + if (page->mainFrame() == m_frame) + page->inspectorController()->resume(); + } +#endif + + setProvisionalDocumentLoader(m_policyDocumentLoader.get()); + m_loadType = type; + setState(FrameStateProvisional); + + setPolicyDocumentLoader(0); + + if (isBackForwardLoadType(type) && history()->provisionalItem()->isInPageCache()) { + loadProvisionalItemFromCachedPage(); + return; + } + + if (formState) + m_client->dispatchWillSubmitForm(&PolicyChecker::continueLoadAfterWillSubmitForm, formState); + else + continueLoadAfterWillSubmitForm(); +} + +void FrameLoader::callContinueLoadAfterNewWindowPolicy(void* argument, + const ResourceRequest& request, PassRefPtr<FormState> formState, const String& frameName, const NavigationAction& action, bool shouldContinue) +{ + FrameLoader* loader = static_cast<FrameLoader*>(argument); + loader->continueLoadAfterNewWindowPolicy(request, formState, frameName, action, shouldContinue); +} + +void FrameLoader::continueLoadAfterNewWindowPolicy(const ResourceRequest& request, + PassRefPtr<FormState> formState, const String& frameName, const NavigationAction& action, bool shouldContinue) +{ + if (!shouldContinue) + return; + + RefPtr<Frame> frame = m_frame; + RefPtr<Frame> mainFrame = m_client->dispatchCreatePage(action); + if (!mainFrame) + return; + + if (frameName != "_blank") + mainFrame->tree()->setName(frameName); + + mainFrame->page()->setOpenedByDOM(); + mainFrame->loader()->m_client->dispatchShow(); + if (!m_suppressOpenerInNewFrame) + mainFrame->loader()->setOpener(frame.get()); + mainFrame->loader()->loadWithNavigationAction(request, NavigationAction(), false, FrameLoadTypeStandard, formState); +} + +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(); + notifier()->assignIdentifierToInitialRequest(identifier, m_documentLoader.get(), request); + } + + ResourceRequest newRequest(request); + notifier()->dispatchWillSendRequest(m_documentLoader.get(), identifier, newRequest, ResourceResponse()); + + if (newRequest.isNull()) + error = cancelledError(request); + else + error = ResourceError(); + + request = newRequest; +} + +void FrameLoader::loadedResourceFromMemoryCache(const CachedResource* resource) +{ + Page* page = m_frame->page(); + if (!page) + return; + + if (!resource->sendResourceLoadCallbacks() || m_documentLoader->haveToldClientAboutLoad(resource->url())) + return; + + if (!page->areMemoryCacheClientCallsEnabled()) { +#if ENABLE(INSPECTOR) + page->inspectorController()->didLoadResourceFromMemoryCache(m_documentLoader.get(), resource); +#endif + m_documentLoader->recordMemoryCacheLoadForFutureClientNotification(resource->url()); + m_documentLoader->didTellClientAboutLoad(resource->url()); + return; + } + + ResourceRequest request(resource->url()); + if (m_client->dispatchDidLoadResourceFromMemoryCache(m_documentLoader.get(), request, resource->response(), resource->encodedSize())) { +#if ENABLE(INSPECTOR) + page->inspectorController()->didLoadResourceFromMemoryCache(m_documentLoader.get(), resource); +#endif + m_documentLoader->didTellClientAboutLoad(resource->url()); + return; + } + + unsigned long identifier; + ResourceError error; + requestFromDelegate(request, identifier, error); +#if ENABLE(INSPECTOR) + page->inspectorController()->markResourceAsCached(identifier); +#endif + notifier()->sendRemainingDelegateMessages(m_documentLoader.get(), identifier, resource->response(), resource->encodedSize(), error); +} + +void FrameLoader::applyUserAgent(ResourceRequest& request) +{ + String userAgent = client()->userAgent(request.url()); + ASSERT(!userAgent.isNull()); + request.setHTTPUserAgent(userAgent); +} + +bool FrameLoader::shouldInterruptLoadForXFrameOptions(const String& content, const KURL& url) +{ + Frame* topFrame = m_frame->tree()->top(); + if (m_frame == topFrame) + return false; + + if (equalIgnoringCase(content, "deny")) + return true; + + if (equalIgnoringCase(content, "sameorigin")) { + RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url); + if (!origin->isSameSchemeHostPort(topFrame->document()->securityOrigin())) + return true; + } + + return false; +} + +void FrameLoader::loadProvisionalItemFromCachedPage() +{ + DocumentLoader* provisionalLoader = provisionalDocumentLoader(); + LOG(PageCache, "WebCorePageCache: Loading provisional DocumentLoader %p with URL '%s' from CachedPage", provisionalDocumentLoader(), provisionalDocumentLoader()->url().string().utf8().data()); + + provisionalLoader->prepareForLoadStart(); + + m_loadingFromCachedPage = true; + + // Should have timing data from previous time(s) the page was shown. + ASSERT(provisionalLoader->timing()->navigationStart); + provisionalLoader->resetTiming(); + provisionalLoader->timing()->navigationStart = currentTime(); + + provisionalLoader->setCommitted(true); + commitProvisionalLoad(); +} + +bool FrameLoader::shouldTreatURLAsSameAsCurrent(const KURL& url) const +{ + if (!history()->currentItem()) + return false; + return url == history()->currentItem()->url() || url == history()->currentItem()->originalURL(); +} + +void FrameLoader::checkDidPerformFirstNavigation() +{ + Page* page = m_frame->page(); + if (!page) + return; + + if (!m_didPerformFirstNavigation && page->backForward()->currentItem() && !page->backForward()->backItem() && !page->backForward()->forwardItem()) { + m_didPerformFirstNavigation = true; + m_client->didPerformFirstNavigation(); + } +} + +Frame* FrameLoader::findFrameForNavigation(const AtomicString& name) +{ + Frame* frame = m_frame->tree()->find(name); + if (!shouldAllowNavigation(frame)) + return 0; + return frame; +} + +void FrameLoader::loadSameDocumentItem(HistoryItem* item) +{ + ASSERT(item->documentSequenceNumber() == history()->currentItem()->documentSequenceNumber()); + + // Save user view state to the current history item here since we don't do a normal load. + // FIXME: Does form state need to be saved here too? + history()->saveScrollPositionAndViewStateToItem(history()->currentItem()); + if (FrameView* view = m_frame->view()) + view->setWasScrolledByUser(false); + + history()->setCurrentItem(item); + + // loadInSameDocument() actually changes the URL and notifies load delegates of a "fake" load + loadInSameDocument(item->url(), item->stateObject(), false); + + // Restore user view state from the current history item here since we don't do a normal load. + history()->restoreScrollPositionAndViewState(); +} + +// FIXME: This function should really be split into a couple pieces, some of +// which should be methods of HistoryController and some of which should be +// methods of FrameLoader. +void FrameLoader::loadDifferentDocumentItem(HistoryItem* item, FrameLoadType loadType) +{ + // Remember this item so we can traverse any child items as child frames load + history()->setProvisionalItem(item); + + if (CachedPage* cachedPage = pageCache()->get(item)) { + loadWithDocumentLoader(cachedPage->documentLoader(), loadType, 0); + return; + } + + KURL itemURL = item->url(); + KURL itemOriginalURL = item->originalURL(); + KURL currentURL; + if (documentLoader()) + currentURL = documentLoader()->url(); + RefPtr<FormData> formData = item->formData(); + + bool addedExtraFields = false; + ResourceRequest request(itemURL); + + if (!item->referrer().isNull()) + request.setHTTPReferrer(item->referrer()); + + // 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->document()); + + request.setHTTPMethod("POST"); + request.setHTTPBody(formData); + request.setHTTPContentType(item->formContentType()); + RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::createFromString(item->referrer()); + addHTTPOriginIfNeeded(request, securityOrigin->toString()); + + // Make sure to add extra fields to the request after the Origin header is added for the FormData case. + // See https://bugs.webkit.org/show_bug.cgi?id=22194 for more discussion. + addExtraFieldsToRequest(request, m_loadType, true, formData); + addedExtraFields = true; + + // 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, m_frame)) + action = NavigationAction(itemURL, loadType, false); + else { + request.setCachePolicy(ReloadIgnoringCacheData); + action = NavigationAction(itemURL, NavigationTypeFormResubmitted); + } + } else { + switch (loadType) { + case FrameLoadTypeReload: + case FrameLoadTypeReloadFromOrigin: + request.setCachePolicy(ReloadIgnoringCacheData); + break; + case FrameLoadTypeBack: + case FrameLoadTypeBackWMLDeckNotAccessible: + case FrameLoadTypeForward: + case FrameLoadTypeIndexedBackForward: + // If the first load within a frame is a navigation within a back/forward list that was attached + // without any of the items being loaded then we should use the default caching policy (<rdar://problem/8131355>). + if (m_stateMachine.committedFirstRealDocumentLoad() && !itemURL.protocolIs("https")) + request.setCachePolicy(ReturnCacheDataElseLoad); + break; + case FrameLoadTypeStandard: + case FrameLoadTypeRedirectWithLockedBackForwardList: + break; + case FrameLoadTypeSame: + default: + ASSERT_NOT_REACHED(); + } + + action = NavigationAction(itemOriginalURL, loadType, false); + } + + if (!addedExtraFields) + addExtraFieldsToRequest(request, m_loadType, true, formData); + + loadWithNavigationAction(request, action, false, loadType, 0); +} + +// Loads content into this frame, as specified by history item +void FrameLoader::loadItem(HistoryItem* item, FrameLoadType loadType) +{ + HistoryItem* currentItem = history()->currentItem(); + bool sameDocumentNavigation = currentItem && item->shouldDoSameDocumentNavigationTo(currentItem); + +#if ENABLE(WML) + // All WML decks should go through the real load mechanism, not the scroll-to-anchor code + // FIXME: Why do WML decks have this different behavior? + // Are WML decks incompatible with HTML5 pushState/replaceState which require inter-document history navigations? + // Should this new API be disabled for WML pages, or does WML need to update their mechanism to act like normal loads? + // If scroll-to-anchor navigations were broken for WML and required them to have different loading behavior, then + // state object loads are certainly also broken for them. + if (frameContainsWMLContent(m_frame)) + sameDocumentNavigation = false; +#endif + + if (sameDocumentNavigation) + loadSameDocumentItem(item); + else + loadDifferentDocumentItem(item, loadType); +} + +void FrameLoader::setMainDocumentError(DocumentLoader* loader, const ResourceError& error) +{ + m_client->setMainDocumentError(loader, error); +} + +void FrameLoader::mainReceivedCompleteError(DocumentLoader* loader, const ResourceError&) +{ + 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::interruptionForPolicyChangeError(const ResourceRequest& request) const +{ + return m_client->interruptForPolicyChangeError(request); +} + +ResourceError FrameLoader::fileDoesNotExistError(const ResourceResponse& response) const +{ + return m_client->fileDoesNotExistError(response); +} + +bool FrameLoader::shouldUseCredentialStorage(ResourceLoader* loader) +{ + return m_client->shouldUseCredentialStorage(loader->documentLoader(), loader->identifier()); +} + +#if USE(PROTECTION_SPACE_AUTH_CALLBACK) +bool FrameLoader::canAuthenticateAgainstProtectionSpace(ResourceLoader* loader, const ProtectionSpace& protectionSpace) +{ + return m_client->canAuthenticateAgainstProtectionSpace(loader->documentLoader(), loader->identifier(), protectionSpace); +} +#endif + +void FrameLoader::setTitle(const String& title) +{ + documentLoader()->setTitle(title); +} + +void FrameLoader::setIconURL(const String& iconURL) +{ + documentLoader()->setIconURL(iconURL); +} + +KURL FrameLoader::originalRequestURL() const +{ + return activeDocumentLoader()->originalRequest().url(); +} + +String FrameLoader::referrer() const +{ + return m_documentLoader ? m_documentLoader->request().httpReferrer() : ""; +} + +void FrameLoader::dispatchDocumentElementAvailable() +{ + m_frame->injectUserScripts(InjectAtDocumentStart); + m_client->documentElementAvailable(); +} + +void FrameLoader::dispatchDidClearWindowObjectsInAllWorlds() +{ + if (!m_frame->script()->canExecuteScripts(NotAboutToExecuteScript)) + return; + + Vector<DOMWrapperWorld*> worlds; + ScriptController::getAllWorlds(worlds); + for (size_t i = 0; i < worlds.size(); ++i) + dispatchDidClearWindowObjectInWorld(worlds[i]); +} + +void FrameLoader::dispatchDidClearWindowObjectInWorld(DOMWrapperWorld* world) +{ + if (!m_frame->script()->canExecuteScripts(NotAboutToExecuteScript) || !m_frame->script()->existingWindowShell(world)) + return; + + m_client->dispatchDidClearWindowObjectInWorld(world); + +#if ENABLE(INSPECTOR) + if (world != mainThreadNormalWorld()) + return; + + if (Page* page = m_frame->page()) { + if (InspectorController* inspector = page->inspectorController()) + inspector->inspectedWindowScriptObjectCleared(m_frame); + } +#endif +} + +void FrameLoader::updateSandboxFlags() +{ + SandboxFlags flags = m_forcedSandboxFlags; + if (Frame* parentFrame = m_frame->tree()->parent()) + flags |= parentFrame->loader()->sandboxFlags(); + if (HTMLFrameOwnerElement* ownerElement = m_frame->ownerElement()) + flags |= ownerElement->sandboxFlags(); + + if (m_sandboxFlags == flags) + return; + + m_sandboxFlags = flags; + + for (Frame* child = m_frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) + child->loader()->updateSandboxFlags(); +} + +void FrameLoader::didChangeTitle(DocumentLoader* loader) +{ + m_client->didChangeTitle(loader); + + if (loader == m_documentLoader) { + // Must update the entries in the back-forward list too. + history()->setCurrentItemTitle(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::didChangeIcons(DocumentLoader* loader) +{ + if (loader == m_documentLoader) + m_client->dispatchDidChangeIcons(); +} + +void FrameLoader::dispatchDidCommitLoad() +{ + if (m_stateMachine.creatingInitialEmptyDocument()) + return; + + m_client->dispatchDidCommitLoad(); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didCommitLoad(m_documentLoader.get()); +#endif +} + +void FrameLoader::tellClientAboutPastMemoryCacheLoads() +{ + ASSERT(m_frame->page()); + ASSERT(m_frame->page()->areMemoryCacheClientCallsEnabled()); + + if (!m_documentLoader) + return; + + Vector<String> pastLoads; + m_documentLoader->takeMemoryCacheLoadsForClientNotification(pastLoads); + + size_t size = pastLoads.size(); + for (size_t i = 0; i < size; ++i) { + CachedResource* resource = cache()->resourceForURL(KURL(ParsedURLString, pastLoads[i])); + + // FIXME: These loads, loaded from cache, but now gone from the cache by the time + // Page::setMemoryCacheClientCallsEnabled(true) is called, will not be seen by the client. + // Consider if there's some efficient way of remembering enough to deliver this client call. + // We have the URL, but not the rest of the response or the length. + if (!resource) + continue; + + ResourceRequest request(resource->url()); + m_client->dispatchDidLoadResourceFromMemoryCache(m_documentLoader.get(), request, resource->response(), resource->encodedSize()); + } +} + +NetworkingContext* FrameLoader::networkingContext() const +{ + return m_networkingContext.get(); +} + +bool FrameLoaderClient::hasHTMLView() const +{ + return true; +} + +Frame* createWindow(Frame* openerFrame, Frame* lookupFrame, const FrameLoadRequest& request, const WindowFeatures& features, bool& created) +{ + ASSERT(!features.dialog || request.frameName().isEmpty()); + + if (!request.frameName().isEmpty() && request.frameName() != "_blank") { + Frame* frame = lookupFrame->tree()->find(request.frameName()); + if (frame && openerFrame->loader()->shouldAllowNavigation(frame)) { + if (!request.resourceRequest().url().isEmpty()) + frame->loader()->loadFrameRequest(request, false, false, 0, 0, SendReferrer); + if (Page* page = frame->page()) + page->chrome()->focus(); + created = false; + return frame; + } + } + + // Sandboxed frames cannot open new auxiliary browsing contexts. + if (isDocumentSandboxed(openerFrame, SandboxNavigation)) + return 0; + + // FIXME: Setting the referrer should be the caller's responsibility. + FrameLoadRequest requestWithReferrer = request; + requestWithReferrer.resourceRequest().setHTTPReferrer(openerFrame->loader()->outgoingReferrer()); + FrameLoader::addHTTPOriginIfNeeded(requestWithReferrer.resourceRequest(), openerFrame->loader()->outgoingOrigin()); + + Page* oldPage = openerFrame->page(); + if (!oldPage) + return 0; + + NavigationAction action; + Page* page = oldPage->chrome()->createWindow(openerFrame, requestWithReferrer, features, action); + 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; +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/FrameLoader.h b/Source/WebCore/loader/FrameLoader.h new file mode 100644 index 0000000..95755d6 --- /dev/null +++ b/Source/WebCore/loader/FrameLoader.h @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) Research In Motion Limited 2009. 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 "CachePolicy.h" +#include "DocumentWriter.h" +#include "FrameLoaderStateMachine.h" +#include "FrameLoaderTypes.h" +#include "HistoryController.h" +#include "NavigationScheduler.h" +#include "PolicyCallback.h" +#include "PolicyChecker.h" +#include "ResourceLoadNotifier.h" +#include "ResourceRequest.h" +#include "SubframeLoader.h" +#include "ThreadableLoader.h" +#include "Timer.h" +#include <wtf/Forward.h> + +namespace WebCore { + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size +class Archive; +#endif +class AuthenticationChallenge; +class CachedFrameBase; +class CachedPage; +class CachedResource; +class Chrome; +class DOMWrapperWorld; +class Document; +class DocumentLoader; +class Event; +class FormData; +class FormState; +class FormSubmission; +class Frame; +class FrameLoaderClient; +class FrameNetworkingContext; +class HistoryItem; +class HTMLFormElement; +class IconLoader; +class NavigationAction; +class NetworkingContext; +class Page; +class ProtectionSpace; +class ResourceError; +class ResourceLoader; +class ResourceResponse; +class ScriptSourceCode; +class ScriptValue; +class SecurityOrigin; +class SerializedScriptValue; +class SharedBuffer; +class SubstituteData; +class TextResourceDecoder; + +struct FrameLoadRequest; +struct WindowFeatures; + +bool isBackForwardLoadType(FrameLoadType); + +class FrameLoader : public Noncopyable { +public: + FrameLoader(Frame*, FrameLoaderClient*); + ~FrameLoader(); + + void init(); + + Frame* frame() const { return m_frame; } + + PolicyChecker* policyChecker() const { return &m_policyChecker; } + HistoryController* history() const { return &m_history; } + ResourceLoadNotifier* notifier() const { return &m_notifer; } + DocumentWriter* writer() const { return &m_writer; } + SubframeLoader* subframeLoader() const { return &m_subframeLoader; } + + // FIXME: This is not cool, people. There are too many different functions that all start loads. + // We should aim to consolidate these into a smaller set of functions, and try to reuse more of + // the logic by extracting common code paths. + + void prepareForLoadStart(); + void setupForReplace(); + void setupForReplaceByMIMEType(const String& newMIMEType); + + void loadURLIntoChildFrame(const KURL&, const String& referer, Frame*); + + void loadFrameRequest(const FrameLoadRequest&, bool lockHistory, bool lockBackForwardList, // Called by submitForm, calls loadPostRequest and loadURL. + PassRefPtr<Event>, PassRefPtr<FormState>, ReferrerPolicy); + + void load(const ResourceRequest&, bool lockHistory); // Called by WebFrame, calls load(ResourceRequest, SubstituteData). + void load(const ResourceRequest&, const SubstituteData&, bool lockHistory); // Called both by WebFrame and internally, calls load(DocumentLoader*). + void load(const ResourceRequest&, const String& frameName, bool lockHistory); // Called by WebPluginController. + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + void loadArchive(PassRefPtr<Archive>); +#endif + + static void reportLocalLoadFailed(Frame*, const String& url); + + unsigned long loadResourceSynchronously(const ResourceRequest&, StoredCredentials, ResourceError&, ResourceResponse&, Vector<char>& data); + + bool canHandleRequest(const ResourceRequest&); + + // Also not cool. + void stopAllLoaders(DatabasePolicy = DatabasePolicyStop); + void stopForUserCancel(bool deferCheckLoadComplete = false); + + bool isLoadingMainResource() const { return m_isLoadingMainResource; } + bool isLoading() const; + bool frameHasLoaded() const; + void transferLoadingResourcesFromPage(Page*); + void dispatchTransferLoadingResourceFromPage(unsigned long, DocumentLoader*, const ResourceRequest&, Page*); + + int numPendingOrLoadingRequests(bool recurse) const; + String referrer() const; + String outgoingReferrer() const; + String outgoingOrigin() const; + + DocumentLoader* activeDocumentLoader() const; + DocumentLoader* documentLoader() const { return m_documentLoader.get(); } + DocumentLoader* policyDocumentLoader() const { return m_policyDocumentLoader.get(); } + DocumentLoader* provisionalDocumentLoader() const { return m_provisionalDocumentLoader.get(); } + FrameState state() const { return m_state; } + static double timeOfLastCompletedLoad(); + + bool shouldUseCredentialStorage(ResourceLoader*); +#if USE(PROTECTION_SPACE_AUTH_CALLBACK) + bool canAuthenticateAgainstProtectionSpace(ResourceLoader* loader, const ProtectionSpace& protectionSpace); +#endif + const ResourceRequest& originalRequest() const; + const ResourceRequest& initialRequest() const; + void receivedMainResourceError(const ResourceError&, bool isComplete); + + bool willLoadMediaElementURL(KURL&); + + 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; + ResourceError interruptionForPolicyChangeError(const ResourceRequest&) const; + + bool isHostedByObjectElement() const; + bool isLoadingMainFrame() const; + bool canShowMIMEType(const String& MIMEType) const; + bool representationExistsForURLScheme(const String& URLScheme); + String generatedMIMETypeForURLScheme(const String& URLScheme); + + void reload(bool endToEndReload = false); + void reloadWithOverrideEncoding(const String& overrideEncoding); + + void didReceiveServerRedirectForProvisionalLoadForFrame(); + void finishedLoadingDocument(DocumentLoader*); + 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*); + void didChangeIcons(DocumentLoader*); + + FrameLoadType loadType() const; + + CachePolicy subresourceCachePolicy() const; + + void didFirstLayout(); + + void didFirstVisuallyNonEmptyLayout(); + + void loadedResourceFromMemoryCache(const CachedResource*); + void tellClientAboutPastMemoryCacheLoads(); + + void checkLoadComplete(); + void detachFromParent(); + void detachViewsAndDocumentLoader(); + + void addExtraFieldsToSubresourceRequest(ResourceRequest&); + void addExtraFieldsToMainResourceRequest(ResourceRequest&); + + static void addHTTPOriginIfNeeded(ResourceRequest&, String origin); + + FrameLoaderClient* client() const { return m_client; } + + void setDefersLoading(bool); + + void changeLocation(PassRefPtr<SecurityOrigin>, const KURL&, const String& referrer, bool lockHistory = true, bool lockBackForwardList = true, bool refresh = false); + void urlSelected(const KURL&, const String& target, PassRefPtr<Event>, bool lockHistory, bool lockBackForwardList, ReferrerPolicy); + + void submitForm(PassRefPtr<FormSubmission>); + + void stop(); + void stopLoading(UnloadEventPolicy, DatabasePolicy = DatabasePolicyStop); + bool closeURL(); + + void didExplicitOpen(); + + // Callbacks from DocumentWriter + void didBeginDocument(bool dispatchWindowObjectAvailable); + void didEndDocument(); + void willSetEncoding(); + + KURL iconURL(); + void commitIconURLToIconDatabase(const KURL&); + + KURL baseURL() const; + + void handledOnloadEvents(); + String userAgent(const KURL&) const; + + void dispatchDidClearWindowObjectInWorld(DOMWrapperWorld*); + void dispatchDidClearWindowObjectsInAllWorlds(); + void dispatchDocumentElementAvailable(); + + void ownerElementSandboxFlagsChanged() { updateSandboxFlags(); } + + bool isSandboxed(SandboxFlags mask) const { return m_sandboxFlags & mask; } + SandboxFlags sandboxFlags() const { return m_sandboxFlags; } + // The following sandbox flags will be forced, regardless of changes to + // the sandbox attribute of any parent frames. + void setForcedSandboxFlags(SandboxFlags flags) { m_forcedSandboxFlags = flags; m_sandboxFlags |= flags; } + + // Mixed content related functions. + static bool isMixedContent(SecurityOrigin* context, const KURL&); + void checkIfDisplayInsecureContent(SecurityOrigin* context, const KURL&); + void checkIfRunInsecureContent(SecurityOrigin* context, const KURL&); + + Frame* opener(); + void setOpener(Frame*); + + bool isProcessingUserGesture(); + + void resetMultipleFormSubmissionProtection(); + + void checkCallImplicitClose(); + + void frameDetached(); + + const KURL& url() const { return m_URL; } + + // setURL is a low-level setter and does not trigger loading. + void setURL(const KURL&); + + void loadDone(); + void finishedParsing(); + void checkCompleted(); + + void checkDidPerformFirstNavigation(); + + bool isComplete() const; + + KURL completeURL(const String& url); + + void cancelAndClear(); + + void setTitle(const String&); + void setIconURL(const String&); + + void commitProvisionalLoad(); + bool isLoadingFromCachedPage() const { return m_loadingFromCachedPage; } + + FrameLoaderStateMachine* stateMachine() const { return &m_stateMachine; } + + void iconLoadDecisionAvailable(); + + bool shouldAllowNavigation(Frame* targetFrame) const; + Frame* findFrameForNavigation(const AtomicString& name); + + void startIconLoader(); + + void applyUserAgent(ResourceRequest& request); + + bool shouldInterruptLoadForXFrameOptions(const String&, const KURL&); + + void open(CachedFrameBase&); + + // FIXME: Should these really be public? + void completed(); + bool allAncestorsAreComplete() const; // including this + bool allChildrenAreComplete() const; // immediate children, not all descendants + void clientRedirected(const KURL&, double delay, double fireDate, bool lockBackForwardList); + void clientRedirectCancelledOrFinished(bool cancelWithLoadInProgress); + void loadItem(HistoryItem*, FrameLoadType); + + // FIXME: This is public because this asynchronous callback from the FrameLoaderClient + // uses the policy machinery (and therefore is called via the PolicyChecker). Once we + // introduce a proper callback type for this function, we should make it private again. + void continueLoadAfterWillSubmitForm(); + + bool suppressOpenerInNewFrame() const { return m_suppressOpenerInNewFrame; } + + static ObjectContentType defaultObjectContentType(const KURL& url, const String& mimeType); + + void clear(bool clearWindowProperties = true, bool clearScriptObjects = true, bool clearFrameView = true); + + bool quickRedirectComing() const { return m_quickRedirectComing; } + + bool shouldClose(); + + void started(); + + bool pageDismissalEventBeingDispatched() const { return m_pageDismissalEventBeingDispatched; } + + NetworkingContext* networkingContext() const; + +private: + void checkTimerFired(Timer<FrameLoader>*); + + void loadSameDocumentItem(HistoryItem*); + void loadDifferentDocumentItem(HistoryItem*, FrameLoadType); + + void loadProvisionalItemFromCachedPage(); + + void receivedFirstData(); + + void updateFirstPartyForCookies(); + void setFirstPartyForCookies(const KURL&); + + void addExtraFieldsToRequest(ResourceRequest&, FrameLoadType loadType, bool isMainResource, bool cookiePolicyURLFromRequest); + + // Also not cool. + void stopLoadingSubframes(); + + void clearProvisionalLoad(); + void markLoadComplete(); + void transitionToCommitted(PassRefPtr<CachedPage>); + void frameLoadCompleted(); + + void mainReceivedError(const ResourceError&, bool isComplete); + + static void callContinueLoadAfterNavigationPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + static void callContinueLoadAfterNewWindowPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, const NavigationAction&, bool shouldContinue); + static void callContinueFragmentScrollAfterNavigationPolicy(void*, const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + + bool fireBeforeUnloadEvent(Chrome*); + + void continueLoadAfterNavigationPolicy(const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); + void continueLoadAfterNewWindowPolicy(const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, const NavigationAction&, bool shouldContinue); + void continueFragmentScrollAfterNavigationPolicy(const ResourceRequest&, bool shouldContinue); + + bool shouldScrollToAnchor(bool isFormSubmission, const String& httpMethod, FrameLoadType, const KURL&); + + void checkLoadCompleteForThisFrame(); + + void setDocumentLoader(DocumentLoader*); + void setPolicyDocumentLoader(DocumentLoader*); + void setProvisionalDocumentLoader(DocumentLoader*); + + void setState(FrameState); + + void closeOldDataSources(); + void prepareForCachedPageRestore(); + + bool shouldReloadToHandleUnreachableURL(DocumentLoader*); + + void dispatchDidCommitLoad(); + + void urlSelected(const FrameLoadRequest&, PassRefPtr<Event>, bool lockHistory, bool lockBackForwardList, ReferrerPolicy, ShouldReplaceDocumentIfJavaScriptURL); + + void loadWithDocumentLoader(DocumentLoader*, FrameLoadType, PassRefPtr<FormState>); // Calls continueLoadAfterNavigationPolicy + void load(DocumentLoader*); // Calls loadWithDocumentLoader + + void loadWithNavigationAction(const ResourceRequest&, const NavigationAction&, // Calls loadWithDocumentLoader + bool lockHistory, FrameLoadType, PassRefPtr<FormState>); + + void loadPostRequest(const ResourceRequest&, const String& referrer, // Called by loadFrameRequest, calls loadWithNavigationAction + const String& frameName, bool lockHistory, FrameLoadType, PassRefPtr<Event>, PassRefPtr<FormState>); + void loadURL(const KURL&, const String& referrer, const String& frameName, // Called by loadFrameRequest, calls loadWithNavigationAction or dispatches to navigation policy delegate + bool lockHistory, FrameLoadType, PassRefPtr<Event>, PassRefPtr<FormState>); + + bool shouldReload(const KURL& currentURL, const KURL& destinationURL); + + void requestFromDelegate(ResourceRequest&, unsigned long& identifier, ResourceError&); + + void recursiveCheckLoadComplete(); + + void detachChildren(); + void closeAndRemoveChild(Frame*); + + void loadInSameDocument(const KURL&, SerializedScriptValue* stateObject, bool isNewNavigation); + + void provisionalLoadStarted(); + + bool didOpenURL(const KURL&); + + void scheduleCheckCompleted(); + void scheduleCheckLoadComplete(); + void startCheckCompleteTimer(); + + KURL originalRequestURL() const; + + bool shouldTreatURLAsSameAsCurrent(const KURL&) const; + + void updateSandboxFlags(); + + Frame* m_frame; + FrameLoaderClient* m_client; + + mutable PolicyChecker m_policyChecker; + mutable HistoryController m_history; + mutable ResourceLoadNotifier m_notifer; + mutable DocumentWriter m_writer; + mutable SubframeLoader m_subframeLoader; + mutable FrameLoaderStateMachine m_stateMachine; + + 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; + + bool m_delegateIsHandlingProvisionalLoadError; + + bool m_quickRedirectComing; + bool m_sentRedirectNotification; + bool m_inStopAllLoaders; + + String m_outgoingReferrer; + + bool m_isExecutingJavaScriptFormAction; + + bool m_didCallImplicitClose; + bool m_wasUnloadEventEmitted; + bool m_pageDismissalEventBeingDispatched; + bool m_isComplete; + bool m_isLoadingMainResource; + + RefPtr<SerializedScriptValue> m_pendingStateObject; + + KURL m_URL; + KURL m_workingURL; + + OwnPtr<IconLoader> m_iconLoader; + bool m_mayLoadIconLater; + + bool m_needsClear; + + KURL m_submittedFormURL; + + Timer<FrameLoader> m_checkTimer; + bool m_shouldCallCheckCompleted; + bool m_shouldCallCheckLoadComplete; + + Frame* m_opener; + HashSet<Frame*> m_openedFrames; + + bool m_didPerformFirstNavigation; + bool m_loadingFromCachedPage; + bool m_suppressOpenerInNewFrame; + + SandboxFlags m_sandboxFlags; + SandboxFlags m_forcedSandboxFlags; + + RefPtr<FrameNetworkingContext> m_networkingContext; + + KURL m_previousUrl; +}; + +// This function is called by createWindow() in JSDOMWindowBase.cpp, for example, for +// modal dialog creation. The lookupFrame is for looking up the frame name in case +// the frame name references a frame different from the openerFrame, e.g. when it is +// "_self" or "_parent". +// +// FIXME: Consider making this function part of an appropriate class (not FrameLoader) +// and moving it to a more appropriate location. +Frame* createWindow(Frame* openerFrame, Frame* lookupFrame, const FrameLoadRequest&, const WindowFeatures&, bool& created); + +} // namespace WebCore + +#endif // FrameLoader_h diff --git a/Source/WebCore/loader/FrameLoaderClient.h b/Source/WebCore/loader/FrameLoaderClient.h new file mode 100644 index 0000000..73860a5 --- /dev/null +++ b/Source/WebCore/loader/FrameLoaderClient.h @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 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 "ScrollTypes.h" +#include <wtf/Forward.h> +#include <wtf/Vector.h> + +#if PLATFORM(MAC) +#ifdef __OBJC__ +#import <Foundation/Foundation.h> +typedef id RemoteAXObjectRef; +#else +typedef void* RemoteAXObjectRef; +#endif +#endif + +typedef class _jobject* jobject; + +#if PLATFORM(MAC) && !defined(__OBJC__) +class NSCachedURLResponse; +class NSView; +#endif + +namespace WebCore { + + class AuthenticationChallenge; + class CachedFrame; + class Color; + class DOMWrapperWorld; + class DocumentLoader; + class Element; + class FormState; + class Frame; + class FrameLoader; + class FrameNetworkingContext; + class HistoryItem; + class HTMLAppletElement; + class HTMLFormElement; + class HTMLFrameOwnerElement; +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + class HTMLMediaElement; +#endif + class HTMLPlugInElement; + class IntSize; + class KURL; + class NavigationAction; + class Page; + class ProtectionSpace; + class PluginView; + class PolicyChecker; + class ResourceError; + class ResourceHandle; + class ResourceLoader; + class ResourceRequest; + class ResourceResponse; + class SecurityOrigin; + class SharedBuffer; + class SubstituteData; + class Widget; + + typedef void (PolicyChecker::*FramePolicyFunction)(PolicyAction); + + class FrameLoaderClient { + public: + // An inline function cannot be the first non-abstract virtual function declared + // in the class as it results in the vtable being generated as a weak symbol. + // This hurts performance (in Mac OS X at least, when loadig frameworks), so we + // don't want to do it in WebKit. + virtual bool hasHTMLView() const; + + virtual ~FrameLoaderClient() { } + + virtual void frameLoaderDestroyed() = 0; + + virtual bool hasWebView() const = 0; // mainly for assertions + + 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 bool shouldUseCredentialStorage(DocumentLoader*, unsigned long identifier) = 0; + virtual void dispatchDidReceiveAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) = 0; + virtual void dispatchDidCancelAuthenticationChallenge(DocumentLoader*, unsigned long identifier, const AuthenticationChallenge&) = 0; +#if USE(PROTECTION_SPACE_AUTH_CALLBACK) + virtual bool canAuthenticateAgainstProtectionSpace(DocumentLoader*, unsigned long identifier, const ProtectionSpace&) = 0; +#endif + 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 dispatchDidNavigateWithinPage() { } + virtual void dispatchDidChangeLocationWithinPage() = 0; + virtual void dispatchDidPushStateWithinPage() = 0; + virtual void dispatchDidReplaceStateWithinPage() = 0; + virtual void dispatchDidPopStateWithinPage() = 0; + virtual void dispatchWillClose() = 0; + virtual void dispatchDidReceiveIcon() = 0; + virtual void dispatchDidStartProvisionalLoad() = 0; + virtual void dispatchDidReceiveTitle(const String& title) = 0; + virtual void dispatchDidChangeIcons() = 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 void dispatchDidFirstVisuallyNonEmptyLayout() = 0; + + virtual Frame* dispatchCreatePage(const NavigationAction&) = 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 dispatchWillSendSubmitEvent(HTMLFormElement*) = 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() = 0; + virtual void updateGlobalHistoryRedirectLinks() = 0; + + virtual bool shouldGoToHistoryItem(HistoryItem*) const = 0; + virtual void dispatchDidAddBackForwardItem(HistoryItem*) const = 0; + virtual void dispatchDidRemoveBackForwardItem(HistoryItem*) const = 0; + virtual void dispatchDidChangeBackForwardIndex() const = 0; + + // This frame has displayed inactive content (such as an image) from an + // insecure source. Inactive content cannot spread to other frames. + virtual void didDisplayInsecureContent() = 0; + + // The indicated security origin has run active content (such as a + // script) from an insecure source. Note that the insecure content can + // spread to other frames in the same origin. + virtual void didRunInsecureContent(SecurityOrigin*) = 0; + + 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 canShowMIMETypeAsHTML(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 savePlatformDataToCachedFrame(CachedFrame*) = 0; + virtual void transitionToCommittedFromCachedFrame(CachedFrame*) = 0; + virtual void transitionToCommittedForNewPage() = 0; + + virtual void didSaveToPageCache() = 0; + virtual void didRestoreFromPageCache() = 0; + + virtual void dispatchDidBecomeFrameset(bool) = 0; // Can change due to navigation or DOM modification. + + 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 void didTransferChildFrameToNewDocument(Page* oldPage) = 0; + virtual void transferLoadingResourceFromPage(unsigned long identifier, DocumentLoader*, const ResourceRequest&, Page* oldPage) = 0; + virtual PassRefPtr<Widget> createPlugin(const IntSize&, HTMLPlugInElement*, const KURL&, const Vector<String>&, const Vector<String>&, const String&, bool loadManually) = 0; + virtual void redirectDataToPlugin(Widget* pluginWidget) = 0; + + virtual PassRefPtr<Widget> createJavaAppletWidget(const IntSize&, HTMLAppletElement*, const KURL& baseURL, const Vector<String>& paramNames, const Vector<String>& paramValues) = 0; + + virtual void dispatchDidFailToStartPlugin(const PluginView*) const { } +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + virtual PassRefPtr<Widget> createMediaPlayerProxyPlugin(const IntSize&, HTMLMediaElement*, const KURL&, const Vector<String>&, const Vector<String>&, const String&) = 0; + virtual void hideMediaPlayerProxyPlugin(Widget*) = 0; + virtual void showMediaPlayerProxyPlugin(Widget*) = 0; +#endif + + virtual ObjectContentType objectContentType(const KURL& url, const String& mimeType) = 0; + virtual String overrideMediaType() const = 0; + + virtual void dispatchDidClearWindowObjectInWorld(DOMWrapperWorld*) = 0; + virtual void documentElementAvailable() = 0; + virtual void didPerformFirstNavigation() const = 0; // "Navigation" here means a transition from one page to another that ends up in the back/forward list. + +#if USE(V8) + virtual void didCreateScriptContextForFrame() = 0; + virtual void didDestroyScriptContextForFrame() = 0; + virtual void didCreateIsolatedScriptContext() = 0; + virtual bool allowScriptExtension(const String& extensionName, int extensionGroup) = 0; +#endif + + virtual void registerForIconNotification(bool listen = true) = 0; +#ifdef ANDROID_APPLE_TOUCH_ICON + virtual void dispatchDidReceiveTouchIconURL(const String& url, bool precomposed) = 0; +#endif + +#if PLATFORM(MAC) + // Allow an accessibility object to retrieve a Frame parent if there's no PlatformWidget. + virtual RemoteAXObjectRef accessibilityRemoteObject() = 0; +#if ENABLE(JAVA_BRIDGE) + virtual jobject javaApplet(NSView*) { return 0; } +#endif + virtual NSCachedURLResponse* willCacheResponse(DocumentLoader*, unsigned long identifier, NSCachedURLResponse*) const = 0; +#endif +#if USE(CFNETWORK) + virtual bool shouldCacheResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse&, const unsigned char* data, unsigned long long length) = 0; +#endif + + virtual bool shouldUsePluginDocument(const String& /*mimeType*/) const { return false; } + virtual bool shouldLoadMediaElementURL(const KURL&) const { return true; } + + virtual void didChangeScrollOffset() { } + + virtual bool allowJavaScript(bool enabledPerSettings) { return enabledPerSettings; } + virtual bool allowPlugins(bool enabledPerSettings) { return enabledPerSettings; } + virtual bool allowImages(bool enabledPerSettings) { return enabledPerSettings; } + + // This callback notifies the client that the frame was about to run + // JavaScript but did not because allowJavaScript returned false. We + // have a separate callback here because there are a number of places + // that need to know if JavaScript is enabled but are not necessarily + // preparing to execute script. + virtual void didNotAllowScript() { } + // This callback is similar, but for plugins. + virtual void didNotAllowPlugins() { } + + virtual PassRefPtr<FrameNetworkingContext> createNetworkingContext() = 0; + }; + +} // namespace WebCore + +#endif // FrameLoaderClient_h diff --git a/Source/WebCore/loader/FrameLoaderStateMachine.cpp b/Source/WebCore/loader/FrameLoaderStateMachine.cpp new file mode 100644 index 0000000..790b144 --- /dev/null +++ b/Source/WebCore/loader/FrameLoaderStateMachine.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 Google 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 Google, Inc. 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 THE COPYRIGHT HOLDERS AND 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 "FrameLoaderStateMachine.h" + +#include <wtf/Assertions.h> + +namespace WebCore { + + +FrameLoaderStateMachine::FrameLoaderStateMachine() + : m_state(Uninitialized) +{ +} + +bool FrameLoaderStateMachine::committingFirstRealLoad() const +{ + return m_state == DisplayingInitialEmptyDocument; +} + +bool FrameLoaderStateMachine::committedFirstRealDocumentLoad() const +{ + return m_state >= DisplayingInitialEmptyDocumentPostCommit; +} + +bool FrameLoaderStateMachine::creatingInitialEmptyDocument() const +{ + return m_state == CreatingInitialEmptyDocument; +} + +bool FrameLoaderStateMachine::isDisplayingInitialEmptyDocument() const +{ + return m_state == DisplayingInitialEmptyDocument || m_state == DisplayingInitialEmptyDocumentPostCommit; +} + +bool FrameLoaderStateMachine::firstLayoutDone() const +{ + return m_state == FirstLayoutDone; +} + +void FrameLoaderStateMachine::advanceTo(State state) +{ + ASSERT(State(m_state + 1) == state || (firstLayoutDone() && state == CommittedFirstRealLoad)); + m_state = state; +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/FrameLoaderStateMachine.h b/Source/WebCore/loader/FrameLoaderStateMachine.h new file mode 100644 index 0000000..c3408c2 --- /dev/null +++ b/Source/WebCore/loader/FrameLoaderStateMachine.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 Google 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 Google, Inc. 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 THE COPYRIGHT HOLDERS AND 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 FrameLoaderStateMachine_h +#define FrameLoaderStateMachine_h + +#include <wtf/Noncopyable.h> + +namespace WebCore { + +// Encapsulates a state machine for FrameLoader. Note that this is different from FrameState, +// which stores the state of the current load that FrameLoader is executing. +class FrameLoaderStateMachine : public Noncopyable { +public: + FrameLoaderStateMachine(); + + // Once a load has been committed, the state may + // alternate between CommittedFirstRealLoad and FirstLayoutDone. + // Otherwise, the states only go down the list. + enum State { + Uninitialized, + CreatingInitialEmptyDocument, + DisplayingInitialEmptyDocument, + DisplayingInitialEmptyDocumentPostCommit, + CommittedFirstRealLoad, + FirstLayoutDone + }; + + bool committingFirstRealLoad() const; + bool committedFirstRealDocumentLoad() const; + bool creatingInitialEmptyDocument() const; + bool isDisplayingInitialEmptyDocument() const; + bool firstLayoutDone() const; + void advanceTo(State); + +private: + State m_state; +}; + +} // namespace WebCore + +#endif // FrameLoaderStateMachine_h diff --git a/Source/WebCore/loader/FrameLoaderTypes.h b/Source/WebCore/loader/FrameLoaderTypes.h new file mode 100644 index 0000000..016de19 --- /dev/null +++ b/Source/WebCore/loader/FrameLoaderTypes.h @@ -0,0 +1,129 @@ +/* + * 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, + }; + + // NOTE: Keep in sync with WebKit/mac/WebView/WebFramePrivate.h and WebKit/win/Interfaces/IWebFramePrivate.idl + enum FrameLoadType { + FrameLoadTypeStandard, + FrameLoadTypeBack, + FrameLoadTypeForward, + FrameLoadTypeIndexedBackForward, // a multi-item hop in the backforward list + FrameLoadTypeReload, + // Skipped value: 'FrameLoadTypeReloadAllowingStaleData', still present in mac/win public API. Ready to be reused + FrameLoadTypeSame = FrameLoadTypeReload + 2, // user loads same URL again (but not reload button) + FrameLoadTypeRedirectWithLockedBackForwardList, // FIXME: Merge "lockBackForwardList", "lockHistory", "quickRedirect" and "clientRedirect" into a single concept of redirect. + FrameLoadTypeReplace, + FrameLoadTypeReloadFromOrigin, + FrameLoadTypeBackWMLDeckNotAccessible + }; + + enum NavigationType { + NavigationTypeLinkClicked, + NavigationTypeFormSubmitted, + NavigationTypeBackForward, + NavigationTypeReload, + NavigationTypeFormResubmitted, + NavigationTypeOther + }; + + enum DatabasePolicy { + DatabasePolicyStop, // The database thread should be stopped and database connections closed. + DatabasePolicyContinue + }; + + enum ObjectContentType { + ObjectContentNone, + ObjectContentImage, + ObjectContentFrame, + ObjectContentNetscapePlugin, + ObjectContentOtherPlugin + }; + + enum UnloadEventPolicy { + UnloadEventPolicyNone, + UnloadEventPolicyUnloadOnly, + UnloadEventPolicyUnloadAndPageHide + }; + + enum ReferrerPolicy { + SendReferrer, + NoReferrer + }; + + enum SandboxFlag { + SandboxNone = 0, + SandboxNavigation = 1, + SandboxPlugins = 1 << 1, + SandboxOrigin = 1 << 2, + SandboxForms = 1 << 3, + SandboxScripts = 1 << 4, + SandboxTopNavigation = 1 << 5, + SandboxAll = -1 // Mask with all bits set to 1. + }; + + enum SecurityCheckPolicy { + SkipSecurityCheck, + DoSecurityCheck + }; + + // Passed to FrameLoader::urlSelected() and ScriptController::executeIfJavaScriptURL() + // to control whether, in the case of a JavaScript URL, executeIfJavaScriptURL() should + // replace the document. It is a FIXME to eliminate this extra parameter from + // executeIfJavaScriptURL(), in which case this enum can go away. + enum ShouldReplaceDocumentIfJavaScriptURL { + ReplaceDocumentIfJavaScriptURL, + DoNotReplaceDocumentIfJavaScriptURL + }; + + enum ReasonForCallingAllowPlugins { + AboutToInstantiatePlugin, + NotAboutToInstantiatePlugin + }; + + typedef int SandboxFlags; +} + +#endif diff --git a/Source/WebCore/loader/FrameNetworkingContext.h b/Source/WebCore/loader/FrameNetworkingContext.h new file mode 100644 index 0000000..dff1144 --- /dev/null +++ b/Source/WebCore/loader/FrameNetworkingContext.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) + + 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 FrameNetworkingContext_h +#define FrameNetworkingContext_h + +#include "Frame.h" +#include "NetworkingContext.h" + +namespace WebCore { + +class FrameNetworkingContext : public NetworkingContext { +public: + void invalidate() + { + m_frame = 0; + } + +protected: + FrameNetworkingContext(Frame* frame) + : m_frame(frame) + { + } + + Frame* frame() const { return m_frame; } + +private: + virtual bool isValid() const { return m_frame; } + + Frame* m_frame; +}; + +} + +#endif // FrameNetworkingContext_h diff --git a/Source/WebCore/loader/HistoryController.cpp b/Source/WebCore/loader/HistoryController.cpp new file mode 100644 index 0000000..ff733a9 --- /dev/null +++ b/Source/WebCore/loader/HistoryController.cpp @@ -0,0 +1,681 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "HistoryController.h" + +#include "BackForwardController.h" +#include "CachedPage.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "FrameLoaderStateMachine.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "HistoryItem.h" +#include "Logging.h" +#include "Page.h" +#include "PageCache.h" +#include "PageGroup.h" +#include "Settings.h" +#include <wtf/text/CString.h> + +#if USE(PLATFORM_STRATEGIES) +#include "PlatformStrategies.h" +#include "VisitedLinkStrategy.h" +#endif + +namespace WebCore { + +static inline void addVisitedLink(Page* page, const KURL& url) +{ +#if USE(PLATFORM_STRATEGIES) + platformStrategies()->visitedLinkStrategy()->addVisitedLink(page, visitedLinkHash(url.string().characters(), url.string().length())); +#else + page->group().addVisitedLink(url); +#endif +} + +HistoryController::HistoryController(Frame* frame) + : m_frame(frame) + , m_frameLoadComplete(true) +{ +} + +HistoryController::~HistoryController() +{ +} + +void HistoryController::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_frame->loader()->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 HistoryController::restoreScrollPositionAndViewState() +{ + if (!m_frame->loader()->stateMachine()->committedFirstRealDocumentLoad()) + return; + + ASSERT(m_currentItem); + + // 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_currentItem) + 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_frame->loader()->client()->restoreViewState(); + + if (FrameView* view = m_frame->view()) + if (!view->wasScrolledByUser()) + view->setScrollPosition(m_currentItem->scrollPoint()); +} + +void HistoryController::updateBackForwardListForFragmentScroll() +{ + updateBackForwardListClippedAtTarget(false); +} + +void HistoryController::saveDocumentState() +{ + // FIXME: Reading this bit of FrameLoader state here is unfortunate. I need to study + // this more to see if we can remove this dependency. + if (m_frame->loader()->stateMachine()->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 we keep track of the end of a page transition with m_frameLoadComplete. We + // leverage the checkLoadComplete recursion to achieve this goal. + + HistoryItem* item = m_frameLoadComplete ? m_currentItem.get() : m_previousItem.get(); + if (!item) + return; + + Document* document = m_frame->document(); + ASSERT(document); + + if (item->isCurrentDocument(document)) { + LOG(Loading, "WebCoreLoading %s: saving form state to %p", m_frame->tree()->uniqueName().string().utf8().data(), item); + item->setDocumentState(document->formElementsState()); + } +} + +// Walk the frame tree, telling all frames to save their form state into their current +// history item. +void HistoryController::saveDocumentAndScrollState() +{ + for (Frame* frame = m_frame; frame; frame = frame->tree()->traverseNext(m_frame)) { + frame->loader()->history()->saveDocumentState(); + frame->loader()->history()->saveScrollPositionAndViewStateToItem(frame->loader()->history()->currentItem()); + } +} + +void HistoryController::restoreDocumentState() +{ + Document* doc = m_frame->document(); + + HistoryItem* itemToRestore = 0; + + switch (m_frame->loader()->loadType()) { + case FrameLoadTypeReload: + case FrameLoadTypeReloadFromOrigin: + case FrameLoadTypeSame: + case FrameLoadTypeReplace: + break; + case FrameLoadTypeBack: + case FrameLoadTypeBackWMLDeckNotAccessible: + case FrameLoadTypeForward: + case FrameLoadTypeIndexedBackForward: + case FrameLoadTypeRedirectWithLockedBackForwardList: + case FrameLoadTypeStandard: + itemToRestore = m_currentItem.get(); + } + + if (!itemToRestore) + return; + + LOG(Loading, "WebCoreLoading %s: restoring form state from %p", m_frame->tree()->uniqueName().string().utf8().data(), itemToRestore); + doc->setStateForNewFormElements(itemToRestore->documentState()); +} + +void HistoryController::invalidateCurrentItemCachedPage() +{ + // When we are pre-commit, the currentItem is where the pageCache data resides + CachedPage* cachedPage = pageCache()->get(currentItem()); + + // 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(currentItem()); +} + +// Main funnel for navigating to a previous location (back/forward, non-search snap-back) +// This includes recursion to handle loading into framesets properly +void HistoryController::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_frame->loader()->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 + HistoryItem* currentItem = page->backForward()->currentItem(); + page->backForward()->setCurrentItem(targetItem); + Settings* settings = m_frame->settings(); + page->setGlobalHistoryItem((!settings || settings->privateBrowsingEnabled()) ? 0 : targetItem); + recursiveGoToItem(targetItem, currentItem, type); +} + +void HistoryController::updateForBackForwardNavigation() +{ +#if !LOG_DISABLED + if (m_frame->loader()->documentLoader()) + LOG(History, "WebCoreHistory: Updating History for back/forward navigation in frame %s", m_frame->loader()->documentLoader()->title().utf8().data()); +#endif + + // Must grab the current scroll position before disturbing it + if (!m_frameLoadComplete) + saveScrollPositionAndViewStateToItem(m_previousItem.get()); +} + +void HistoryController::updateForReload() +{ +#if !LOG_DISABLED + if (m_frame->loader()->documentLoader()) + LOG(History, "WebCoreHistory: Updating History for reload in frame %s", m_frame->loader()->documentLoader()->title().utf8().data()); +#endif + + if (m_currentItem) { + pageCache()->remove(m_currentItem.get()); + + if (m_frame->loader()->loadType() == FrameLoadTypeReload || m_frame->loader()->loadType() == FrameLoadTypeReloadFromOrigin) + saveScrollPositionAndViewStateToItem(m_currentItem.get()); + + // Sometimes loading a page again leads to a different result because of cookies. Bugzilla 4072 + if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty()) + m_currentItem->setURL(m_frame->loader()->documentLoader()->requestURL()); + } +} + +// There are 3 things you might think of as "history", all of which are handled by these functions. +// +// 1) Back/forward: The m_currentItem is part of this mechanism. +// 2) Global history: Handled by the client. +// 3) Visited links: Handled by the PageGroup. + +void HistoryController::updateForStandardLoad(HistoryUpdateType updateType) +{ + LOG(History, "WebCoreHistory: Updating History for Standard Load in frame %s", m_frame->loader()->documentLoader()->url().string().ascii().data()); + + FrameLoader* frameLoader = m_frame->loader(); + + Settings* settings = m_frame->settings(); + bool needPrivacy = !settings || settings->privateBrowsingEnabled(); + const KURL& historyURL = frameLoader->documentLoader()->urlForHistory(); + + if (!frameLoader->documentLoader()->isClientRedirect()) { + if (!historyURL.isEmpty()) { + if (updateType != UpdateAllExceptBackForwardList) + updateBackForwardListClippedAtTarget(true); + if (!needPrivacy) { + frameLoader->client()->updateGlobalHistory(); + frameLoader->documentLoader()->setDidCreateGlobalHistoryEntry(true); + if (frameLoader->documentLoader()->unreachableURL().isEmpty()) + frameLoader->client()->updateGlobalHistoryRedirectLinks(); + } + if (Page* page = m_frame->page()) + page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForward()->currentItem()); + } + } else if (frameLoader->documentLoader()->unreachableURL().isEmpty() && m_currentItem) { + m_currentItem->setURL(frameLoader->documentLoader()->url()); + m_currentItem->setFormInfoFromRequest(frameLoader->documentLoader()->request()); + } + + if (!historyURL.isEmpty() && !needPrivacy) { + if (Page* page = m_frame->page()) + addVisitedLink(page, historyURL); + + if (!frameLoader->documentLoader()->didCreateGlobalHistoryEntry() && frameLoader->documentLoader()->unreachableURL().isEmpty() && !frameLoader->url().isEmpty()) + frameLoader->client()->updateGlobalHistoryRedirectLinks(); + } +} + +void HistoryController::updateForRedirectWithLockedBackForwardList() +{ +#if !LOG_DISABLED + if (m_frame->loader()->documentLoader()) + LOG(History, "WebCoreHistory: Updating History for redirect load in frame %s", m_frame->loader()->documentLoader()->title().utf8().data()); +#endif + + Settings* settings = m_frame->settings(); + bool needPrivacy = !settings || settings->privateBrowsingEnabled(); + const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory(); + + if (m_frame->loader()->documentLoader()->isClientRedirect()) { + if (!m_currentItem && !m_frame->tree()->parent()) { + if (!historyURL.isEmpty()) { + updateBackForwardListClippedAtTarget(true); + if (!needPrivacy) { + m_frame->loader()->client()->updateGlobalHistory(); + m_frame->loader()->documentLoader()->setDidCreateGlobalHistoryEntry(true); + if (m_frame->loader()->documentLoader()->unreachableURL().isEmpty()) + m_frame->loader()->client()->updateGlobalHistoryRedirectLinks(); + } + if (Page* page = m_frame->page()) + page->setGlobalHistoryItem(needPrivacy ? 0 : page->backForward()->currentItem()); + } + } + if (m_currentItem) { + m_currentItem->setURL(m_frame->loader()->documentLoader()->url()); + m_currentItem->setFormInfoFromRequest(m_frame->loader()->documentLoader()->request()); + } + } else { + Frame* parentFrame = m_frame->tree()->parent(); + if (parentFrame && parentFrame->loader()->history()->m_currentItem) + parentFrame->loader()->history()->m_currentItem->setChildItem(createItem(true)); + } + + if (!historyURL.isEmpty() && !needPrivacy) { + if (Page* page = m_frame->page()) + addVisitedLink(page, historyURL); + + if (!m_frame->loader()->documentLoader()->didCreateGlobalHistoryEntry() && m_frame->loader()->documentLoader()->unreachableURL().isEmpty() && !m_frame->loader()->url().isEmpty()) + m_frame->loader()->client()->updateGlobalHistoryRedirectLinks(); + } +} + +void HistoryController::updateForClientRedirect() +{ +#if !LOG_DISABLED + if (m_frame->loader()->documentLoader()) + LOG(History, "WebCoreHistory: Updating History for client redirect in frame %s", m_frame->loader()->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_currentItem) { + m_currentItem->clearDocumentState(); + m_currentItem->clearScrollPoint(); + } + + Settings* settings = m_frame->settings(); + bool needPrivacy = !settings || settings->privateBrowsingEnabled(); + const KURL& historyURL = m_frame->loader()->documentLoader()->urlForHistory(); + + if (!historyURL.isEmpty() && !needPrivacy) { + if (Page* page = m_frame->page()) + addVisitedLink(page, historyURL); + } +} + +void HistoryController::updateForCommit() +{ + FrameLoader* frameLoader = m_frame->loader(); +#if !LOG_DISABLED + if (frameLoader->documentLoader()) + LOG(History, "WebCoreHistory: Updating History for commit in frame %s", frameLoader->documentLoader()->title().utf8().data()); +#endif + FrameLoadType type = frameLoader->loadType(); + if (isBackForwardLoadType(type) || + ((type == FrameLoadTypeReload || type == FrameLoadTypeReloadFromOrigin) && !frameLoader->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_frameLoadComplete = false; + m_previousItem = m_currentItem; + ASSERT(m_provisionalItem); + m_currentItem = m_provisionalItem; + m_provisionalItem = 0; + } +} + +void HistoryController::updateForSameDocumentNavigation() +{ + if (m_frame->loader()->url().isEmpty()) + return; + + Settings* settings = m_frame->settings(); + if (!settings || settings->privateBrowsingEnabled()) + return; + + Page* page = m_frame->page(); + if (!page) + return; + + addVisitedLink(page, m_frame->loader()->url()); +} + +void HistoryController::updateForFrameLoadCompleted() +{ + // 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 track that + // the load is complete so that we use the current item instead. + m_frameLoadComplete = true; +} + +void HistoryController::setCurrentItem(HistoryItem* item) +{ + m_frameLoadComplete = false; + m_previousItem = m_currentItem; + m_currentItem = item; +} + +void HistoryController::setCurrentItemTitle(const String& title) +{ + if (m_currentItem) + m_currentItem->setTitle(title); +} + +bool HistoryController::currentItemShouldBeReplaced() const +{ + // From the HTML5 spec for location.assign(): + // "If the browsing context's session history contains only one Document, + // and that was the about:blank Document created when the browsing context + // was created, then the navigation must be done with replacement enabled." + return m_currentItem && !m_previousItem && equalIgnoringCase(m_currentItem->urlString(), blankURL()); +} + +void HistoryController::setProvisionalItem(HistoryItem* item) +{ + m_provisionalItem = item; +} + +PassRefPtr<HistoryItem> HistoryController::createItem(bool useOriginal) +{ + DocumentLoader* documentLoader = m_frame->loader()->documentLoader(); + + KURL unreachableURL = documentLoader ? documentLoader->unreachableURL() : KURL(); + + KURL url; + KURL originalURL; + + if (!unreachableURL.isEmpty()) { + url = unreachableURL; + originalURL = unreachableURL; + } else { + originalURL = documentLoader ? documentLoader->originalURL() : KURL(); + if (useOriginal) + url = originalURL; + else if (documentLoader) + url = documentLoader->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()->uniqueName() : ""; + String title = documentLoader ? documentLoader->title() : ""; + + RefPtr<HistoryItem> item = HistoryItem::create(url, m_frame->tree()->uniqueName(), parent, title); + item->setOriginalURLString(originalURL.string()); + + if (!unreachableURL.isEmpty() || !documentLoader || documentLoader->response().httpStatusCode() >= 400) + item->setLastVisitWasFailure(true); + + // Save form state if this is a POST + if (documentLoader) { + if (useOriginal) + item->setFormInfoFromRequest(documentLoader->originalRequest()); + else + item->setFormInfoFromRequest(documentLoader->request()); + } + + // Set the item for which we will save document state + m_frameLoadComplete = false; + m_previousItem = m_currentItem; + m_currentItem = item; + + return item.release(); +} + +PassRefPtr<HistoryItem> HistoryController::createItemTree(Frame* targetFrame, bool clipAtTarget) +{ + RefPtr<HistoryItem> bfItem = createItem(m_frame->tree()->parent() ? true : false); + if (!m_frameLoadComplete) + saveScrollPositionAndViewStateToItem(m_previousItem.get()); + + if (!clipAtTarget || m_frame != targetFrame) { + // save frame state for items that aren't loading (khtml doesn't save those) + saveDocumentState(); + + // clipAtTarget is false for navigations within the same document, so + // we should copy the documentSequenceNumber over to the newly create + // item. Non-target items are just clones, and they should therefore + // preserve the same itemSequenceNumber. + if (m_previousItem) { + if (m_frame != targetFrame) + bfItem->setItemSequenceNumber(m_previousItem->itemSequenceNumber()); + bfItem->setDocumentSequenceNumber(m_previousItem->documentSequenceNumber()); + } + + 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->history()->createItemTree(targetFrame, clipAtTarget)); + } + } + // FIXME: Eliminate the isTargetItem flag in favor of itemSequenceNumber. + if (m_frame == targetFrame) + bfItem->setIsTargetItem(true); + return bfItem; +} + +// 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 HistoryController::recursiveGoToItem(HistoryItem* item, HistoryItem* fromItem, FrameLoadType type) +{ + ASSERT(item); + ASSERT(fromItem); + + // If the item we're going to is a clone of the item we're at, then do + // not load it again, and continue history traversal to its children. + // The current frame tree and the frame tree snapshot in the item have + // to match. + // Note: If item and fromItem are the same, then we need to create a new + // document. + if (item != fromItem + && item->itemSequenceNumber() == fromItem->itemSequenceNumber() + && currentFramesMatchItem(item) + && fromItem->hasSameFrames(item)) + { + // This content is good, so leave it alone and look for children that need reloading + // Save form state (works from currentItem, since m_frameLoadComplete is true) + ASSERT(m_frameLoadComplete); + saveDocumentState(); + saveScrollPositionAndViewStateToItem(m_currentItem.get()); + + if (FrameView* view = m_frame->view()) + view->setWasScrolledByUser(false); + + m_previousItem = m_currentItem; + m_currentItem = 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 childFrameName = childItems[i]->target(); + HistoryItem* fromChildItem = fromItem->childItemWithTarget(childFrameName); + ASSERT(fromChildItem); + Frame* childFrame = m_frame->tree()->child(childFrameName); + ASSERT(childFrame); + childFrame->loader()->history()->recursiveGoToItem(childItems[i].get(), fromChildItem, type); + } + } else { + m_frame->loader()->loadItem(item, type); + } +} + +// Helper method that determines whether the current frame tree matches given history item's. +bool HistoryController::currentFramesMatchItem(HistoryItem* item) const +{ + if ((!m_frame->tree()->uniqueName().isEmpty() || !item->target().isEmpty()) && m_frame->tree()->uniqueName() != item->target()) + return false; + + 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; + } + + return true; +} + +void HistoryController::updateBackForwardListClippedAtTarget(bool doClip) +{ + // 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 function is called with doClip=true 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. + + Page* page = m_frame->page(); + if (!page) + return; + + if (m_frame->loader()->documentLoader()->urlForHistory().isEmpty()) + return; + + Frame* mainFrame = page->mainFrame(); + ASSERT(mainFrame); + FrameLoader* frameLoader = mainFrame->loader(); + + frameLoader->checkDidPerformFirstNavigation(); + + RefPtr<HistoryItem> topItem = frameLoader->history()->createItemTree(m_frame, doClip); + LOG(BackForward, "WebCoreBackForward - Adding backforward item %p for frame %s", topItem.get(), m_frame->loader()->documentLoader()->url().string().ascii().data()); + page->backForward()->addItem(topItem.release()); +} + +void HistoryController::pushState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString) +{ + if (!m_currentItem) + return; + + Page* page = m_frame->page(); + ASSERT(page); + + // Get a HistoryItem tree for the current frame tree. + RefPtr<HistoryItem> topItem = page->mainFrame()->loader()->history()->createItemTree(m_frame, false); + + // Override data in the current item (created by createItemTree) to reflect + // the pushState() arguments. + m_currentItem->setTitle(title); + m_currentItem->setStateObject(stateObject); + m_currentItem->setURLString(urlString); + + page->backForward()->addItem(topItem.release()); +} + +void HistoryController::replaceState(PassRefPtr<SerializedScriptValue> stateObject, const String& title, const String& urlString) +{ + if (!m_currentItem) + return; + + if (!urlString.isEmpty()) + m_currentItem->setURLString(urlString); + m_currentItem->setTitle(title); + m_currentItem->setStateObject(stateObject); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/HistoryController.h b/Source/WebCore/loader/HistoryController.h new file mode 100644 index 0000000..1bf5072 --- /dev/null +++ b/Source/WebCore/loader/HistoryController.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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. + */ + +#ifndef HistoryController_h +#define HistoryController_h + +#include "FrameLoaderTypes.h" +#include "PlatformString.h" +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Frame; +class HistoryItem; +class SerializedScriptValue; + +class HistoryController : public Noncopyable { +public: + enum HistoryUpdateType { UpdateAll, UpdateAllExceptBackForwardList }; + + HistoryController(Frame*); + ~HistoryController(); + + void saveScrollPositionAndViewStateToItem(HistoryItem*); + void restoreScrollPositionAndViewState(); + + void updateBackForwardListForFragmentScroll(); + + void saveDocumentState(); + void saveDocumentAndScrollState(); + void restoreDocumentState(); + + void invalidateCurrentItemCachedPage(); + + void goToItem(HistoryItem*, FrameLoadType); + + void updateForBackForwardNavigation(); + void updateForReload(); + void updateForStandardLoad(HistoryUpdateType updateType = UpdateAll); + void updateForRedirectWithLockedBackForwardList(); + void updateForClientRedirect(); + void updateForCommit(); + void updateForSameDocumentNavigation(); + void updateForFrameLoadCompleted(); + + HistoryItem* currentItem() const { return m_currentItem.get(); } + void setCurrentItem(HistoryItem*); + void setCurrentItemTitle(const String&); + bool currentItemShouldBeReplaced() const; + + HistoryItem* previousItem() const { return m_previousItem.get(); } + + HistoryItem* provisionalItem() const { return m_provisionalItem.get(); } + void setProvisionalItem(HistoryItem*); + + void pushState(PassRefPtr<SerializedScriptValue>, const String& title, const String& url); + void replaceState(PassRefPtr<SerializedScriptValue>, const String& title, const String& url); + +private: + PassRefPtr<HistoryItem> createItem(bool useOriginal); + PassRefPtr<HistoryItem> createItemTree(Frame* targetFrame, bool clipAtTarget); + + void recursiveGoToItem(HistoryItem*, HistoryItem*, FrameLoadType); + bool currentFramesMatchItem(HistoryItem*) const; + void updateBackForwardListClippedAtTarget(bool doClip); + + Frame* m_frame; + + RefPtr<HistoryItem> m_currentItem; + RefPtr<HistoryItem> m_previousItem; + RefPtr<HistoryItem> m_provisionalItem; + + bool m_frameLoadComplete; +}; + +} // namespace WebCore + +#endif // HistoryController_h diff --git a/Source/WebCore/loader/ImageLoader.cpp b/Source/WebCore/loader/ImageLoader.cpp new file mode 100644 index 0000000..a77e8c0 --- /dev/null +++ b/Source/WebCore/loader/ImageLoader.cpp @@ -0,0 +1,375 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2005, 2006, 2007, 2009, 2010 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 "CachedImage.h" +#include "CachedResourceLoader.h" +#include "Document.h" +#include "Element.h" +#include "HTMLNames.h" +#include "HTMLObjectElement.h" +#include "RenderImage.h" + +#if ENABLE(SVG) +#include "RenderSVGImage.h" +#endif +#if ENABLE(VIDEO) +#include "RenderVideo.h" +#endif + +#if !ASSERT_DISABLED +// ImageLoader objects are allocated as members of other objects, so generic pointer check would always fail. +namespace WTF { + +template<> struct ValueCheck<WebCore::ImageLoader*> { + typedef WebCore::ImageLoader* TraitType; + static void checkConsistency(const WebCore::ImageLoader* p) + { + if (!p) + return; + ASSERT(p->element()); + ValueCheck<WebCore::Element*>::checkConsistency(p->element()); + } +}; + +} +#endif + +namespace WebCore { + +class ImageEventSender : public Noncopyable { +public: + ImageEventSender(const AtomicString& eventType); + + void dispatchEventSoon(ImageLoader*); + void cancelEvent(ImageLoader*); + + void dispatchPendingEvents(); + +#if !ASSERT_DISABLED + bool hasPendingEvents(ImageLoader* loader) { return m_dispatchSoonList.find(loader) != notFound; } +#endif + +private: + void timerFired(Timer<ImageEventSender>*); + + AtomicString m_eventType; + Timer<ImageEventSender> m_timer; + Vector<ImageLoader*> m_dispatchSoonList; + Vector<ImageLoader*> m_dispatchingList; +}; + +static ImageEventSender& beforeLoadEventSender() +{ + DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().beforeloadEvent)); + return sender; +} + +static ImageEventSender& loadEventSender() +{ + DEFINE_STATIC_LOCAL(ImageEventSender, sender, (eventNames().loadEvent)); + return sender; +} + +ImageLoader::ImageLoader(Element* element) + : m_element(element) + , m_image(0) + , m_firedBeforeLoad(true) + , m_firedLoad(true) + , m_imageComplete(true) + , m_loadManually(false) +{ +} + +ImageLoader::~ImageLoader() +{ + if (m_image) + m_image->removeClient(this); + + ASSERT(!m_firedBeforeLoad || !beforeLoadEventSender().hasPendingEvents(this)); + if (!m_firedBeforeLoad) + beforeLoadEventSender().cancelEvent(this); + + ASSERT(!m_firedLoad || !loadEventSender().hasPendingEvents(this)); + if (!m_firedLoad) + loadEventSender().cancelEvent(this); +} + +void ImageLoader::setImage(CachedImage* newImage) +{ + ASSERT(m_failedLoadURL.isEmpty()); + CachedImage* oldImage = m_image.get(); + if (newImage != oldImage) { + m_image = newImage; + if (!m_firedBeforeLoad) { + beforeLoadEventSender().cancelEvent(this); + m_firedBeforeLoad = true; + } + if (!m_firedLoad) { + loadEventSender().cancelEvent(this); + m_firedLoad = true; + } + m_imageComplete = true; + if (newImage) + newImage->addClient(this); + if (oldImage) + oldImage->removeClient(this); + } + + if (RenderImageResource* imageResource = renderImageResource()) + imageResource->resetAnimation(); +} + +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. + Document* document = m_element->document(); + if (!document->renderer()) + return; + + AtomicString attr = m_element->getAttribute(m_element->imageSourceAttributeName()); + + if (attr == m_failedLoadURL) + return; + + // 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() && document->baseURI().isLocalFile()))) { + if (m_loadManually) { + bool autoLoadOtherImages = document->cachedResourceLoader()->autoLoadImages(); + document->cachedResourceLoader()->setAutoLoadImages(false); + newImage = new CachedImage(sourceURI(attr)); + newImage->setLoading(true); + newImage->setOwningCachedResourceLoader(document->cachedResourceLoader()); + document->cachedResourceLoader()->m_documentResources.set(newImage->url(), newImage); + document->cachedResourceLoader()->setAutoLoadImages(autoLoadOtherImages); + } else + newImage = document->cachedResourceLoader()->requestImage(sourceURI(attr)); + + // If we do not have an image here, it means that a cross-site + // violation occurred. + m_failedLoadURL = !newImage ? attr : AtomicString(); + } + + CachedImage* oldImage = m_image.get(); + if (newImage != oldImage) { + if (!m_firedBeforeLoad) + beforeLoadEventSender().cancelEvent(this); + if (!m_firedLoad) + loadEventSender().cancelEvent(this); + + m_image = newImage; + m_firedBeforeLoad = !newImage; + m_firedLoad = !newImage; + m_imageComplete = !newImage; + + if (newImage) { + newImage->addClient(this); + if (!m_element->document()->hasListenerType(Document::BEFORELOAD_LISTENER)) + dispatchPendingBeforeLoadEvent(); + else + beforeLoadEventSender().dispatchEventSoon(this); + } + if (oldImage) + oldImage->removeClient(this); + } + + if (RenderImageResource* imageResource = renderImageResource()) + imageResource->resetAnimation(); +} + +void ImageLoader::updateFromElementIgnoringPreviousError() +{ + // Clear previous error. + m_failedLoadURL = AtomicString(); + updateFromElement(); +} + +void ImageLoader::notifyFinished(CachedResource*) +{ + ASSERT(m_failedLoadURL.isEmpty()); + + m_imageComplete = true; + if (haveFiredBeforeLoadEvent()) + updateRenderer(); + + if (m_firedLoad) + return; + + loadEventSender().dispatchEventSoon(this); +} + +RenderImageResource* ImageLoader::renderImageResource() +{ + RenderObject* renderer = m_element->renderer(); + + if (!renderer) + return 0; + + if (renderer->isImage()) + return toRenderImage(renderer)->imageResource(); + +#if ENABLE(SVG) + if (renderer->isSVGImage()) + return toRenderSVGImage(renderer)->imageResource(); +#endif + +#if ENABLE(VIDEO) + if (renderer->isVideo()) + return toRenderVideo(renderer)->imageResource(); +#endif + + return 0; +} + +void ImageLoader::updateRenderer() +{ + RenderImageResource* imageResource = renderImageResource(); + + if (!imageResource) + return; + + // Only update the renderer if it doesn't have an image or if what we have + // is a complete image. This prevents flickering in the case where a dynamic + // change is happening between two images. + CachedImage* cachedImage = imageResource->cachedImage(); + if (m_image != cachedImage && (m_imageComplete || !cachedImage)) + imageResource->setCachedImage(m_image.get()); +} + +void ImageLoader::dispatchPendingBeforeLoadEvent() +{ + if (m_firedBeforeLoad) + return; + if (!m_image) + return; + if (!m_element->document()->attached()) + return; + m_firedBeforeLoad = true; + if (m_element->dispatchBeforeLoadEvent(m_image->url())) { + updateRenderer(); + return; + } + if (m_image) { + m_image->removeClient(this); + m_image = 0; + } + loadEventSender().cancelEvent(this); + + if (m_element->hasTagName(HTMLNames::objectTag)) + static_cast<HTMLObjectElement*>(m_element)->renderFallbackContent(); +} + +void ImageLoader::dispatchPendingLoadEvent() +{ + if (m_firedLoad) + return; + if (!m_image) + return; + if (!m_element->document()->attached()) + return; + m_firedLoad = true; + dispatchLoadEvent(); +} + +void ImageLoader::dispatchPendingBeforeLoadEvents() +{ + beforeLoadEventSender().dispatchPendingEvents(); +} + +void ImageLoader::dispatchPendingLoadEvents() +{ + loadEventSender().dispatchPendingEvents(); +} + +void ImageLoader::elementWillMoveToNewOwnerDocument() +{ + setImage(0); +} + +ImageEventSender::ImageEventSender(const AtomicString& eventType) + : m_eventType(eventType) + , m_timer(this, &ImageEventSender::timerFired) +{ +} + +void ImageEventSender::dispatchEventSoon(ImageLoader* loader) +{ + m_dispatchSoonList.append(loader); + if (!m_timer.isActive()) + m_timer.startOneShot(0); +} + +void ImageEventSender::cancelEvent(ImageLoader* loader) +{ + // Remove instances of this loader from both lists. + // Use loops because we allow multiple instances to get into the lists. + size_t size = m_dispatchSoonList.size(); + for (size_t i = 0; i < size; ++i) { + if (m_dispatchSoonList[i] == loader) + m_dispatchSoonList[i] = 0; + } + size = m_dispatchingList.size(); + for (size_t i = 0; i < size; ++i) { + if (m_dispatchingList[i] == loader) + m_dispatchingList[i] = 0; + } + if (m_dispatchSoonList.isEmpty()) + m_timer.stop(); +} + +void ImageEventSender::dispatchPendingEvents() +{ + // Need to avoid re-entering this function; if new dispatches are + // scheduled before the parent finishes processing the list, they + // will set a timer and eventually be processed. + if (!m_dispatchingList.isEmpty()) + return; + + m_timer.stop(); + + m_dispatchSoonList.checkConsistency(); + + m_dispatchingList.swap(m_dispatchSoonList); + size_t size = m_dispatchingList.size(); + for (size_t i = 0; i < size; ++i) { + if (ImageLoader* loader = m_dispatchingList[i]) { + if (m_eventType == eventNames().beforeloadEvent) + loader->dispatchPendingBeforeLoadEvent(); + else + loader->dispatchPendingLoadEvent(); + } + } + m_dispatchingList.clear(); +} + +void ImageEventSender::timerFired(Timer<ImageEventSender>*) +{ + dispatchPendingEvents(); +} + +} diff --git a/Source/WebCore/loader/ImageLoader.h b/Source/WebCore/loader/ImageLoader.h new file mode 100644 index 0000000..9bf7624 --- /dev/null +++ b/Source/WebCore/loader/ImageLoader.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2004, 2009 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 ImageLoader_h +#define ImageLoader_h + +#include "CachedResourceClient.h" +#include "CachedResourceHandle.h" +#include <wtf/text/AtomicString.h> + +namespace WebCore { + +class Element; +class ImageLoadEventSender; +class RenderImageResource; + +class ImageLoader : public CachedResourceClient { +public: + ImageLoader(Element*); + virtual ~ImageLoader(); + + // This function should be called when the element is attached to a document; starts + // loading if a load hasn't already been started. + void updateFromElement(); + + // This function should be called whenever the 'src' attribute is set, even if its value + // doesn't change; starts new load unconditionally (matches Firefox and Opera behavior). + void updateFromElementIgnoringPreviousError(); + + void elementWillMoveToNewOwnerDocument(); + + Element* element() const { return m_element; } + bool imageComplete() const { return m_imageComplete; } + + CachedImage* image() const { return m_image.get(); } + void setImage(CachedImage*); // Cancels pending beforeload and load events, and doesn't dispatch new ones. + + void setLoadManually(bool loadManually) { m_loadManually = loadManually; } + + bool haveFiredBeforeLoadEvent() const { return m_firedBeforeLoad; } + bool haveFiredLoadEvent() const { return m_firedLoad; } + + static void dispatchPendingBeforeLoadEvents(); + static void dispatchPendingLoadEvents(); + +protected: + virtual void notifyFinished(CachedResource*); + +private: + virtual void dispatchLoadEvent() = 0; + virtual String sourceURI(const AtomicString&) const = 0; + + friend class ImageEventSender; + void dispatchPendingBeforeLoadEvent(); + void dispatchPendingLoadEvent(); + + RenderImageResource* renderImageResource(); + void updateRenderer(); + + Element* m_element; + CachedResourceHandle<CachedImage> m_image; + AtomicString m_failedLoadURL; + bool m_firedBeforeLoad : 1; + bool m_firedLoad : 1; + bool m_imageComplete : 1; + bool m_loadManually : 1; +}; + +} + +#endif diff --git a/Source/WebCore/loader/MainResourceLoader.cpp b/Source/WebCore/loader/MainResourceLoader.cpp new file mode 100644 index 0000000..e6abefd --- /dev/null +++ b/Source/WebCore/loader/MainResourceLoader.cpp @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "MainResourceLoader.h" + +#include "ApplicationCacheHost.h" +#include "DocumentLoadTiming.h" +#include "DocumentLoader.h" +#include "FormState.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "HTMLFormElement.h" +#include "Page.h" +#if PLATFORM(QT) +#include "PluginDatabase.h" +#endif +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceLoadScheduler.h" +#include "SchemeRegistry.h" +#include "Settings.h" +#include <wtf/CurrentTime.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()->notifier()->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()->policyChecker()->cancelCheck(); + 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(); + else if (m_substituteData.isValid()) { + // A redirect resulted in loading substitute data. + ASSERT(documentLoader()->timing()->redirectCount); + handle()->cancel(); + handleDataLoadSoon(request); + } + + 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); + documentLoader()->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); + + ASSERT(documentLoader()->timing()->fetchStart); + if (!redirectResponse.isNull()) { + DocumentLoadTiming* documentLoadTiming = documentLoader()->timing(); + + // Check if the redirected url is allowed to access the redirecting url's timing information. + RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::create(newRequest.url()); + if (!securityOrigin->canRequest(redirectResponse.url())) + documentLoadTiming->hasCrossOriginRedirect = true; + + documentLoadTiming->redirectCount++; + if (!documentLoadTiming->redirectStart) + documentLoadTiming->redirectStart = documentLoadTiming->fetchStart; + documentLoadTiming->redirectEnd = currentTime(); + documentLoadTiming->fetchStart = documentLoadTiming->redirectEnd; + } + + // 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.setFirstPartyForCookies(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); + + Frame* top = m_frame->tree()->top(); + if (top != m_frame) + frameLoader()->checkIfDisplayInsecureContent(top->document()->securityOrigin(), newRequest.url()); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (!redirectResponse.isNull()) { + // We checked application cache for initial URL, now we need to check it for redirected one. + ASSERT(!m_substituteData.isValid()); + documentLoader()->applicationCacheHost()->maybeLoadMainResourceForRedirect(newRequest, m_substituteData); + } +#endif + + // 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. + if (!redirectResponse.isNull()) { + ref(); // balanced by deref in continueAfterNavigationPolicy + frameLoader()->policyChecker()->checkNavigationPolicy(newRequest, callContinueAfterNavigationPolicy, this); + } +} + +static bool shouldLoadAsEmptyDocument(const KURL& url) +{ +#if PLATFORM(TORCHMOBILE) + return url.isEmpty() || (url.protocolIs("about") && equalIgnoringRef(url, blankURL())); +#else + return url.isEmpty() || SchemeRegistry::shouldLoadURLSchemeAsEmptyDocument(url.protocol()); +#endif +} + +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()->policyChecker()->cannotShowMIMEType(r); + // Check reachedTerminalState since the load may have already been cancelled inside of _handleUnimplementablePolicyWithErrorCode::. + if (!reachedTerminalState()) + stopLoadingForPolicyChange(); + return; + } + break; + } + + case PolicyDownload: + // m_handle can be null, e.g. when loading a substitute resource from application cache. + if (!m_handle) { + receivedError(cannotShowURLError()); + return; + } + frameLoader()->client()->download(m_handle.get(), request(), m_handle.get()->firstRequest(), 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(0); + } else if (shouldLoadAsEmptyDocument(url) || frameLoader()->representationExistsForURLScheme(url.protocol())) + didFinishLoading(0); + } +} + +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 +} + +#if PLATFORM(QT) +void MainResourceLoader::substituteMIMETypeFromPluginDatabase(const ResourceResponse& r) +{ + if (!m_frame->loader()->subframeLoader()->allowPlugins(NotAboutToInstantiatePlugin)) + return; + + String filename = r.url().lastPathComponent(); + if (filename.endsWith("/")) + return; + + size_t extensionPos = filename.reverseFind('.'); + if (extensionPos == notFound) + return; + + String extension = filename.substring(extensionPos + 1); + String mimeType = PluginDatabase::installedPlugins()->MIMETypeForExtension(extension); + if (!mimeType.isEmpty()) { + ResourceResponse* response = const_cast<ResourceResponse*>(&r); + response->setMimeType(mimeType); + } +} +#endif + +void MainResourceLoader::didReceiveResponse(const ResourceResponse& r) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForMainResponse(request(), r)) + return; +#endif + + HTTPHeaderMap::const_iterator it = r.httpHeaderFields().find(AtomicString("x-frame-options")); + if (it != r.httpHeaderFields().end()) { + String content = it->second; + if (m_frame->loader()->shouldInterruptLoadForXFrameOptions(content, r.url())) { + cancel(); + return; + } + } + + // 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 PLATFORM(QT) + if (r.mimeType() == "application/octet-stream") + substituteMIMETypeFromPluginDatabase(r); +#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 + + ASSERT(frameLoader()->activeDocumentLoader()); + + // Always show content with valid substitute data. + if (frameLoader()->activeDocumentLoader()->substituteData().isValid()) { + callContinueAfterContentPolicy(this, 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() && m_response.mimeType() == "application/x-ftp-directory") { + callContinueAfterContentPolicy(this, PolicyUse); + return; + } +#endif + + frameLoader()->policyChecker()->checkContentPolicy(m_response.mimeType(), callContinueAfterContentPolicy, this); +} + +void MainResourceLoader::didReceiveData(const char* data, int length, long long lengthReceived, bool allAtOnce) +{ + ASSERT(data); + ASSERT(length != 0); + + ASSERT(!m_response.isNull()); + +#if USE(CFNETWORK) || (PLATFORM(MAC) && !defined(BUILDING_ON_TIGER)) + // Workaround for <rdar://problem/6060782> + if (m_response.isNull()) { + m_response = ResourceResponse(KURL(), "text/html", 0, String(), String()); + if (DocumentLoader* documentLoader = frameLoader()->activeDocumentLoader()) + documentLoader->setResponse(m_response); + } +#endif + + // 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 + + #if ENABLE(OFFLINE_WEB_APPLICATIONS) + documentLoader()->applicationCacheHost()->mainResourceDataReceived(data, length, lengthReceived, allAtOnce); +#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); + + m_timeOfLastDataReceived = currentTime(); + + ResourceLoader::didReceiveData(data, length, lengthReceived, allAtOnce); +} + +void MainResourceLoader::didFinishLoading(double finishTime) +{ + // 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 + + ASSERT(!documentLoader()->timing()->responseEnd); + documentLoader()->timing()->responseEnd = finishTime ? finishTime : m_timeOfLastDataReceived; + frameLoader()->finishedLoading(); + ResourceLoader::didFinishLoading(finishTime); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + dl->applicationCacheHost()->finishedLoadingMainResource(); +#endif +} + +void MainResourceLoader::didFail(const ResourceError& error) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForMainError(request(), error)) + return; +#endif + + // 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(MainResourceLoaderTimer*) +{ + RefPtr<MainResourceLoader> protect(this); + + KURL url = m_substituteData.responseURL(); + if (url.isEmpty()) + url = m_initialRequest.url(); + + // Clear the initial request here so that subsequent entries into the + // loader will not think there's still a deferred load left to do. + m_initialRequest = ResourceRequest(); + + ResourceResponse response(url, m_substituteData.mimeType(), m_substituteData.content()->size(), m_substituteData.textEncoding(), ""); + didReceiveResponse(response); +} + +void MainResourceLoader::startDataLoadTimer() +{ + m_dataLoadTimer.startOneShot(0); + +#if HAVE(RUNLOOP_TIMER) + if (SchedulePairHashSet* scheduledPairs = m_frame->page()->scheduledRunLoopPairs()) + m_dataLoadTimer.schedule(*scheduledPairs); +#endif +} + +void MainResourceLoader::handleDataLoadSoon(const ResourceRequest& r) +{ + m_initialRequest = r; + + if (m_documentLoader->deferMainResourceDataLoad()) + startDataLoadTimer(); + 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; + + resourceLoadScheduler()->addMainResourceLoad(this); + if (m_substituteData.isValid()) + handleDataLoadSoon(r); + else if (shouldLoadEmpty || frameLoader()->representationExistsForURLScheme(url.protocol())) + handleEmptyLoad(url, !shouldLoadEmpty); + else + m_handle = ResourceHandle::create(m_frame->loader()->networkingContext(), r, this, false, true); + + return false; +} + +bool MainResourceLoader::load(const ResourceRequest& r, const SubstituteData& substituteData) +{ + ASSERT(!m_handle); + + m_substituteData = substituteData; + + ASSERT(documentLoader()->timing()->navigationStart); + ASSERT(!documentLoader()->timing()->fetchStart); + documentLoader()->timing()->fetchStart = currentTime(); + ResourceRequest request(r); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + documentLoader()->applicationCacheHost()->maybeLoadMainResource(request, m_substituteData); +#endif + + bool defer = defersLoading(); + if (defer) { + bool shouldLoadEmpty = shouldLoadAsEmptyDocument(request.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()) + startDataLoadTimer(); + else { + ResourceRequest r(m_initialRequest); + m_initialRequest = ResourceRequest(); + loadNow(r); + } + } +} + +} diff --git a/Source/WebCore/loader/MainResourceLoader.h b/Source/WebCore/loader/MainResourceLoader.h new file mode 100644 index 0000000..1620f7a --- /dev/null +++ b/Source/WebCore/loader/MainResourceLoader.h @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#ifndef MainResourceLoader_h +#define MainResourceLoader_h + +#include "FrameLoaderTypes.h" +#include "ResourceLoader.h" +#include "SubstituteData.h" +#include <wtf/Forward.h> + +#if HAVE(RUNLOOP_TIMER) +#include "RunLoopTimer.h" +#else +#include "Timer.h" +#endif + +namespace WebCore { + + 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(double finishTime); + virtual void didFail(const ResourceError&); + +#if HAVE(RUNLOOP_TIMER) + typedef RunLoopTimer<MainResourceLoader> MainResourceLoaderTimer; +#else + typedef Timer<MainResourceLoader> MainResourceLoaderTimer; +#endif + + void handleDataLoadNow(MainResourceLoaderTimer*); + + bool isLoadingMultipartContent() const { return m_loadingMultipartContent; } + + private: + MainResourceLoader(Frame*); + + virtual void didCancel(const ResourceError&); + + bool loadNow(ResourceRequest&); + + void handleEmptyLoad(const KURL&, bool forURLScheme); + void handleDataLoadSoon(const ResourceRequest& r); + + void startDataLoadTimer(); + 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&); + +#if PLATFORM(QT) + void substituteMIMETypeFromPluginDatabase(const ResourceResponse&); +#endif + + ResourceRequest m_initialRequest; + SubstituteData m_substituteData; + + MainResourceLoaderTimer m_dataLoadTimer; + + bool m_loadingMultipartContent; + bool m_waitingForContentPolicy; + double m_timeOfLastDataReceived; + }; + +} + +#endif diff --git a/Source/WebCore/loader/NavigationAction.cpp b/Source/WebCore/loader/NavigationAction.cpp new file mode 100644 index 0000000..ed68e43 --- /dev/null +++ b/Source/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 || frameLoadType == FrameLoadTypeReloadFromOrigin) + 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/Source/WebCore/loader/NavigationAction.h b/Source/WebCore/loader/NavigationAction.h new file mode 100644 index 0000000..0477c6e --- /dev/null +++ b/Source/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/Source/WebCore/loader/NavigationScheduler.cpp b/Source/WebCore/loader/NavigationScheduler.cpp new file mode 100644 index 0000000..175219c --- /dev/null +++ b/Source/WebCore/loader/NavigationScheduler.cpp @@ -0,0 +1,442 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) 2009 Adam Barth. 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 "NavigationScheduler.h" + +#include "BackForwardController.h" +#include "DOMWindow.h" +#include "DocumentLoader.h" +#include "Event.h" +#include "FormState.h" +#include "FormSubmission.h" +#include "Frame.h" +#include "FrameLoadRequest.h" +#include "FrameLoader.h" +#include "FrameLoaderStateMachine.h" +#include "HTMLFormElement.h" +#include "HTMLFrameOwnerElement.h" +#include "HistoryItem.h" +#include "Page.h" +#include "UserGestureIndicator.h" +#include <wtf/CurrentTime.h> + +namespace WebCore { + +unsigned NavigationDisablerForBeforeUnload::s_navigationDisableCount = 0; + +class ScheduledNavigation : public Noncopyable { +public: + ScheduledNavigation(double delay, bool lockHistory, bool lockBackForwardList, bool wasDuringLoad, bool isLocationChange) + : m_delay(delay) + , m_lockHistory(lockHistory) + , m_lockBackForwardList(lockBackForwardList) + , m_wasDuringLoad(wasDuringLoad) + , m_isLocationChange(isLocationChange) + , m_wasUserGesture(ScriptController::processingUserGesture()) + { + } + virtual ~ScheduledNavigation() { } + + virtual void fire(Frame*) = 0; + + virtual bool shouldStartTimer(Frame*) { return true; } + virtual void didStartTimer(Frame*, Timer<NavigationScheduler>*) { } + virtual void didStopTimer(Frame*, bool /* newLoadInProgress */) { } + + double delay() const { return m_delay; } + bool lockHistory() const { return m_lockHistory; } + bool lockBackForwardList() const { return m_lockBackForwardList; } + bool wasDuringLoad() const { return m_wasDuringLoad; } + bool isLocationChange() const { return m_isLocationChange; } + bool wasUserGesture() const { return m_wasUserGesture; } + +protected: + void clearUserGesture() { m_wasUserGesture = false; } + +private: + double m_delay; + bool m_lockHistory; + bool m_lockBackForwardList; + bool m_wasDuringLoad; + bool m_isLocationChange; + bool m_wasUserGesture; +}; + +class ScheduledURLNavigation : public ScheduledNavigation { +protected: + ScheduledURLNavigation(double delay, PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool duringLoad, bool isLocationChange) + : ScheduledNavigation(delay, lockHistory, lockBackForwardList, duringLoad, isLocationChange) + , m_securityOrigin(securityOrigin) + , m_url(url) + , m_referrer(referrer) + , m_haveToldClient(false) + { + } + + virtual void fire(Frame* frame) + { + UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); + frame->loader()->changeLocation(m_securityOrigin, KURL(ParsedURLString, m_url), m_referrer, lockHistory(), lockBackForwardList(), false); + } + + virtual void didStartTimer(Frame* frame, Timer<NavigationScheduler>* timer) + { + if (m_haveToldClient) + return; + m_haveToldClient = true; + frame->loader()->clientRedirected(KURL(ParsedURLString, m_url), delay(), currentTime() + timer->nextFireInterval(), lockBackForwardList()); + } + + virtual void didStopTimer(Frame* frame, bool newLoadInProgress) + { + if (!m_haveToldClient) + return; + frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress); + } + + SecurityOrigin* securityOrigin() const { return m_securityOrigin.get(); } + String url() const { return m_url; } + String referrer() const { return m_referrer; } + +private: + RefPtr<SecurityOrigin> m_securityOrigin; + String m_url; + String m_referrer; + bool m_haveToldClient; +}; + +class ScheduledRedirect : public ScheduledURLNavigation { +public: + ScheduledRedirect(double delay, PassRefPtr<SecurityOrigin> securityOrigin, const String& url, bool lockHistory, bool lockBackForwardList) + : ScheduledURLNavigation(delay, securityOrigin, url, String(), lockHistory, lockBackForwardList, false, false) + { + clearUserGesture(); + } + + virtual bool shouldStartTimer(Frame* frame) { return frame->loader()->allAncestorsAreComplete(); } +}; + +class ScheduledLocationChange : public ScheduledURLNavigation { +public: + ScheduledLocationChange(PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList, bool duringLoad) + : ScheduledURLNavigation(0.0, securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad, true) { } +}; + +class ScheduledRefresh : public ScheduledURLNavigation { +public: + ScheduledRefresh(PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer) + : ScheduledURLNavigation(0.0, securityOrigin, url, referrer, true, true, false, true) + { + } + + virtual void fire(Frame* frame) + { + UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); + frame->loader()->changeLocation(securityOrigin(), KURL(ParsedURLString, url()), referrer(), lockHistory(), lockBackForwardList(), true); + } +}; + +class ScheduledHistoryNavigation : public ScheduledNavigation { +public: + explicit ScheduledHistoryNavigation(int historySteps) + : ScheduledNavigation(0, false, false, false, true) + , m_historySteps(historySteps) + { + } + + virtual void fire(Frame* frame) + { + UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); + + FrameLoader* loader = frame->loader(); + if (!m_historySteps) { + // Special case for go(0) from a frame -> reload only the frame + // To follow Firefox and IE's behavior, history reload can only navigate the self frame. + loader->urlSelected(loader->url(), "_self", 0, lockHistory(), lockBackForwardList(), SendReferrer); + 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. + frame->page()->backForward()->goBackOrForward(m_historySteps); + } + +private: + int m_historySteps; +}; + +class ScheduledFormSubmission : public ScheduledNavigation { +public: + ScheduledFormSubmission(PassRefPtr<FormSubmission> submission, bool lockBackForwardList, bool duringLoad) + : ScheduledNavigation(0, submission->lockHistory(), lockBackForwardList, duringLoad, true) + , m_submission(submission) + , m_haveToldClient(false) + { + ASSERT(m_submission->state()); + } + + virtual void fire(Frame* frame) + { + UserGestureIndicator gestureIndicator(wasUserGesture() ? DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); + + // The submitForm function will find a target frame before using the redirection timer. + // Now that the timer has fired, we need to repeat the security check which normally is done when + // selecting a target, in case conditions have changed. Other code paths avoid this by targeting + // without leaving a time window. If we fail the check just silently drop the form submission. + Frame* requestingFrame = m_submission->state()->sourceFrame(); + if (!requestingFrame->loader()->shouldAllowNavigation(frame)) + return; + FrameLoadRequest frameRequest(requestingFrame->document()->securityOrigin()); + m_submission->populateFrameLoadRequest(frameRequest); + frame->loader()->loadFrameRequest(frameRequest, lockHistory(), lockBackForwardList(), m_submission->event(), m_submission->state(), SendReferrer); + } + + virtual void didStartTimer(Frame* frame, Timer<NavigationScheduler>* timer) + { + if (m_haveToldClient) + return; + m_haveToldClient = true; + frame->loader()->clientRedirected(m_submission->requestURL(), delay(), currentTime() + timer->nextFireInterval(), lockBackForwardList()); + } + + virtual void didStopTimer(Frame* frame, bool newLoadInProgress) + { + if (!m_haveToldClient) + return; + frame->loader()->clientRedirectCancelledOrFinished(newLoadInProgress); + } + +private: + RefPtr<FormSubmission> m_submission; + bool m_haveToldClient; +}; + +NavigationScheduler::NavigationScheduler(Frame* frame) + : m_frame(frame) + , m_timer(this, &NavigationScheduler::timerFired) +{ +} + +NavigationScheduler::~NavigationScheduler() +{ +} + +bool NavigationScheduler::redirectScheduledDuringLoad() +{ + return m_redirect && m_redirect->wasDuringLoad(); +} + +bool NavigationScheduler::locationChangePending() +{ + return m_redirect && m_redirect->isLocationChange(); +} + +void NavigationScheduler::clear() +{ + m_timer.stop(); + m_redirect.clear(); +} + +inline bool NavigationScheduler::shouldScheduleNavigation() const +{ + return m_frame->page(); +} + +inline bool NavigationScheduler::shouldScheduleNavigation(const String& url) const +{ + return shouldScheduleNavigation() && (protocolIsJavaScript(url) || NavigationDisablerForBeforeUnload::isNavigationAllowed()); +} + +void NavigationScheduler::scheduleRedirect(double delay, const String& url) +{ + if (!shouldScheduleNavigation(url)) + return; + if (delay < 0 || delay > INT_MAX / 1000) + return; + if (url.isEmpty()) + return; + + // We want a new back/forward list item if the refresh timeout is > 1 second. + if (!m_redirect || delay <= m_redirect->delay()) + schedule(adoptPtr(new ScheduledRedirect(delay, m_frame->document()->securityOrigin(), url, true, delay <= 1))); +} + +bool NavigationScheduler::mustLockBackForwardList(Frame* targetFrame) +{ + // Non-user navigation before the page has finished firing onload should not create a new back/forward item. + // See https://webkit.org/b/42861 for the original motivation for this. + if (!ScriptController::processingUserGesture() && targetFrame->loader()->documentLoader() && !targetFrame->loader()->documentLoader()->wasOnloadHandled()) + return true; + + // Navigation of a subframe during loading of an ancestor frame does not create a new back/forward item. + // The definition of "during load" is any time before all handlers for the load event have been run. + // See https://bugs.webkit.org/show_bug.cgi?id=14957 for the original motivation for this. + for (Frame* ancestor = targetFrame->tree()->parent(); ancestor; ancestor = ancestor->tree()->parent()) { + Document* document = ancestor->document(); + if (!ancestor->loader()->isComplete() || (document && document->processingLoadEvent())) + return true; + } + return false; +} + +void NavigationScheduler::scheduleLocationChange(PassRefPtr<SecurityOrigin> securityOrigin, const String& url, const String& referrer, bool lockHistory, bool lockBackForwardList) +{ + if (!shouldScheduleNavigation(url)) + return; + if (url.isEmpty()) + return; + + lockBackForwardList = lockBackForwardList || mustLockBackForwardList(m_frame); + + FrameLoader* loader = m_frame->loader(); + + // 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(ParsedURLString, url); + if (parsedURL.hasFragmentIdentifier() && equalIgnoringFragmentIdentifier(loader->url(), parsedURL)) { + loader->changeLocation(securityOrigin, loader->completeURL(url), referrer, lockHistory, lockBackForwardList); + 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 = !loader->stateMachine()->committedFirstRealDocumentLoad(); + + schedule(adoptPtr(new ScheduledLocationChange(securityOrigin, url, referrer, lockHistory, lockBackForwardList, duringLoad))); +} + +void NavigationScheduler::scheduleFormSubmission(PassRefPtr<FormSubmission> submission) +{ + ASSERT(m_frame->page()); + + // FIXME: Do we need special handling for form submissions where the URL is the same + // as the current one except for the fragment part? See scheduleLocationChange above. + + // 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_frame->loader()->stateMachine()->committedFirstRealDocumentLoad(); + + // If this is a child frame and the form submission was triggered by a script, lock the back/forward list + // to match IE and Opera. + // See https://bugs.webkit.org/show_bug.cgi?id=32383 for the original motivation for this. + bool lockBackForwardList = mustLockBackForwardList(m_frame) + || (submission->state()->formSubmissionTrigger() == SubmittedByJavaScript + && m_frame->tree()->parent() && !ScriptController::processingUserGesture()); + + schedule(adoptPtr(new ScheduledFormSubmission(submission, lockBackForwardList, duringLoad))); +} + +void NavigationScheduler::scheduleRefresh() +{ + if (!shouldScheduleNavigation()) + return; + const KURL& url = m_frame->loader()->url(); + if (url.isEmpty()) + return; + + schedule(adoptPtr(new ScheduledRefresh(m_frame->document()->securityOrigin(), url.string(), m_frame->loader()->outgoingReferrer()))); +} + +void NavigationScheduler::scheduleHistoryNavigation(int steps) +{ + if (!shouldScheduleNavigation()) + return; + + // Invalid history navigations (such as history.forward() during a new load) have the side effect of cancelling any scheduled + // redirects. We also avoid the possibility of cancelling the current load by avoiding the scheduled redirection altogether. + BackForwardController* backForward = m_frame->page()->backForward(); + if (steps > backForward->forwardCount() || -steps > backForward->backCount()) { + cancel(); + return; + } + + // In all other cases, schedule the history traversal to occur asynchronously. + schedule(adoptPtr(new ScheduledHistoryNavigation(steps))); +} + +void NavigationScheduler::timerFired(Timer<NavigationScheduler>*) +{ + if (!m_frame->page()) + return; + if (m_frame->page()->defersLoading()) + return; + + OwnPtr<ScheduledNavigation> redirect(m_redirect.release()); + redirect->fire(m_frame); +} + +void NavigationScheduler::schedule(PassOwnPtr<ScheduledNavigation> redirect) +{ + ASSERT(m_frame->page()); + + // 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 (redirect->wasDuringLoad()) { + if (DocumentLoader* provisionalDocumentLoader = m_frame->loader()->provisionalDocumentLoader()) + provisionalDocumentLoader->stopLoading(); + m_frame->loader()->stopLoading(UnloadEventPolicyUnloadAndPageHide); + } + + cancel(); + m_redirect = redirect; + + if (!m_frame->loader()->isComplete() && m_redirect->isLocationChange()) + m_frame->loader()->completed(); + + startTimer(); +} + +void NavigationScheduler::startTimer() +{ + if (!m_redirect) + return; + + ASSERT(m_frame->page()); + if (m_timer.isActive()) + return; + if (!m_redirect->shouldStartTimer(m_frame)) + return; + + m_timer.startOneShot(m_redirect->delay()); + m_redirect->didStartTimer(m_frame, &m_timer); +} + +void NavigationScheduler::cancel(bool newLoadInProgress) +{ + m_timer.stop(); + + OwnPtr<ScheduledNavigation> redirect(m_redirect.release()); + if (redirect) + redirect->didStopTimer(m_frame, newLoadInProgress); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/NavigationScheduler.h b/Source/WebCore/loader/NavigationScheduler.h new file mode 100644 index 0000000..2014ff2 --- /dev/null +++ b/Source/WebCore/loader/NavigationScheduler.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) 2009 Adam Barth. 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 NavigationScheduler_h +#define NavigationScheduler_h + +#include "Event.h" +#include "Timer.h" +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class FormState; +class FormSubmission; +class Frame; +class ScheduledNavigation; +class SecurityOrigin; + +struct FrameLoadRequest; + +class NavigationDisablerForBeforeUnload { + WTF_MAKE_NONCOPYABLE(NavigationDisablerForBeforeUnload); + +public: + NavigationDisablerForBeforeUnload() + { + s_navigationDisableCount++; + } + ~NavigationDisablerForBeforeUnload() + { + ASSERT(s_navigationDisableCount); + s_navigationDisableCount--; + } + static bool isNavigationAllowed() { return !s_navigationDisableCount; } + +private: + static unsigned s_navigationDisableCount; +}; + +class NavigationScheduler { + WTF_MAKE_NONCOPYABLE(NavigationScheduler); + +public: + NavigationScheduler(Frame*); + ~NavigationScheduler(); + + bool redirectScheduledDuringLoad(); + bool locationChangePending(); + + void scheduleRedirect(double delay, const String& url); + void scheduleLocationChange(PassRefPtr<SecurityOrigin>, const String& url, const String& referrer, bool lockHistory = true, bool lockBackForwardList = true); + void scheduleFormSubmission(PassRefPtr<FormSubmission>); + void scheduleRefresh(); + void scheduleHistoryNavigation(int steps); + + void startTimer(); + + void cancel(bool newLoadInProgress = false); + void clear(); + +private: + bool shouldScheduleNavigation() const; + bool shouldScheduleNavigation(const String& url) const; + + void timerFired(Timer<NavigationScheduler>*); + void schedule(PassOwnPtr<ScheduledNavigation>); + + static bool mustLockBackForwardList(Frame* targetFrame); + + Frame* m_frame; + Timer<NavigationScheduler> m_timer; + OwnPtr<ScheduledNavigation> m_redirect; +}; + +} // namespace WebCore + +#endif // NavigationScheduler_h diff --git a/Source/WebCore/loader/NetscapePlugInStreamLoader.cpp b/Source/WebCore/loader/NetscapePlugInStreamLoader.cpp new file mode 100644 index 0000000..75a2fe9 --- /dev/null +++ b/Source/WebCore/loader/NetscapePlugInStreamLoader.cpp @@ -0,0 +1,141 @@ +/* + * 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, const ResourceRequest& request) +{ + RefPtr<NetscapePlugInStreamLoader> loader(adoptRef(new NetscapePlugInStreamLoader(frame, client))); + loader->setShouldBufferData(false); + loader->documentLoader()->addPlugInStreamLoader(loader.get()); + if (!loader->init(request)) + return 0; + + return loader.release(); +} + +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(double finishTime) +{ + RefPtr<NetscapePlugInStreamLoader> protect(this); + + m_documentLoader->removePlugInStreamLoader(this); + m_client->didFinishLoading(this); + ResourceLoader::didFinishLoading(finishTime); +} + +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); + + // If calling didFail spins the run loop the stream loader can reach the terminal state. + // If that's the case we just return early. + if (reachedTerminalState()) + return; + + // 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/Source/WebCore/loader/NetscapePlugInStreamLoader.h b/Source/WebCore/loader/NetscapePlugInStreamLoader.h new file mode 100644 index 0000000..4d7d03b --- /dev/null +++ b/Source/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*, const ResourceRequest&); + 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(double finishTime); + virtual void didFail(const ResourceError&); + + virtual void releaseResources(); + + NetscapePlugInStreamLoader(Frame*, NetscapePlugInStreamLoaderClient*); + + virtual void didCancel(const ResourceError& error); + + NetscapePlugInStreamLoaderClient* m_client; + }; + +} diff --git a/Source/WebCore/loader/PingLoader.cpp b/Source/WebCore/loader/PingLoader.cpp new file mode 100644 index 0000000..280620a --- /dev/null +++ b/Source/WebCore/loader/PingLoader.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" +#include "PingLoader.h" + +#include "FormData.h" +#include "Frame.h" +#include "FrameLoaderClient.h" +#include "Page.h" +#include "ProgressTracker.h" +#include "ResourceHandle.h" +#include "SecurityOrigin.h" +#include <wtf/OwnPtr.h> +#include <wtf/UnusedParam.h> +#include <wtf/text/CString.h> + +namespace WebCore { + +void PingLoader::loadImage(Frame* frame, const KURL& url) +{ + if (!frame->document()->securityOrigin()->canDisplay(url)) { + FrameLoader::reportLocalLoadFailed(frame, url); + return; + } + + ResourceRequest request(url); + request.setTargetType(ResourceRequest::TargetIsImage); + request.setHTTPHeaderField("Cache-Control", "max-age=0"); + if (!SecurityOrigin::shouldHideReferrer(request.url(), frame->loader()->outgoingReferrer())) + request.setHTTPReferrer(frame->loader()->outgoingReferrer()); + frame->loader()->addExtraFieldsToSubresourceRequest(request); + OwnPtr<PingLoader> pingLoader = adoptPtr(new PingLoader(frame, request)); + + // Leak the ping loader, since it will kill itself as soon as it receives a response. + PingLoader* leakedPingLoader = pingLoader.leakPtr(); + UNUSED_PARAM(leakedPingLoader); +} + +// http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#hyperlink-auditing +void PingLoader::sendPing(Frame* frame, const KURL& pingURL, const KURL& destinationURL) +{ + ResourceRequest request(pingURL); + request.setTargetType(ResourceRequest::TargetIsSubresource); + request.setHTTPMethod("POST"); + request.setHTTPContentType("text/ping"); + request.setHTTPBody(FormData::create("PING")); + request.setHTTPHeaderField("Cache-Control", "max-age=0"); + frame->loader()->addExtraFieldsToSubresourceRequest(request); + + SecurityOrigin* sourceOrigin = frame->document()->securityOrigin(); + RefPtr<SecurityOrigin> pingOrigin = SecurityOrigin::create(pingURL); + FrameLoader::addHTTPOriginIfNeeded(request, sourceOrigin->toString()); + request.setHTTPHeaderField("Ping-To", destinationURL); + if (sourceOrigin->isSameSchemeHostPort(pingOrigin.get())) + request.setHTTPHeaderField("Ping-From", frame->document()->url()); + else if (!SecurityOrigin::shouldHideReferrer(pingURL, frame->loader()->outgoingReferrer())) + request.setHTTPReferrer(frame->loader()->outgoingReferrer()); + OwnPtr<PingLoader> pingLoader = adoptPtr(new PingLoader(frame, request)); + + // Leak the ping loader, since it will kill itself as soon as it receives a response. + PingLoader* leakedPingLoader = pingLoader.leakPtr(); + UNUSED_PARAM(leakedPingLoader); +} + +PingLoader::PingLoader(Frame* frame, const ResourceRequest& request) + : m_timeout(this, &PingLoader::timeout) +{ + unsigned long identifier = frame->page()->progress()->createUniqueIdentifier(); + m_shouldUseCredentialStorage = frame->loader()->client()->shouldUseCredentialStorage(frame->loader()->activeDocumentLoader(), identifier); + m_handle = ResourceHandle::create(frame->loader()->networkingContext(), request, this, false, false); + + // If the server never responds, FrameLoader won't be able to cancel this load and + // we'll sit here waiting forever. Set a very generous timeout, just in case. + m_timeout.startOneShot(60000); +} + +PingLoader::~PingLoader() +{ + m_handle->cancel(); +} + +} diff --git a/Source/WebCore/loader/PingLoader.h b/Source/WebCore/loader/PingLoader.h new file mode 100644 index 0000000..3a076fb --- /dev/null +++ b/Source/WebCore/loader/PingLoader.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef PingLoader_h +#define PingLoader_h + +#include "ResourceHandleClient.h" +#include "Timer.h" +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Frame; +class KURL; +class ResourceError; +class ResourceHandle; +class ResourceResponse; + +// This class triggers asynchronous loads independent of Frame staying alive (i.e., auditing pingbacks). +// Since nothing depends on resources loaded through this class, we just want +// to allow the load to live long enough to ensure the message was actually sent. +// Therefore, as soon as a callback is received from the ResourceHandle, this class +// will cancel the load and delete itself. +class PingLoader : private ResourceHandleClient, public Noncopyable { +public: + static void loadImage(Frame*, const KURL& url); + static void sendPing(Frame*, const KURL& pingURL, const KURL& destinationURL); + + ~PingLoader(); + +private: + PingLoader(Frame*, const ResourceRequest&); + + void didReceiveResponse(ResourceHandle*, const ResourceResponse&) { delete this; } + void didReceiveData(ResourceHandle*, const char*, int) { delete this; } + void didFinishLoading(ResourceHandle*, double) { delete this; } + void didFail(ResourceHandle*, const ResourceError&) { delete this; } + void timeout(Timer<PingLoader>*) { delete this; } + bool shouldUseCredentialStorage(ResourceHandle*) { return m_shouldUseCredentialStorage; } + + RefPtr<ResourceHandle> m_handle; + Timer<PingLoader> m_timeout; + bool m_shouldUseCredentialStorage; +}; + +} + +#endif diff --git a/Source/WebCore/loader/PlaceholderDocument.cpp b/Source/WebCore/loader/PlaceholderDocument.cpp new file mode 100644 index 0000000..93a26db --- /dev/null +++ b/Source/WebCore/loader/PlaceholderDocument.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009 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 "PlaceholderDocument.h" + +namespace WebCore { + +void PlaceholderDocument::attach() +{ + ASSERT(!attached()); + + // Skipping Document::attach(). + ContainerNode::attach(); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/PlaceholderDocument.h b/Source/WebCore/loader/PlaceholderDocument.h new file mode 100644 index 0000000..3d40a6e --- /dev/null +++ b/Source/WebCore/loader/PlaceholderDocument.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009 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 PlaceholderDocument_h +#define PlaceholderDocument_h + +#include "Document.h" + +namespace WebCore { + +class PlaceholderDocument : public Document { +public: + static PassRefPtr<PlaceholderDocument> create(Frame* frame, const KURL& url) + { + return adoptRef(new PlaceholderDocument(frame, url)); + } + + virtual void attach(); + +private: + PlaceholderDocument(Frame* frame, const KURL& url) : Document(frame, url, false, false) { } +}; + +} // namespace WebCore + +#endif // PlaceholderDocument_h diff --git a/Source/WebCore/loader/PolicyCallback.cpp b/Source/WebCore/loader/PolicyCallback.cpp new file mode 100644 index 0000000..4ec2c84 --- /dev/null +++ b/Source/WebCore/loader/PolicyCallback.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "PolicyCallback.h" + +#include "FormState.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "HTMLFormElement.h" + +namespace WebCore { + +PolicyCallback::PolicyCallback() + : m_navigationFunction(0) + , m_newWindowFunction(0) + , m_contentFunction(0) +{ +} + +PolicyCallback::~PolicyCallback() +{ +} + +void PolicyCallback::clear() +{ + clearRequest(); + m_navigationFunction = 0; + m_newWindowFunction = 0; + m_contentFunction = 0; +} + +void PolicyCallback::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 PolicyCallback::set(const ResourceRequest& request, PassRefPtr<FormState> formState, + const String& frameName, const NavigationAction& navigationAction, NewWindowPolicyDecisionFunction function, void* argument) +{ + m_request = request; + m_formState = formState; + m_frameName = frameName; + m_navigationAction = navigationAction; + + m_navigationFunction = 0; + m_newWindowFunction = function; + m_contentFunction = 0; + m_argument = argument; +} + +void PolicyCallback::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 PolicyCallback::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, m_navigationAction, shouldContinue); + ASSERT(!m_contentFunction); +} + +void PolicyCallback::call(PolicyAction action) +{ + ASSERT(!m_navigationFunction); + ASSERT(!m_newWindowFunction); + ASSERT(m_contentFunction); + m_contentFunction(m_argument, action); +} + +void PolicyCallback::clearRequest() +{ + m_request = ResourceRequest(); + m_formState = 0; + m_frameName = String(); +} + +void PolicyCallback::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, m_navigationAction, false); + if (m_contentFunction) + m_contentFunction(m_argument, PolicyIgnore); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/PolicyCallback.h b/Source/WebCore/loader/PolicyCallback.h new file mode 100644 index 0000000..415a3e3 --- /dev/null +++ b/Source/WebCore/loader/PolicyCallback.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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. + */ + +#ifndef PolicyCallback_h +#define PolicyCallback_h + +#include "FrameLoaderTypes.h" +#include "NavigationAction.h" +#include "ResourceRequest.h" +#include <wtf/RefPtr.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class FormState; + +typedef void (*NavigationPolicyDecisionFunction)(void* argument, + const ResourceRequest&, PassRefPtr<FormState>, bool shouldContinue); +typedef void (*NewWindowPolicyDecisionFunction)(void* argument, + const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, const NavigationAction&, bool shouldContinue); +typedef void (*ContentPolicyDecisionFunction)(void* argument, PolicyAction); + +class PolicyCallback { +public: + PolicyCallback(); + ~PolicyCallback(); + + void clear(); + void set(const ResourceRequest&, PassRefPtr<FormState>, + NavigationPolicyDecisionFunction, void* argument); + void set(const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, const NavigationAction&, + 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; + NavigationAction m_navigationAction; + + NavigationPolicyDecisionFunction m_navigationFunction; + NewWindowPolicyDecisionFunction m_newWindowFunction; + ContentPolicyDecisionFunction m_contentFunction; + void* m_argument; +}; + +} // namespace WebCore + +#endif // PolicyCallback_h diff --git a/Source/WebCore/loader/PolicyChecker.cpp b/Source/WebCore/loader/PolicyChecker.cpp new file mode 100644 index 0000000..2680386 --- /dev/null +++ b/Source/WebCore/loader/PolicyChecker.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "PolicyChecker.h" + +#include "DocumentLoader.h" +#include "FormState.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "HTMLFormElement.h" + +namespace WebCore { + +PolicyChecker::PolicyChecker(Frame* frame) + : m_frame(frame) + , m_delegateIsDecidingNavigationPolicy(false) + , m_delegateIsHandlingUnimplementablePolicy(false) + , m_loadType(FrameLoadTypeStandard) +{ +} + +void PolicyChecker::checkNavigationPolicy(const ResourceRequest& newRequest, NavigationPolicyDecisionFunction function, void* argument) +{ + checkNavigationPolicy(newRequest, m_frame->loader()->activeDocumentLoader(), 0, function, argument); +} + +void PolicyChecker::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_loadType)) + m_loadType = FrameLoadTypeReload; + function(argument, request, 0, true); + return; + } + + loader->setLastCheckedRequest(request); + + m_callback.set(request, formState.get(), function, argument); + + m_delegateIsDecidingNavigationPolicy = true; + m_frame->loader()->client()->dispatchDecidePolicyForNavigationAction(&PolicyChecker::continueAfterNavigationPolicy, + action, request, formState); + m_delegateIsDecidingNavigationPolicy = false; +} + +void PolicyChecker::checkNewWindowPolicy(const NavigationAction& action, NewWindowPolicyDecisionFunction function, + const ResourceRequest& request, PassRefPtr<FormState> formState, const String& frameName, void* argument) +{ + m_callback.set(request, formState, frameName, action, function, argument); + m_frame->loader()->client()->dispatchDecidePolicyForNewWindowAction(&PolicyChecker::continueAfterNewWindowPolicy, + action, request, formState, frameName); +} + +void PolicyChecker::checkContentPolicy(const String& MIMEType, ContentPolicyDecisionFunction function, void* argument) +{ + m_callback.set(function, argument); + m_frame->loader()->client()->dispatchDecidePolicyForMIMEType(&PolicyChecker::continueAfterContentPolicy, + MIMEType, m_frame->loader()->activeDocumentLoader()->request()); +} + +void PolicyChecker::cancelCheck() +{ + m_frame->loader()->client()->cancelPolicyCheck(); + m_callback.clear(); +} + +void PolicyChecker::stopCheck() +{ + m_frame->loader()->client()->cancelPolicyCheck(); + PolicyCallback callback = m_callback; + m_callback.clear(); + callback.cancel(); +} + +void PolicyChecker::cannotShowMIMEType(const ResourceResponse& response) +{ + handleUnimplementablePolicy(m_frame->loader()->client()->cannotShowMIMETypeError(response)); +} + +void PolicyChecker::continueLoadAfterWillSubmitForm(PolicyAction) +{ + // See header file for an explaination of why this function + // isn't like the others. + m_frame->loader()->continueLoadAfterWillSubmitForm(); +} + +void PolicyChecker::continueAfterNavigationPolicy(PolicyAction policy) +{ + PolicyCallback callback = m_callback; + m_callback.clear(); + + bool shouldContinue = policy == PolicyUse; + + switch (policy) { + case PolicyIgnore: + callback.clearRequest(); + break; + case PolicyDownload: + m_frame->loader()->client()->startDownload(callback.request()); + callback.clearRequest(); + break; + case PolicyUse: { + ResourceRequest request(callback.request()); + + if (!m_frame->loader()->client()->canHandleRequest(request)) { + handleUnimplementablePolicy(m_frame->loader()->cannotShowURLError(callback.request())); + callback.clearRequest(); + shouldContinue = false; + } + break; + } + } + + callback.call(shouldContinue); +} + +void PolicyChecker::continueAfterNewWindowPolicy(PolicyAction policy) +{ + PolicyCallback callback = m_callback; + m_callback.clear(); + + switch (policy) { + case PolicyIgnore: + callback.clearRequest(); + break; + case PolicyDownload: + m_frame->loader()->client()->startDownload(callback.request()); + callback.clearRequest(); + break; + case PolicyUse: + break; + } + + callback.call(policy == PolicyUse); +} + +void PolicyChecker::continueAfterContentPolicy(PolicyAction policy) +{ + PolicyCallback callback = m_callback; + m_callback.clear(); + callback.call(policy); +} + +void PolicyChecker::handleUnimplementablePolicy(const ResourceError& error) +{ + m_delegateIsHandlingUnimplementablePolicy = true; + m_frame->loader()->client()->dispatchUnableToImplementPolicy(error); + m_delegateIsHandlingUnimplementablePolicy = false; +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/PolicyChecker.h b/Source/WebCore/loader/PolicyChecker.h new file mode 100644 index 0000000..541729c --- /dev/null +++ b/Source/WebCore/loader/PolicyChecker.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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. + */ + +#ifndef PolicyChecker_h +#define PolicyChecker_h + +#include "FrameLoaderTypes.h" +#include "PlatformString.h" +#include "PolicyCallback.h" +#include "ResourceRequest.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class DocumentLoader; +class FormState; +class Frame; +class NavigationAction; +class ResourceError; +class ResourceResponse; + +class PolicyChecker : public Noncopyable { +public: + PolicyChecker(Frame*); + + void checkNavigationPolicy(const ResourceRequest&, DocumentLoader*, PassRefPtr<FormState>, NavigationPolicyDecisionFunction, void* argument); + void checkNavigationPolicy(const ResourceRequest&, NavigationPolicyDecisionFunction, void* argument); + void checkNewWindowPolicy(const NavigationAction&, NewWindowPolicyDecisionFunction, const ResourceRequest&, PassRefPtr<FormState>, const String& frameName, void* argument); + void checkContentPolicy(const String& MIMEType, ContentPolicyDecisionFunction, void* argument); + + // FIXME: These are different. They could use better names. + void cancelCheck(); + void stopCheck(); + + void cannotShowMIMEType(const ResourceResponse&); + + FrameLoadType loadType() const { return m_loadType; } + void setLoadType(FrameLoadType loadType) { m_loadType = loadType; } + + bool delegateIsDecidingNavigationPolicy() const { return m_delegateIsDecidingNavigationPolicy; } + bool delegateIsHandlingUnimplementablePolicy() const { return m_delegateIsHandlingUnimplementablePolicy; } + + // FIXME: This function is a cheat. Basically, this is just an asynchronouc callback + // from the FrameLoaderClient, but this callback uses the policy types and so has to + // live on this object. In the long term, we should create a type for non-policy + // callbacks from the FrameLoaderClient and remove this vestige. I just don't have + // the heart to hack on all the platforms to make that happen right now. + void continueLoadAfterWillSubmitForm(PolicyAction); + +private: + void continueAfterNavigationPolicy(PolicyAction); + void continueAfterNewWindowPolicy(PolicyAction); + void continueAfterContentPolicy(PolicyAction); + + void handleUnimplementablePolicy(const ResourceError&); + + Frame* m_frame; + + bool m_delegateIsDecidingNavigationPolicy; + bool m_delegateIsHandlingUnimplementablePolicy; + + // 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_loadType; + PolicyCallback m_callback; +}; + +} // namespace WebCore + +#endif // PolicyChecker_h diff --git a/Source/WebCore/loader/ProgressTracker.cpp b/Source/WebCore/loader/ProgressTracker.cpp new file mode 100644 index 0000000..cd15433 --- /dev/null +++ b/Source/WebCore/loader/ProgressTracker.cpp @@ -0,0 +1,263 @@ +/* + * 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 "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderStateMachine.h" +#include "FrameLoaderClient.h" +#include "Logging.h" +#include "ResourceResponse.h" +#include <wtf/text/CString.h> +#include <wtf/CurrentTime.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 : Noncopyable { + ProgressItem(long long length) + : bytesReceived(0) + , estimatedLength(length) { } + + long long bytesReceived; + long long estimatedLength; +}; + +unsigned long ProgressTracker::s_uniqueIdentifier = 0; + +ProgressTracker::ProgressTracker() + : 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, "Progress started (%p) - frame %p(\"%s\"), value %f, tracked frames %d, originating frame %p", this, frame, frame->tree()->uniqueName().string().utf8().data(), m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); + + 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, "Progress completed (%p) - frame %p(\"%s\"), value %f, tracked frames %d, originating frame %p", this, frame, frame->tree()->uniqueName().string().utf8().data(), m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); + + 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, "Final progress complete (%p)", this); + + 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, "Progress incremented (%p) - value %f, tracked frames %d, originating frame %p", this, m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get()); + + 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, adoptPtr(new ProgressItem(estimatedLength)).leakPtr()); +} + +void ProgressTracker::incrementProgress(unsigned long identifier, const char*, int length) +{ + ProgressItem* item = m_progressItems.get(identifier); + + // FIXME: Can this ever happen? + if (!item) + return; + + RefPtr<Frame> frame = m_originatingProgressFrame; + + frame->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 = frame->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; + + // For documents that use WebCore's layout system, treat first layout as the half-way point. + // FIXME: The hasHTMLView function is a sort of roundabout way of asking "do you use WebCore's layout system". + bool useClampedMaxProgress = frame->loader()->client()->hasHTMLView() + && !frame->loader()->stateMachine()->firstLayoutDone(); + double maxProgressValue = useClampedMaxProgress ? 0.5 : finalProgressValue; + 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, "Progress incremented (%p) - value %f, tracked frames %d", this, m_progressValue, m_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; + + frame->loader()->client()->postProgressEstimateChangedNotification(); + + m_lastNotifiedProgressValue = m_progressValue; + m_lastNotifiedProgressTime = now; + } + } + + frame->loader()->client()->didChangeEstimatedProgress(); +} + +void ProgressTracker::completeProgress(unsigned long identifier) +{ + ProgressItem* item = m_progressItems.get(identifier); + + // This can happen if a load fails without receiving any response data. + 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 ++s_uniqueIdentifier; +} + + +} diff --git a/Source/WebCore/loader/ProgressTracker.h b/Source/WebCore/loader/ProgressTracker.h new file mode 100644 index 0000000..5d5b6b2 --- /dev/null +++ b/Source/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 : public Noncopyable { +public: + ProgressTracker(); + ~ProgressTracker(); + + static 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(); + + static unsigned long s_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/Source/WebCore/loader/ResourceLoadNotifier.cpp b/Source/WebCore/loader/ResourceLoadNotifier.cpp new file mode 100644 index 0000000..d002ef3 --- /dev/null +++ b/Source/WebCore/loader/ResourceLoadNotifier.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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 "ResourceLoadNotifier.h" + +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "InspectorController.h" +#include "Page.h" +#include "ProgressTracker.h" +#include "ResourceLoader.h" + +namespace WebCore { + +ResourceLoadNotifier::ResourceLoadNotifier(Frame* frame) + : m_frame(frame) +{ +} + +void ResourceLoadNotifier::didReceiveAuthenticationChallenge(ResourceLoader* loader, const AuthenticationChallenge& currentWebChallenge) +{ + m_frame->loader()->client()->dispatchDidReceiveAuthenticationChallenge(loader->documentLoader(), loader->identifier(), currentWebChallenge); +} + +void ResourceLoadNotifier::didCancelAuthenticationChallenge(ResourceLoader* loader, const AuthenticationChallenge& currentWebChallenge) +{ + m_frame->loader()->client()->dispatchDidCancelAuthenticationChallenge(loader->documentLoader(), loader->identifier(), currentWebChallenge); +} + +void ResourceLoadNotifier::willSendRequest(ResourceLoader* loader, ResourceRequest& clientRequest, const ResourceResponse& redirectResponse) +{ + m_frame->loader()->applyUserAgent(clientRequest); + + dispatchWillSendRequest(loader->documentLoader(), loader->identifier(), clientRequest, redirectResponse); +} + +void ResourceLoadNotifier::didReceiveResponse(ResourceLoader* loader, const ResourceResponse& r) +{ + loader->documentLoader()->addResponse(r); + + if (Page* page = m_frame->page()) + page->progress()->incrementProgress(loader->identifier(), r); + + dispatchDidReceiveResponse(loader->documentLoader(), loader->identifier(), r); +} + +void ResourceLoadNotifier::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 ResourceLoadNotifier::didFinishLoad(ResourceLoader* loader, double finishTime) +{ + if (Page* page = m_frame->page()) + page->progress()->completeProgress(loader->identifier()); + dispatchDidFinishLoading(loader->documentLoader(), loader->identifier(), finishTime); +} + +void ResourceLoadNotifier::didFailToLoad(ResourceLoader* loader, const ResourceError& error) +{ + if (Page* page = m_frame->page()) + page->progress()->completeProgress(loader->identifier()); + + if (!error.isNull()) + m_frame->loader()->client()->dispatchDidFailLoading(loader->documentLoader(), loader->identifier(), error); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didFailLoading(loader->identifier(), error); +#endif +} + +void ResourceLoadNotifier::assignIdentifierToInitialRequest(unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request) +{ + m_frame->loader()->client()->assignIdentifierToInitialRequest(identifier, loader, request); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->identifierForInitialRequest(identifier, loader, request); +#endif +} + +void ResourceLoadNotifier::dispatchWillSendRequest(DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + StringImpl* oldRequestURL = request.url().string().impl(); + m_frame->loader()->documentLoader()->didTellClientAboutLoad(request.url()); + + m_frame->loader()->client()->dispatchWillSendRequest(loader, identifier, request, redirectResponse); + + // If the URL changed, then we want to put that new URL in the "did tell client" set too. + if (!request.isNull() && oldRequestURL != request.url().string().impl()) + m_frame->loader()->documentLoader()->didTellClientAboutLoad(request.url()); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->willSendRequest(identifier, request, redirectResponse); +#endif +} + +void ResourceLoadNotifier::dispatchDidReceiveResponse(DocumentLoader* loader, unsigned long identifier, const ResourceResponse& r) +{ + m_frame->loader()->client()->dispatchDidReceiveResponse(loader, identifier, r); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didReceiveResponse(identifier, loader, r); +#endif +} + +void ResourceLoadNotifier::dispatchDidReceiveContentLength(DocumentLoader* loader, unsigned long identifier, int length) +{ + m_frame->loader()->client()->dispatchDidReceiveContentLength(loader, identifier, length); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didReceiveContentLength(identifier, length); +#endif +} + +void ResourceLoadNotifier::dispatchDidFinishLoading(DocumentLoader* loader, unsigned long identifier, double finishTime) +{ + m_frame->loader()->client()->dispatchDidFinishLoading(loader, identifier); + +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didFinishLoading(identifier, finishTime); +#endif +} + +void ResourceLoadNotifier::dispatchTransferLoadingResourceFromPage(unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request, Page* oldPage) +{ + ASSERT(oldPage != m_frame->page()); + m_frame->loader()->client()->transferLoadingResourceFromPage(identifier, loader, request, oldPage); + + oldPage->progress()->completeProgress(identifier); +} + +void ResourceLoadNotifier::sendRemainingDelegateMessages(DocumentLoader* loader, unsigned long identifier, const ResourceResponse& response, int length, const ResourceError& error) +{ + if (!response.isNull()) + dispatchDidReceiveResponse(loader, identifier, response); + + if (length > 0) + dispatchDidReceiveContentLength(loader, identifier, length); + + if (error.isNull()) + dispatchDidFinishLoading(loader, identifier, 0); + else + m_frame->loader()->client()->dispatchDidFailLoading(loader, identifier, error); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/ResourceLoadNotifier.h b/Source/WebCore/loader/ResourceLoadNotifier.h new file mode 100644 index 0000000..2f10856 --- /dev/null +++ b/Source/WebCore/loader/ResourceLoadNotifier.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.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. + */ + +#ifndef ResourceLoadNotifier_h +#define ResourceLoadNotifier_h + +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class AuthenticationChallenge; +class DocumentLoader; +class Frame; +class Page; +class ResourceError; +class ResourceLoader; +class ResourceResponse; +class ResourceRequest; + +class ResourceLoadNotifier : public Noncopyable { +public: + ResourceLoadNotifier(Frame*); + + void didReceiveAuthenticationChallenge(ResourceLoader*, const AuthenticationChallenge&); + void didCancelAuthenticationChallenge(ResourceLoader*, const AuthenticationChallenge&); + + void willSendRequest(ResourceLoader*, ResourceRequest&, const ResourceResponse& redirectResponse); + void didReceiveResponse(ResourceLoader*, const ResourceResponse&); + void didReceiveData(ResourceLoader*, const char*, int, int lengthReceived); + void didFinishLoad(ResourceLoader*, double finishTime); + void didFailToLoad(ResourceLoader*, const ResourceError&); + + void assignIdentifierToInitialRequest(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, double finishTime); + void dispatchTransferLoadingResourceFromPage(unsigned long, DocumentLoader*, const ResourceRequest&, Page*); + + void sendRemainingDelegateMessages(DocumentLoader*, unsigned long identifier, const ResourceResponse&, int length, const ResourceError&); + +private: + Frame* m_frame; +}; + +} // namespace WebCore + +#endif // ResourceLoadNotifier_h diff --git a/Source/WebCore/loader/ResourceLoadScheduler.cpp b/Source/WebCore/loader/ResourceLoadScheduler.cpp new file mode 100644 index 0000000..8cf2c18 --- /dev/null +++ b/Source/WebCore/loader/ResourceLoadScheduler.cpp @@ -0,0 +1,289 @@ +/* + 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. + Copyright (C) 2010 Google 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 "ResourceLoadScheduler.h" + +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "InspectorInstrumentation.h" +#include "KURL.h" +#include "Logging.h" +#include "NetscapePlugInStreamLoader.h" +#include "ResourceLoader.h" +#include "ResourceRequest.h" +#include "SubresourceLoader.h" +#include <wtf/text/CString.h> + +#define REQUEST_MANAGEMENT_ENABLED 1 + +namespace WebCore { + +#if REQUEST_MANAGEMENT_ENABLED +static const unsigned maxRequestsInFlightForNonHTTPProtocols = 20; +// Match the parallel connection count used by the networking layer. +static unsigned maxRequestsInFlightPerHost; +#else +static const unsigned maxRequestsInFlightForNonHTTPProtocols = 10000; +static const unsigned maxRequestsInFlightPerHost = 10000; +#endif + +ResourceLoadScheduler::HostInformation* ResourceLoadScheduler::hostForURL(const KURL& url, CreateHostPolicy createHostPolicy) +{ + if (!url.protocolInHTTPFamily()) + return m_nonHTTPProtocolHost; + + m_hosts.checkConsistency(); + String hostName = url.host(); + HostInformation* host = m_hosts.get(hostName); + if (!host && createHostPolicy == CreateIfNotFound) { + host = new HostInformation(hostName, maxRequestsInFlightPerHost); + m_hosts.add(hostName, host); + } + return host; +} + +ResourceLoadScheduler* resourceLoadScheduler() +{ + ASSERT(isMainThread()); + DEFINE_STATIC_LOCAL(ResourceLoadScheduler, resourceLoadScheduler, ()); + return &resourceLoadScheduler; +} + +ResourceLoadScheduler::ResourceLoadScheduler() + : m_nonHTTPProtocolHost(new HostInformation(String(), maxRequestsInFlightForNonHTTPProtocols)) + , m_requestTimer(this, &ResourceLoadScheduler::requestTimerFired) + , m_isSuspendingPendingRequests(false) + , m_isSerialLoadingEnabled(false) +{ +#if REQUEST_MANAGEMENT_ENABLED + maxRequestsInFlightPerHost = initializeMaximumHTTPConnectionCountPerHost(); +#endif +} + +PassRefPtr<SubresourceLoader> ResourceLoadScheduler::scheduleSubresourceLoad(Frame* frame, SubresourceLoaderClient* client, const ResourceRequest& request, ResourceLoadPriority priority, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks, bool shouldContentSniff) +{ + PassRefPtr<SubresourceLoader> loader = SubresourceLoader::create(frame, client, request, securityCheck, sendResourceLoadCallbacks, shouldContentSniff); + if (loader) + scheduleLoad(loader.get(), priority); + return loader; +} + +PassRefPtr<NetscapePlugInStreamLoader> ResourceLoadScheduler::schedulePluginStreamLoad(Frame* frame, NetscapePlugInStreamLoaderClient* client, const ResourceRequest& request) +{ + PassRefPtr<NetscapePlugInStreamLoader> loader = NetscapePlugInStreamLoader::create(frame, client, request); + if (loader) + scheduleLoad(loader.get(), ResourceLoadPriorityLow); + return loader; +} + +void ResourceLoadScheduler::addMainResourceLoad(ResourceLoader* resourceLoader) +{ + hostForURL(resourceLoader->url(), CreateIfNotFound)->addLoadInProgress(resourceLoader); +} + +void ResourceLoadScheduler::scheduleLoad(ResourceLoader* resourceLoader, ResourceLoadPriority priority) +{ + ASSERT(resourceLoader); + ASSERT(priority != ResourceLoadPriorityUnresolved); +#if !REQUEST_MANAGEMENT_ENABLED + priority = ResourceLoadPriorityHighest; +#endif + + LOG(ResourceLoading, "ResourceLoadScheduler::load resource %p '%s'", resourceLoader, resourceLoader->url().string().latin1().data()); + HostInformation* host = hostForURL(resourceLoader->url(), CreateIfNotFound); + bool hadRequests = host->hasRequests(); + host->schedule(resourceLoader, priority); + + if (priority > ResourceLoadPriorityLow || !resourceLoader->url().protocolInHTTPFamily() || (priority == ResourceLoadPriorityLow && !hadRequests)) { + // Try to request important resources immediately. + servePendingRequests(host, priority); + } else { + // Handle asynchronously so early low priority requests don't get scheduled before later high priority ones. + InspectorInstrumentation::didScheduleResourceRequest(resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame()->document() : 0, resourceLoader->url()); + scheduleServePendingRequests(); + } +} + +void ResourceLoadScheduler::remove(ResourceLoader* resourceLoader) +{ + ASSERT(resourceLoader); + + HostInformation* host = hostForURL(resourceLoader->url()); + if (host) + host->remove(resourceLoader); + scheduleServePendingRequests(); +} + +void ResourceLoadScheduler::crossOriginRedirectReceived(ResourceLoader* resourceLoader, const KURL& redirectURL) +{ + HostInformation* oldHost = hostForURL(resourceLoader->url()); + ASSERT(oldHost); + HostInformation* newHost = hostForURL(redirectURL, CreateIfNotFound); + + if (oldHost->name() == newHost->name()) + return; + + newHost->addLoadInProgress(resourceLoader); + oldHost->remove(resourceLoader); +} + +void ResourceLoadScheduler::servePendingRequests(ResourceLoadPriority minimumPriority) +{ + LOG(ResourceLoading, "ResourceLoadScheduler::servePendingRequests. m_isSuspendingPendingRequests=%d", m_isSuspendingPendingRequests); + if (m_isSuspendingPendingRequests) + return; + + m_requestTimer.stop(); + + servePendingRequests(m_nonHTTPProtocolHost, minimumPriority); + + Vector<HostInformation*> hostsToServe; + m_hosts.checkConsistency(); + HostMap::iterator end = m_hosts.end(); + for (HostMap::iterator iter = m_hosts.begin(); iter != end; ++iter) + hostsToServe.append(iter->second); + + int size = hostsToServe.size(); + for (int i = 0; i < size; ++i) { + HostInformation* host = hostsToServe[i]; + if (host->hasRequests()) + servePendingRequests(host, minimumPriority); + else + delete m_hosts.take(host->name()); + } +} + +void ResourceLoadScheduler::servePendingRequests(HostInformation* host, ResourceLoadPriority minimumPriority) +{ + LOG(ResourceLoading, "ResourceLoadScheduler::servePendingRequests HostInformation.m_name='%s'", host->name().latin1().data()); + + for (int priority = ResourceLoadPriorityHighest; priority >= minimumPriority; --priority) { + HostInformation::RequestQueue& requestsPending = host->requestsPending((ResourceLoadPriority) priority); + + while (!requestsPending.isEmpty()) { + RefPtr<ResourceLoader> resourceLoader = requestsPending.first(); + + // For named hosts - which are only http(s) hosts - we should always enforce the connection limit. + // For non-named hosts - everything but http(s) - we should only enforce the limit if the document isn't done parsing + // and we don't know all stylesheets yet. + Document* document = resourceLoader->frameLoader() ? resourceLoader->frameLoader()->frame()->document() : 0; + bool shouldLimitRequests = !host->name().isNull() || (document && (document->parsing() || !document->haveStylesheetsLoaded())); + if (shouldLimitRequests && host->limitRequests()) + return; + + requestsPending.removeFirst(); + host->addLoadInProgress(resourceLoader.get()); + resourceLoader->start(); + } + } +} + +void ResourceLoadScheduler::suspendPendingRequests() +{ + ASSERT(!m_isSuspendingPendingRequests); + m_isSuspendingPendingRequests = true; +} + +void ResourceLoadScheduler::resumePendingRequests() +{ + ASSERT(m_isSuspendingPendingRequests); + m_isSuspendingPendingRequests = false; + if (!m_hosts.isEmpty() || m_nonHTTPProtocolHost->hasRequests()) + scheduleServePendingRequests(); +} + +void ResourceLoadScheduler::scheduleServePendingRequests() +{ + LOG(ResourceLoading, "ResourceLoadScheduler::scheduleServePendingRequests, m_requestTimer.isActive()=%u", m_requestTimer.isActive()); + if (!m_requestTimer.isActive()) + m_requestTimer.startOneShot(0); +} + +void ResourceLoadScheduler::requestTimerFired(Timer<ResourceLoadScheduler>*) +{ + LOG(ResourceLoading, "ResourceLoadScheduler::requestTimerFired\n"); + servePendingRequests(); +} + +ResourceLoadScheduler::HostInformation::HostInformation(const String& name, unsigned maxRequestsInFlight) + : m_name(name) + , m_maxRequestsInFlight(maxRequestsInFlight) +{ +} + +ResourceLoadScheduler::HostInformation::~HostInformation() +{ + ASSERT(m_requestsLoading.isEmpty()); + for (unsigned p = 0; p <= ResourceLoadPriorityHighest; p++) + ASSERT(m_requestsPending[p].isEmpty()); +} + +void ResourceLoadScheduler::HostInformation::schedule(ResourceLoader* resourceLoader, ResourceLoadPriority priority) +{ + m_requestsPending[priority].append(resourceLoader); +} + +void ResourceLoadScheduler::HostInformation::addLoadInProgress(ResourceLoader* resourceLoader) +{ + LOG(ResourceLoading, "HostInformation '%s' loading '%s'. Current count %d", m_name.latin1().data(), resourceLoader->url().string().latin1().data(), m_requestsLoading.size()); + m_requestsLoading.add(resourceLoader); +} + +void ResourceLoadScheduler::HostInformation::remove(ResourceLoader* resourceLoader) +{ + if (m_requestsLoading.contains(resourceLoader)) { + m_requestsLoading.remove(resourceLoader); + return; + } + + for (int priority = ResourceLoadPriorityHighest; priority >= ResourceLoadPriorityLowest; --priority) { + RequestQueue::iterator end = m_requestsPending[priority].end(); + for (RequestQueue::iterator it = m_requestsPending[priority].begin(); it != end; ++it) { + if (*it == resourceLoader) { + m_requestsPending[priority].remove(it); + return; + } + } + } +} + +bool ResourceLoadScheduler::HostInformation::hasRequests() const +{ + if (!m_requestsLoading.isEmpty()) + return true; + for (unsigned p = 0; p <= ResourceLoadPriorityHighest; p++) { + if (!m_requestsPending[p].isEmpty()) + return true; + } + return false; +} + +bool ResourceLoadScheduler::HostInformation::limitRequests() const +{ + return m_requestsLoading.size() >= (resourceLoadScheduler()->isSerialLoadingEnabled() ? 1 : m_maxRequestsInFlight); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/ResourceLoadScheduler.h b/Source/WebCore/loader/ResourceLoadScheduler.h new file mode 100644 index 0000000..163b67e --- /dev/null +++ b/Source/WebCore/loader/ResourceLoadScheduler.h @@ -0,0 +1,117 @@ +/* + 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. + Copyright (C) 2010 Google 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 ResourceLoadScheduler_h +#define ResourceLoadScheduler_h + +#include "FrameLoaderTypes.h" +#include "PlatformString.h" +#include "ResourceLoadPriority.h" +#include "Timer.h" +#include <wtf/Deque.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class Frame; +class KURL; +class NetscapePlugInStreamLoader; +class NetscapePlugInStreamLoaderClient; +class ResourceLoader; +class ResourceRequest; +class SubresourceLoader; +class SubresourceLoaderClient; + +class ResourceLoadScheduler : public Noncopyable { +public: + friend ResourceLoadScheduler* resourceLoadScheduler(); + + PassRefPtr<SubresourceLoader> scheduleSubresourceLoad(Frame*, SubresourceLoaderClient*, const ResourceRequest&, ResourceLoadPriority = ResourceLoadPriorityLow, SecurityCheckPolicy = DoSecurityCheck, bool sendResourceLoadCallbacks = true, bool shouldContentSniff = true); + PassRefPtr<NetscapePlugInStreamLoader> schedulePluginStreamLoad(Frame*, NetscapePlugInStreamLoaderClient*, const ResourceRequest&); + void addMainResourceLoad(ResourceLoader*); + void remove(ResourceLoader*); + void crossOriginRedirectReceived(ResourceLoader*, const KURL& redirectURL); + + void servePendingRequests(ResourceLoadPriority minimumPriority = ResourceLoadPriorityVeryLow); + void suspendPendingRequests(); + void resumePendingRequests(); + + bool isSerialLoadingEnabled() const { return m_isSerialLoadingEnabled; } + void setSerialLoadingEnabled(bool b) { m_isSerialLoadingEnabled = b; } + +private: + ResourceLoadScheduler(); + ~ResourceLoadScheduler(); + + void scheduleLoad(ResourceLoader*, ResourceLoadPriority); + void scheduleServePendingRequests(); + void requestTimerFired(Timer<ResourceLoadScheduler>*); + + class HostInformation : public Noncopyable { + public: + HostInformation(const String&, unsigned); + ~HostInformation(); + + const String& name() const { return m_name; } + void schedule(ResourceLoader*, ResourceLoadPriority = ResourceLoadPriorityVeryLow); + void addLoadInProgress(ResourceLoader*); + void remove(ResourceLoader*); + bool hasRequests() const; + bool limitRequests() const; + + typedef Deque<RefPtr<ResourceLoader> > RequestQueue; + RequestQueue& requestsPending(ResourceLoadPriority priority) { return m_requestsPending[priority]; } + + private: + RequestQueue m_requestsPending[ResourceLoadPriorityHighest + 1]; + typedef HashSet<RefPtr<ResourceLoader> > RequestMap; + RequestMap m_requestsLoading; + const String m_name; + const int m_maxRequestsInFlight; + }; + + enum CreateHostPolicy { + CreateIfNotFound, + FindOnly + }; + + HostInformation* hostForURL(const KURL&, CreateHostPolicy = FindOnly); + void servePendingRequests(HostInformation*, ResourceLoadPriority); + + typedef HashMap<String, HostInformation*, StringHash> HostMap; + HostMap m_hosts; + HostInformation* m_nonHTTPProtocolHost; + + Timer<ResourceLoadScheduler> m_requestTimer; + + bool m_isSuspendingPendingRequests; + bool m_isSerialLoadingEnabled; +}; + +ResourceLoadScheduler* resourceLoadScheduler(); + +} + +#endif diff --git a/Source/WebCore/loader/ResourceLoader.cpp b/Source/WebCore/loader/ResourceLoader.cpp new file mode 100644 index 0000000..bc56000 --- /dev/null +++ b/Source/WebCore/loader/ResourceLoader.cpp @@ -0,0 +1,527 @@ +/* + * 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 "ApplicationCacheHost.h" +#include "DocumentLoader.h" +#include "FileStreamProxy.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "InspectorInstrumentation.h" +#include "Page.h" +#include "ProgressTracker.h" +#include "ResourceError.h" +#include "ResourceHandle.h" +#include "ResourceLoadScheduler.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()) +{ +} + +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; + + resourceLoadScheduler()->remove(this); + + if (m_handle) { + // Clear out the ResourceHandle's client so that it doesn't try to call + // us back after we release it, unless it has been replaced by someone else. + if (m_handle->client() == this) + m_handle->setClient(0); + m_handle = 0; + } + + m_resourceData = 0; + m_deferredRequest = ResourceRequest(); +} + +bool ResourceLoader::init(const ResourceRequest& r) +{ + ASSERT(!m_handle); + ASSERT(m_request.isNull()); + ASSERT(m_deferredRequest.isNull()); + ASSERT(!m_documentLoader->isSubstituteLoadPending(this)); + + ResourceRequest clientRequest(r); + + // https://bugs.webkit.org/show_bug.cgi?id=26391 + // The various plug-in implementations call directly to ResourceLoader::load() instead of piping requests + // through FrameLoader. As a result, they miss the FrameLoader::addExtraFieldsToRequest() step which sets + // up the 1st party for cookies URL. Until plug-in implementations can be reigned in to pipe through that + // method, we need to make sure there is always a 1st party for cookies set. + if (clientRequest.firstPartyForCookies().isNull()) { + if (Document* document = m_frame->document()) + clientRequest.setFirstPartyForCookies(document->firstPartyForCookies()); + } + + m_request = clientRequest; + + willSendRequest(m_request, ResourceResponse()); + if (m_request.isNull()) { + didFail(frameLoader()->cancelledError(m_request)); + return false; + } + + return true; +} + +void ResourceLoader::start() +{ + ASSERT(!m_handle); + ASSERT(!m_request.isNull()); + ASSERT(m_deferredRequest.isNull()); + +#if ENABLE(ARCHIVE) // ANDROID extension: disabled to reduce code size + if (m_documentLoader->scheduleArchiveLoad(this, m_request, m_request.url())) + return; +#endif + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (m_documentLoader->applicationCacheHost()->maybeLoadResource(this, m_request, m_request.url())) + return; +#endif + + if (m_defersLoading) { + m_deferredRequest = m_request; + return; + } + + if (!m_reachedTerminalState) + m_handle = ResourceHandle::create(m_frame->loader()->networkingContext(), m_request, this, m_defersLoading, m_shouldContentSniff); +} + +void ResourceLoader::setDefersLoading(bool defers) +{ + m_defersLoading = defers; + if (m_handle) + m_handle->setDefersLoading(defers); + if (!defers && !m_deferredRequest.isNull()) { + m_request = m_deferredRequest; + m_deferredRequest = ResourceRequest(); + start(); + } +} + +#if PLATFORM(ANDROID) +// TODO: This needs upstreaming to WebKit. +void ResourceLoader::pauseLoad(bool pause) +{ + if (m_handle) + m_handle->pauseLoad(pause); +} +#endif + +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()->notifier()->assignIdentifierToInitialRequest(m_identifier, documentLoader(), request); + } + + frameLoader()->notifier()->willSendRequest(this, request, redirectResponse); + } + + if (!redirectResponse.isNull()) + resourceLoadScheduler()->crossOriginRedirectReceived(this, request.url()); + m_request = request; +} + +void ResourceLoader::didSendData(unsigned long long, unsigned long long) +{ +} + +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()->notifier()->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()->notifier()->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(double finishTime) +{ + // 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(finishTime); + releaseResources(); +} + +void ResourceLoader::didFinishLoadingOnePart(double finishTime) +{ + if (m_cancelled) + return; + ASSERT(!m_reachedTerminalState); + + if (m_calledDidFinishLoad) + return; + m_calledDidFinishLoad = true; + if (m_sendResourceLoadCallbacks) + frameLoader()->notifier()->didFinishLoad(this, finishTime); +} + +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()->notifier()->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. Canceling 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_identifier && !m_calledDidFinishLoad) + frameLoader()->notifier()->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) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForRedirect(this, request, redirectResponse)) + return; +#endif + 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) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForResponse(this, response)) + return; +#endif + InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceResponse(m_frame.get(), identifier(), response); + didReceiveResponse(response); + InspectorInstrumentation::didReceiveResourceResponse(cookie); +} + +void ResourceLoader::didReceiveData(ResourceHandle*, const char* data, int length, int lengthReceived) +{ + InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceData(m_frame.get(), identifier()); + didReceiveData(data, length, lengthReceived, false); + InspectorInstrumentation::didReceiveResourceData(cookie); +} + +void ResourceLoader::didFinishLoading(ResourceHandle*, double finishTime) +{ + didFinishLoading(finishTime); +} + +void ResourceLoader::didFail(ResourceHandle*, const ResourceError& error) +{ +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + if (documentLoader()->applicationCacheHost()->maybeLoadFallbackForError(this, error)) + return; +#endif + didFail(error); +} + +void ResourceLoader::wasBlocked(ResourceHandle*) +{ + didFail(blockedError()); +} + +void ResourceLoader::cannotShowURL(ResourceHandle*) +{ + didFail(cannotShowURLError()); +} + +bool ResourceLoader::shouldUseCredentialStorage() +{ + RefPtr<ResourceLoader> protector(this); + return frameLoader()->shouldUseCredentialStorage(this); +} + +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()->notifier()->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()->notifier()->didCancelAuthenticationChallenge(this, challenge); +} + +#if USE(PROTECTION_SPACE_AUTH_CALLBACK) +bool ResourceLoader::canAuthenticateAgainstProtectionSpace(const ProtectionSpace& protectionSpace) +{ + RefPtr<ResourceLoader> protector(this); + return frameLoader()->canAuthenticateAgainstProtectionSpace(this, protectionSpace); +} +#endif + +void ResourceLoader::receivedCancellation(const AuthenticationChallenge&) +{ + cancel(); +} + +void ResourceLoader::willCacheResponse(ResourceHandle*, CacheStoragePolicy& policy) +{ + // <rdar://problem/7249553> - There are reports of crashes with this method being called + // with a null m_frame->settings(), which can only happen if the frame doesn't have a page. + // Sadly we have no reproducible cases of this. + // We think that any frame without a page shouldn't have any loads happening in it, yet + // there is at least one code path where that is not true. + ASSERT(m_frame->settings()); + + // When in private browsing mode, prevent caching to disk + if (policy == StorageAllowed && m_frame->settings() && m_frame->settings()->privateBrowsingEnabled()) + policy = StorageAllowedInMemoryOnly; +} + +#if ENABLE(BLOB) +AsyncFileStream* ResourceLoader::createAsyncFileStream(FileStreamClient* client) +{ + // It is OK to simply return a pointer since FileStreamProxy::create adds an extra ref. + return FileStreamProxy::create(m_frame->document()->scriptExecutionContext(), client).get(); +} +#endif + +} diff --git a/Source/WebCore/loader/ResourceLoader.h b/Source/WebCore/loader/ResourceLoader.h new file mode 100644 index 0000000..e5c5d3f --- /dev/null +++ b/Source/WebCore/loader/ResourceLoader.h @@ -0,0 +1,179 @@ +/* + * 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 ApplicationCacheHost; + class DocumentLoader; + class Frame; + class FrameLoader; + class ProtectionSpace; + class ResourceHandle; + class SharedBuffer; + + class ResourceLoader : public RefCounted<ResourceLoader>, protected ResourceHandleClient { + public: + virtual ~ResourceLoader(); + + void cancel(); + + virtual bool init(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); +#if PLATFORM(ANDROID) +// TODO: This needs upstreaming to WebKit. + virtual void pauseLoad(bool); +#endif + + 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); + virtual void didReceiveCachedMetadata(const char*, int) { } + void willStopBufferingData(const char*, int); + virtual void didFinishLoading(double finishTime); + virtual void didFail(const ResourceError&); + + virtual bool shouldUseCredentialStorage(); + virtual void didReceiveAuthenticationChallenge(const AuthenticationChallenge&); + void didCancelAuthenticationChallenge(const AuthenticationChallenge&); +#if USE(PROTECTION_SPACE_AUTH_CALLBACK) + virtual bool canAuthenticateAgainstProtectionSpace(const ProtectionSpace&); +#endif + 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 didReceiveCachedMetadata(ResourceHandle*, const char* data, int length) { didReceiveCachedMetadata(data, length); } + virtual void didFinishLoading(ResourceHandle*, double finishTime); + 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 bool shouldUseCredentialStorage(ResourceHandle*) { return shouldUseCredentialStorage(); } + virtual void didReceiveAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge& challenge) { didReceiveAuthenticationChallenge(challenge); } + virtual void didCancelAuthenticationChallenge(ResourceHandle*, const AuthenticationChallenge& challenge) { didCancelAuthenticationChallenge(challenge); } +#if USE(PROTECTION_SPACE_AUTH_CALLBACK) + virtual bool canAuthenticateAgainstProtectionSpace(ResourceHandle*, const ProtectionSpace& protectionSpace) { return canAuthenticateAgainstProtectionSpace(protectionSpace); } +#endif + virtual void receivedCancellation(ResourceHandle*, const AuthenticationChallenge& challenge) { receivedCancellation(challenge); } + virtual void willCacheResponse(ResourceHandle*, CacheStoragePolicy&); +#if PLATFORM(MAC) + virtual NSCachedURLResponse* willCacheResponse(ResourceHandle*, NSCachedURLResponse*); +#endif +#if USE(CFNETWORK) + virtual bool shouldCacheResponse(ResourceHandle*, CFCachedURLResponseRef); +#endif +#if ENABLE(BLOB) + virtual AsyncFileStream* createAsyncFileStream(FileStreamClient*); +#endif + + const KURL& url() const { return m_request.url(); } + ResourceHandle* handle() const { return m_handle.get(); } + bool sendResourceLoadCallbacks() const { return m_sendResourceLoadCallbacks; } + + bool reachedTerminalState() const { return m_reachedTerminalState; } + + void setShouldBufferData(bool shouldBufferData); + + protected: + ResourceLoader(Frame*, bool sendResourceLoadCallbacks, bool shouldContentSniff); + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + friend class ApplicationCacheHost; // for access to request() +#endif + friend class ResourceLoadScheduler; // for access to start() + // start() actually sends the load to the network (unless the load is being + // deferred) and should only be called by ResourceLoadScheduler or setDefersLoading(). + void start(); + + virtual void didCancel(const ResourceError&); + void didFinishLoadingOnePart(double finishTime); + + const ResourceRequest& request() const { return m_request; } + 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; + ResourceRequest m_deferredRequest; + }; + +} + +#endif diff --git a/Source/WebCore/loader/SinkDocument.cpp b/Source/WebCore/loader/SinkDocument.cpp new file mode 100644 index 0000000..47535dc --- /dev/null +++ b/Source/WebCore/loader/SinkDocument.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 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 "SinkDocument.h" + +#include "RawDataDocumentParser.h" + +namespace WebCore { + +class SinkDocumentParser : public RawDataDocumentParser { +public: + static PassRefPtr<SinkDocumentParser> create(SinkDocument* document) + { + return adoptRef(new SinkDocumentParser(document)); + } + +private: + SinkDocumentParser(SinkDocument* document) + : RawDataDocumentParser(document) + { + } + + // Ignore all data. + virtual void appendBytes(DocumentWriter*, const char*, int, bool) { } +}; + +SinkDocument::SinkDocument(Frame* frame, const KURL& url) + : HTMLDocument(frame, url) +{ + setCompatibilityMode(QuirksMode); + lockCompatibilityMode(); +} + +PassRefPtr<DocumentParser> SinkDocument::createParser() +{ + return SinkDocumentParser::create(this); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/SinkDocument.h b/Source/WebCore/loader/SinkDocument.h new file mode 100644 index 0000000..50152ff --- /dev/null +++ b/Source/WebCore/loader/SinkDocument.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 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 SinkDocument_h +#define SinkDocument_h + +#include "HTMLDocument.h" + +namespace WebCore { + +class SinkDocument : public HTMLDocument { +public: + static PassRefPtr<SinkDocument> create(Frame* frame, const KURL& url) + { + return adoptRef(new SinkDocument(frame, url)); + } + +private: + SinkDocument(Frame*, const KURL&); + + virtual PassRefPtr<DocumentParser> createParser(); +}; + + +}; // namespace WebCore + +#endif // SinkDocument_h diff --git a/Source/WebCore/loader/SubframeLoader.cpp b/Source/WebCore/loader/SubframeLoader.cpp new file mode 100644 index 0000000..eba3173 --- /dev/null +++ b/Source/WebCore/loader/SubframeLoader.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) 2008 Alp Toker <alp@atoker.com> + * Copyright (C) Research In Motion Limited 2009. 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 "SubframeLoader.h" + +#include "Frame.h" +#include "FrameLoaderClient.h" +#include "HTMLAppletElement.h" +#include "HTMLFrameElementBase.h" +#include "HTMLNames.h" +#include "HTMLPlugInImageElement.h" +#include "MIMETypeRegistry.h" +#include "Page.h" +#include "PluginData.h" +#include "PluginDocument.h" +#include "RenderEmbeddedObject.h" +#include "RenderView.h" +#include "Settings.h" +#include "XSSAuditor.h" + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +#include "HTMLMediaElement.h" +#include "RenderVideo.h" +#endif + +namespace WebCore { + +using namespace HTMLNames; + +SubframeLoader::SubframeLoader(Frame* frame) + : m_containsPlugins(false) + , m_frame(frame) +{ +} + +void SubframeLoader::clear() +{ + m_containsPlugins = false; +} + +bool SubframeLoader::requestFrame(HTMLFrameOwnerElement* ownerElement, const String& urlString, const AtomicString& frameName, bool lockHistory, bool lockBackForwardList) +{ + // Support for <frame src="javascript:string"> + KURL scriptURL; + KURL url; + if (protocolIsJavaScript(urlString)) { + scriptURL = completeURL(urlString); // completeURL() encodes the URL. + url = blankURL(); + } else + url = completeURL(urlString); + + Frame* frame = loadOrRedirectSubframe(ownerElement, url, frameName, lockHistory, lockBackForwardList); + if (!frame) + return false; + + if (!scriptURL.isEmpty()) + frame->script()->executeIfJavaScriptURL(scriptURL); + + return true; +} + +bool SubframeLoader::resourceWillUsePlugin(const String& url, const String& mimeType) +{ + KURL completedURL; + if (!url.isEmpty()) + completedURL = completeURL(url); + bool useFallback; + return shouldUsePlugin(completedURL, mimeType, false, useFallback); +} + +bool SubframeLoader::requestObject(HTMLPlugInImageElement* ownerElement, 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 (!m_frame->script()->xssAuditor()->canLoadObject(url)) { + // It is unsafe to honor the request for this object. + return false; + } + + // FIXME: None of this code should use renderers! + RenderEmbeddedObject* renderer = ownerElement->renderEmbeddedObject(); + ASSERT(renderer); + if (!renderer) + return false; + + KURL completedURL; + if (!url.isEmpty()) + completedURL = completeURL(url); + + bool useFallback; + if (shouldUsePlugin(completedURL, mimeType, renderer->hasFallbackContent(), useFallback)) { + Settings* settings = m_frame->settings(); + if ((!allowPlugins(AboutToInstantiatePlugin) + // Application plugins are plugins implemented by the user agent, for example Qt plugins, + // as opposed to third-party code such as flash. The user agent decides whether or not they are + // permitted, rather than WebKit. + && !MIMETypeRegistry::isApplicationPluginMIMEType(mimeType)) + || (!settings->isJavaEnabled() && MIMETypeRegistry::isJavaAppletMIMEType(mimeType))) + return false; + if (m_frame->document() && m_frame->document()->securityOrigin()->isSandboxed(SandboxPlugins)) + return false; + + ASSERT(ownerElement->hasTagName(objectTag) || ownerElement->hasTagName(embedTag)); + HTMLPlugInImageElement* pluginElement = static_cast<HTMLPlugInImageElement*>(ownerElement); + + return loadPlugin(pluginElement, completedURL, mimeType, paramNames, paramValues, useFallback); + } + + // If the plug-in element already contains a subframe, loadOrRedirectSubframe will re-use it. Otherwise, + // it will create a new frame and set it as the RenderPart's widget, causing what was previously + // in the widget to be torn down. + return loadOrRedirectSubframe(ownerElement, completedURL, frameName, true, true); +} + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +PassRefPtr<Widget> SubframeLoader::loadMediaPlayerProxyPlugin(Node* node, const KURL& url, + const Vector<String>& paramNames, const Vector<String>& paramValues) +{ + ASSERT(node->hasTagName(videoTag) || node->hasTagName(audioTag)); + + if (!m_frame->script()->xssAuditor()->canLoadObject(url.string())) + return 0; + + KURL completedURL; + if (!url.isEmpty()) + completedURL = completeURL(url); + + if (!m_frame->document()->securityOrigin()->canDisplay(completedURL)) { + FrameLoader::reportLocalLoadFailed(m_frame, completedURL.string()); + return 0; + } + + HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(node); + RenderPart* renderer = toRenderPart(node->renderer()); + IntSize size; + + if (renderer) + size = IntSize(renderer->contentWidth(), renderer->contentHeight()); + else if (mediaElement->isVideo()) + size = RenderVideo::defaultSize(); + + m_frame->loader()->checkIfRunInsecureContent(m_frame->document()->securityOrigin(), completedURL); + + RefPtr<Widget> widget = m_frame->loader()->client()->createMediaPlayerProxyPlugin(size, mediaElement, completedURL, + paramNames, paramValues, "application/x-media-element-proxy-plugin"); + + if (widget && renderer) { + renderer->setWidget(widget); + renderer->node()->setNeedsStyleRecalc(SyntheticStyleChange); + } + m_containsPlugins = true; + + return widget ? widget.release() : 0; +} + +void FrameLoader::hideMediaPlayerProxyPlugin(Widget* widget) +{ + m_client->hideMediaPlayerProxyPlugin(widget); +} + +void FrameLoader::showMediaPlayerProxyPlugin(Widget* widget) +{ + m_client->showMediaPlayerProxyPlugin(widget); +} + +#endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO) + +PassRefPtr<Widget> SubframeLoader::createJavaAppletWidget(const IntSize& size, HTMLAppletElement* element, const HashMap<String, String>& args) +{ + String baseURLString; + String codeBaseURLString; + 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; + else if (equalIgnoringCase(it->first, "codebase")) + codeBaseURLString = it->second; + paramNames.append(it->first); + paramValues.append(it->second); + } + + if (!codeBaseURLString.isEmpty()) { + KURL codeBaseURL = completeURL(codeBaseURLString); + if (!element->document()->securityOrigin()->canDisplay(codeBaseURL)) { + FrameLoader::reportLocalLoadFailed(m_frame, codeBaseURL.string()); + return 0; + } + } + + if (baseURLString.isEmpty()) + baseURLString = m_frame->document()->baseURL().string(); + KURL baseURL = completeURL(baseURLString); + + RefPtr<Widget> widget; + if (allowPlugins(AboutToInstantiatePlugin)) + widget = m_frame->loader()->client()->createJavaAppletWidget(size, element, baseURL, paramNames, paramValues); + if (!widget) + return 0; + + m_containsPlugins = true; + return widget; +} + +Frame* SubframeLoader::loadOrRedirectSubframe(HTMLFrameOwnerElement* ownerElement, const KURL& url, const AtomicString& frameName, bool lockHistory, bool lockBackForwardList) +{ + Frame* frame = ownerElement->contentFrame(); + if (frame) + frame->navigationScheduler()->scheduleLocationChange(m_frame->document()->securityOrigin(), url.string(), m_frame->loader()->outgoingReferrer(), lockHistory, lockBackForwardList); + else + frame = loadSubframe(ownerElement, url, frameName, m_frame->loader()->outgoingReferrer()); + return frame; +} + +Frame* SubframeLoader::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->marginWidth(); + marginHeight = o->marginHeight(); + } + + if (!ownerElement->document()->securityOrigin()->canDisplay(url)) { + FrameLoader::reportLocalLoadFailed(m_frame, url.string()); + return 0; + } + + bool hideReferrer = SecurityOrigin::shouldHideReferrer(url, referrer); + RefPtr<Frame> frame = m_frame->loader()->client()->createFrame(url, name, ownerElement, hideReferrer ? String() : referrer, allowsScrolling, marginWidth, marginHeight); + + if (!frame) { + m_frame->loader()->checkCallImplicitClose(); + return 0; + } + + // All new frames will have m_isComplete set to true at this point due to synchronously loading + // an empty document in FrameLoader::init(). But many frames will now be starting an + // asynchronous load of url, so we set m_isComplete to false and then check if the load is + // actually completed below. (Note that we set m_isComplete to false even for synchronous + // loads, so that checkCompleted() below won't bail early.) + // FIXME: Can we remove this entirely? m_isComplete normally gets set to false when a load is committed. + frame->loader()->started(); + + RenderObject* renderer = ownerElement->renderer(); + FrameView* view = frame->view(); + if (renderer && renderer->isWidget() && view) + toRenderWidget(renderer)->setWidget(view); + + m_frame->loader()->checkCallImplicitClose(); + + // Some loads are performed synchronously (e.g., about:blank and loads + // cancelled by returning a null ResourceRequest from requestFromDelegate). + // 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 and mark the load as being + // complete. + // 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 (frame->loader()->state() == FrameStateComplete && !frame->loader()->policyDocumentLoader()) + frame->loader()->checkCompleted(); + + return frame.get(); +} + +bool SubframeLoader::allowPlugins(ReasonForCallingAllowPlugins reason) +{ + Settings* settings = m_frame->settings(); + bool allowed = m_frame->loader()->client()->allowPlugins(settings && settings->arePluginsEnabled()); + if (!allowed && reason == AboutToInstantiatePlugin) + m_frame->loader()->client()->didNotAllowPlugins(); + return allowed; +} + +bool SubframeLoader::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback) +{ + if (m_frame->loader()->client()->shouldUsePluginDocument(mimeType)) { + useFallback = false; + return true; + } + + // 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")) { + const PluginData* pluginData = m_frame->page()->pluginData(); + String pluginName = pluginData ? pluginData->pluginNameForMimeType(mimeType) : String(); + if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false)) + return true; + } + + ObjectContentType objectType = m_frame->loader()->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; +} + +Document* SubframeLoader::document() const +{ + return m_frame->document(); +} + +bool SubframeLoader::loadPlugin(HTMLPlugInImageElement* pluginElement, const KURL& url, const String& mimeType, + const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback) +{ + RenderEmbeddedObject* renderer = pluginElement->renderEmbeddedObject(); + + // FIXME: This code should not depend on renderer! + if (!renderer || useFallback) + return false; + + if (!document()->securityOrigin()->canDisplay(url)) { + FrameLoader::reportLocalLoadFailed(m_frame, url.string()); + return false; + } + + FrameLoader* frameLoader = m_frame->loader(); + frameLoader->checkIfRunInsecureContent(document()->securityOrigin(), url); + + IntSize contentSize(renderer->contentWidth(), renderer->contentHeight()); + bool loadManually = document()->isPluginDocument() && !m_containsPlugins && toPluginDocument(document())->shouldLoadPluginManually(); + RefPtr<Widget> widget = frameLoader->client()->createPlugin(contentSize, + pluginElement, url, paramNames, paramValues, mimeType, loadManually); + + if (!widget) { + renderer->setShowsMissingPluginIndicator(); + return false; + } + + renderer->setWidget(widget); + m_containsPlugins = true; + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) || ENABLE(3D_PLUGIN) + pluginElement->setNeedsStyleRecalc(SyntheticStyleChange); +#endif + return true; +} + +KURL SubframeLoader::completeURL(const String& url) const +{ + ASSERT(m_frame->document()); + return m_frame->document()->completeURL(url); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/SubframeLoader.h b/Source/WebCore/loader/SubframeLoader.h new file mode 100644 index 0000000..a573045 --- /dev/null +++ b/Source/WebCore/loader/SubframeLoader.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) + * Copyright (C) Research In Motion Limited 2009. 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 SubframeLoader_h +#define SubframeLoader_h + +#include "FrameLoaderTypes.h" +#include "PlatformString.h" +#include <wtf/Forward.h> +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class Document; +class Frame; +class FrameLoaderClient; +class HTMLAppletElement; +class HTMLFrameOwnerElement; +class HTMLPlugInImageElement; +class IntSize; +class KURL; +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) +class Node; +#endif +class Widget; + +// This is a slight misnomer. It handles the higher level logic of loading both subframes and plugins. +class SubframeLoader : public Noncopyable { +public: + SubframeLoader(Frame*); + + void clear(); + + bool requestFrame(HTMLFrameOwnerElement*, const String& url, const AtomicString& frameName, bool lockHistory = true, bool lockBackForwardList = true); + bool requestObject(HTMLPlugInImageElement*, const String& url, const AtomicString& frameName, + const String& serviceType, const Vector<String>& paramNames, const Vector<String>& paramValues); + +#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) + // FIXME: This should take Element* instead of Node*, or better yet the + // specific type of Element which this code depends on. + PassRefPtr<Widget> loadMediaPlayerProxyPlugin(Node*, const KURL&, const Vector<String>& paramNames, const Vector<String>& paramValues); +#endif + + PassRefPtr<Widget> createJavaAppletWidget(const IntSize&, HTMLAppletElement*, const HashMap<String, String>& args); + + bool allowPlugins(ReasonForCallingAllowPlugins); + + bool containsPlugins() const { return m_containsPlugins; } + + bool resourceWillUsePlugin(const String& url, const String& mimeType); + +private: + Frame* loadOrRedirectSubframe(HTMLFrameOwnerElement*, const KURL&, const AtomicString& frameName, bool lockHistory, bool lockBackForwardList); + Frame* loadSubframe(HTMLFrameOwnerElement*, const KURL&, const String& name, const String& referrer); + bool loadPlugin(HTMLPlugInImageElement*, const KURL&, const String& mimeType, + const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback); + + bool shouldUsePlugin(const KURL&, const String& mimeType, bool hasFallback, bool& useFallback); + + Document* document() const; + + bool m_containsPlugins; + Frame* m_frame; + + KURL completeURL(const String&) const; +}; + +} // namespace WebCore + +#endif // SubframeLoader_h diff --git a/Source/WebCore/loader/SubresourceLoader.cpp b/Source/WebCore/loader/SubresourceLoader.cpp new file mode 100644 index 0000000..69f7906 --- /dev/null +++ b/Source/WebCore/loader/SubresourceLoader.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2006, 2007, 2009 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 "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "ResourceHandle.h" +#include "SecurityOrigin.h" +#include "SubresourceLoaderClient.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 +} + +SubresourceLoader::~SubresourceLoader() +{ +#ifndef NDEBUG + subresourceLoaderCounter.decrement(); +#endif +} + +PassRefPtr<SubresourceLoader> SubresourceLoader::create(Frame* frame, SubresourceLoaderClient* client, const ResourceRequest& request, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks, bool shouldContentSniff) +{ + if (!frame) + return 0; + + FrameLoader* fl = frame->loader(); + if (securityCheck == DoSecurityCheck && (fl->state() == FrameStateProvisional || !fl->activeDocumentLoader() || fl->activeDocumentLoader()->isStopping())) + return 0; + + ResourceRequest newRequest = request; + + if (securityCheck == DoSecurityCheck && !frame->document()->securityOrigin()->canDisplay(request.url())) { + FrameLoader::reportLocalLoadFailed(frame, request.url().string()); + return 0; + } + + if (SecurityOrigin::shouldHideReferrer(request.url(), fl->outgoingReferrer())) + newRequest.clearHTTPReferrer(); + else if (!request.httpReferrer()) + newRequest.setHTTPReferrer(fl->outgoingReferrer()); + FrameLoader::addHTTPOriginIfNeeded(newRequest, fl->outgoingOrigin()); + + fl->addExtraFieldsToSubresourceRequest(newRequest); + + RefPtr<SubresourceLoader> subloader(adoptRef(new SubresourceLoader(frame, client, sendResourceLoadCallbacks, shouldContentSniff))); + subloader->documentLoader()->addSubresourceLoader(subloader.get()); + if (!subloader->init(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(0); + } +} + +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::didReceiveCachedMetadata(const char* data, int length) +{ + // 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->didReceiveCachedMetadata(this, data, length); +} + +void SubresourceLoader::didFinishLoading(double finishTime) +{ + 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(finishTime); +} + +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; + + // The only way the subresource loader can reach the terminal state here is if the run loop spins when calling + // m_client->didFail. This should in theory not happen which is why the assert is here. + ASSERT(!reachedTerminalState()); + if (reachedTerminalState()) + return; + + m_documentLoader->removeSubresourceLoader(this); + ResourceLoader::didCancel(error); +} + +bool SubresourceLoader::shouldUseCredentialStorage() +{ + RefPtr<SubresourceLoader> protect(this); + + bool shouldUse; + if (m_client && m_client->getShouldUseCredentialStorage(this, shouldUse)) + return shouldUse; + + return ResourceLoader::shouldUseCredentialStorage(); +} + +void SubresourceLoader::didReceiveAuthenticationChallenge(const AuthenticationChallenge& challenge) +{ + RefPtr<SubresourceLoader> protect(this); + + ASSERT(handle()->hasAuthenticationChallenge()); + + 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; + + // It may have also handled authentication on its own. + if (!handle()->hasAuthenticationChallenge()) + 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/Source/WebCore/loader/SubresourceLoader.h b/Source/WebCore/loader/SubresourceLoader.h new file mode 100644 index 0000000..cb7ed81 --- /dev/null +++ b/Source/WebCore/loader/SubresourceLoader.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005, 2006, 2009 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 SubresourceLoader_h +#define SubresourceLoader_h + +#include "FrameLoaderTypes.h" +#include "ResourceLoader.h" + +namespace WebCore { + + class ResourceRequest; + class SubresourceLoaderClient; + + class SubresourceLoader : public ResourceLoader { + public: + static PassRefPtr<SubresourceLoader> create(Frame*, SubresourceLoaderClient*, const ResourceRequest&, SecurityCheckPolicy = DoSecurityCheck, bool sendResourceLoadCallbacks = true, bool shouldContentSniff = true); + + void clearClient() { m_client = 0; } + + private: + SubresourceLoader(Frame*, SubresourceLoaderClient*, bool sendResourceLoadCallbacks, bool shouldContentSniff); + virtual ~SubresourceLoader(); + + 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 didReceiveCachedMetadata(const char*, int); + virtual void didFinishLoading(double finishTime); + virtual void didFail(const ResourceError&); + virtual bool shouldUseCredentialStorage(); + virtual void didReceiveAuthenticationChallenge(const AuthenticationChallenge&); + virtual void receivedCancellation(const AuthenticationChallenge&); + virtual void didCancel(const ResourceError&); + + SubresourceLoaderClient* m_client; + bool m_loadingMultipartContent; + }; + +} + +#endif // SubresourceLoader_h diff --git a/Source/WebCore/loader/SubresourceLoaderClient.h b/Source/WebCore/loader/SubresourceLoaderClient.h new file mode 100644 index 0000000..e18abe3 --- /dev/null +++ b/Source/WebCore/loader/SubresourceLoaderClient.h @@ -0,0 +1,62 @@ +/* + * 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 /*lengthReceived*/) { } + virtual void didReceiveCachedMetadata(SubresourceLoader*, const char*, int /*lengthReceived*/) { } + virtual void didFinishLoading(SubresourceLoader*) { } + virtual void didFail(SubresourceLoader*, const ResourceError&) { } + + virtual bool getShouldUseCredentialStorage(SubresourceLoader*, bool& /*shouldUseCredentialStorage*/) { return false; } + virtual void didReceiveAuthenticationChallenge(SubresourceLoader*, const AuthenticationChallenge&) { } + virtual void receivedCancellation(SubresourceLoader*, const AuthenticationChallenge&) { } + +}; + +} // namespace WebCore + +#endif // SubresourceLoaderClient_h diff --git a/Source/WebCore/loader/SubstituteData.h b/Source/WebCore/loader/SubstituteData.h new file mode 100644 index 0000000..0b87b62 --- /dev/null +++ b/Source/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/Source/WebCore/loader/SubstituteResource.h b/Source/WebCore/loader/SubstituteResource.h new file mode 100644 index 0000000..15cbc6f --- /dev/null +++ b/Source/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/Source/WebCore/loader/TextResourceDecoder.cpp b/Source/WebCore/loader/TextResourceDecoder.cpp new file mode 100644 index 0000000..c8198e3 --- /dev/null +++ b/Source/WebCore/loader/TextResourceDecoder.cpp @@ -0,0 +1,694 @@ +/* + Copyright (C) 1999 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 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 "HTMLMetaCharsetParser.h" +#include "HTMLNames.h" +#include "TextCodec.h" +#include "TextEncoding.h" +#include "TextEncodingDetector.h" +#include "TextEncodingRegistry.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 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, bool usesEncodingDetector) + : m_contentType(determineContentType(mimeType)) + , m_encoding(defaultEncoding(m_contentType, specifiedDefaultEncoding)) + , m_source(DefaultEncoding) + , m_hintEncoding(0) + , m_checkedForBOM(false) + , m_checkedForCSSCharset(false) + , m_checkedForHeadCharset(false) + , m_useLenientXMLDecoding(false) + , m_sawError(false) + , m_usesEncodingDetector(usesEncodingDetector) +{ +} + +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_encoding = "windows-1252"; + else if (source == EncodingFromMetaTag || source == EncodingFromXMLHeader || source == EncodingFromCSSCharset) + m_encoding = encoding.closestByteBasedEquivalent(); + else + m_encoding = encoding; + + m_codec.clear(); + 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; +} + +size_t 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. + // We let it override even a user-chosen encoding. + ASSERT(!m_checkedForBOM); + + size_t lengthOfBOM = 0; + + size_t bufferLength = m_buffer.size(); + + size_t buf1Len = bufferLength; + size_t buf2Len = len; + const unsigned char* buf1 = reinterpret_cast<const unsigned char*>(m_buffer.data()); + const unsigned char* buf2 = reinterpret_cast<const unsigned char*>(data); + unsigned char c1 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0; + unsigned char c2 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0; + unsigned char c3 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0; + unsigned char c4 = buf2Len ? (--buf2Len, *buf2++) : 0; + + // Check for the BOM. + if (c1 == 0xFF && c2 == 0xFE) { + if (c3 != 0 || c4 != 0) { + setEncoding(UTF16LittleEndianEncoding(), AutoDetectedEncoding); + lengthOfBOM = 2; + } else { + setEncoding(UTF32LittleEndianEncoding(), AutoDetectedEncoding); + lengthOfBOM = 4; + } + } else if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) { + setEncoding(UTF8Encoding(), AutoDetectedEncoding); + lengthOfBOM = 3; + } else if (c1 == 0xFE && c2 == 0xFF) { + setEncoding(UTF16BigEndianEncoding(), AutoDetectedEncoding); + lengthOfBOM = 2; + } else if (c1 == 0 && c2 == 0 && c3 == 0xFE && c4 == 0xFF) { + setEncoding(UTF32BigEndianEncoding(), AutoDetectedEncoding); + lengthOfBOM = 4; + } + + if (lengthOfBOM || bufferLength + len >= 4) + m_checkedForBOM = true; + + return lengthOfBOM; +} + +bool TextResourceDecoder::checkForCSSCharset(const char* data, size_t len, bool& movedDataToBuffer) +{ + if (m_source != DefaultEncoding && m_source != EncodingFromParentFrame) { + 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; + + ++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; + if (p == pEnd) + return; + // Allow <!-->; other browsers do. + if (*p == '>') { + p++; + } else { + while (p + 2 < 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 + 3 < pEnd && p[1] == '-' && p[2] == '!' && p[3] == '>') { + p += 4; + break; + } + } + p++; + } + } + ptr = p; +} + +bool TextResourceDecoder::checkForHeadCharset(const char* data, size_t len, bool& movedDataToBuffer) +{ + if (m_source != DefaultEncoding && m_source != EncodingFromParentFrame) { + 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; + + // Continue with checking for an HTML meta tag if we were already doing so. + if (m_charsetParser) + return checkForMetaCharset(data, len); + + 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 = 0; + 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; + } + + // The HTTP-EQUIV meta has no effect on XHTML. + if (m_contentType == XML) + return true; + + m_charsetParser = HTMLMetaCharsetParser::create(); + return checkForMetaCharset(data, len); +} + +bool TextResourceDecoder::checkForMetaCharset(const char* data, size_t length) +{ + if (!m_charsetParser->checkForMetaCharset(data, length)) + return false; + + setEncoding(m_charsetParser->encoding(), EncodingFromMetaTag); + m_charsetParser.clear(); + m_checkedForHeadCharset = true; + return true; +} + +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; + } +} + +// We use the encoding detector in two cases: +// 1. Encoding detector is turned ON and no other encoding source is +// available (that is, it's DefaultEncoding). +// 2. Encoding detector is turned ON and the encoding is set to +// the encoding of the parent frame, which is also auto-detected. +// Note that condition #2 is NOT satisfied unless parent-child frame +// relationship is compliant to the same-origin policy. If they're from +// different domains, |m_source| would not be set to EncodingFromParentFrame +// in the first place. +bool TextResourceDecoder::shouldAutoDetect() const +{ + // Just checking m_hintEncoding suffices here because it's only set + // in setHintEncoding when the source is AutoDetectedEncoding. + return m_usesEncodingDetector + && (m_source == DefaultEncoding || (m_source == EncodingFromParentFrame && m_hintEncoding)); +} + +String TextResourceDecoder::decode(const char* data, size_t len) +{ + size_t lengthOfBOM = 0; + if (!m_checkedForBOM) + lengthOfBOM = 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 ""; + + // FIXME: It is wrong to change the encoding downstream after we have already done some decoding. + if (shouldAutoDetect()) { + if (m_encoding.isJapanese()) + detectJapaneseEncoding(data, len); // FIXME: We should use detectTextEncoding() for all languages. + else { + TextEncoding detectedEncoding; + if (detectTextEncoding(data, len, m_hintEncoding, &detectedEncoding)) + setEncoding(detectedEncoding, AutoDetectedEncoding); + } + } + + ASSERT(m_encoding.isValid()); + + if (!m_codec) + m_codec = newTextCodec(m_encoding); + + if (m_buffer.isEmpty()) + return m_codec->decode(data + lengthOfBOM, len - lengthOfBOM, 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_codec->decode(m_buffer.data() + lengthOfBOM, m_buffer.size() - lengthOfBOM, false, m_contentType == XML && !m_useLenientXMLDecoding, m_sawError); + m_buffer.clear(); + return result; +} + +String TextResourceDecoder::flush() +{ + // If we can not identify the encoding even after a document is completely + // loaded, we need to detect the encoding if other conditions for + // autodetection is satisfied. + if (m_buffer.size() && shouldAutoDetect() + && ((!m_checkedForHeadCharset && (m_contentType == HTML || m_contentType == XML)) || (!m_checkedForCSSCharset && (m_contentType == CSS)))) { + TextEncoding detectedEncoding; + if (detectTextEncoding(m_buffer.data(), m_buffer.size(), + m_hintEncoding, &detectedEncoding)) + setEncoding(detectedEncoding, AutoDetectedEncoding); + } + + if (!m_codec) + m_codec = newTextCodec(m_encoding); + + String result = m_codec->decode(m_buffer.data(), m_buffer.size(), true, m_contentType == XML && !m_useLenientXMLDecoding, m_sawError); + m_buffer.clear(); + m_codec.clear(); + m_checkedForBOM = false; // Skip BOM again when re-decoding. + return result; +} + +} diff --git a/Source/WebCore/loader/TextResourceDecoder.h b/Source/WebCore/loader/TextResourceDecoder.h new file mode 100644 index 0000000..0bb855b --- /dev/null +++ b/Source/WebCore/loader/TextResourceDecoder.h @@ -0,0 +1,101 @@ +/* + 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 "TextEncoding.h" + +namespace WebCore { + +class HTMLMetaCharsetParser; + +class TextResourceDecoder : public RefCounted<TextResourceDecoder> { +public: + enum EncodingSource { + DefaultEncoding, + AutoDetectedEncoding, + EncodingFromXMLHeader, + EncodingFromMetaTag, + EncodingFromCSSCharset, + EncodingFromHTTPHeader, + UserChosenEncoding, + EncodingFromParentFrame + }; + + static PassRefPtr<TextResourceDecoder> create(const String& mimeType, const TextEncoding& defaultEncoding = TextEncoding(), bool usesEncodingDetector = false) + { + return adoptRef(new TextResourceDecoder(mimeType, defaultEncoding, usesEncodingDetector)); + } + ~TextResourceDecoder(); + + void setEncoding(const TextEncoding&, EncodingSource); + const TextEncoding& encoding() const { return m_encoding; } + + String decode(const char* data, size_t length); + String flush(); + + void setHintEncoding(const TextResourceDecoder* hintDecoder) + { + // hintEncoding is for use with autodetection, which should be + // only invoked when hintEncoding comes from auto-detection. + if (hintDecoder && hintDecoder->m_source == AutoDetectedEncoding) + m_hintEncoding = hintDecoder->encoding().name(); + } + + void useLenientXMLDecoding() { m_useLenientXMLDecoding = true; } + bool sawError() const { return m_sawError; } + +private: + TextResourceDecoder(const String& mimeType, const TextEncoding& defaultEncoding, + bool usesEncodingDetector); + + enum ContentType { PlainText, HTML, XML, CSS }; // PlainText only checks for BOM. + static ContentType determineContentType(const String& mimeType); + static const TextEncoding& defaultEncoding(ContentType, const TextEncoding& defaultEncoding); + + size_t checkForBOM(const char*, size_t); + bool checkForCSSCharset(const char*, size_t, bool& movedDataToBuffer); + bool checkForHeadCharset(const char*, size_t, bool& movedDataToBuffer); + bool checkForMetaCharset(const char*, size_t); + void detectJapaneseEncoding(const char*, size_t); + bool shouldAutoDetect() const; + + ContentType m_contentType; + TextEncoding m_encoding; + OwnPtr<TextCodec> m_codec; + EncodingSource m_source; + const char* m_hintEncoding; + Vector<char> m_buffer; + bool m_checkedForBOM; + bool m_checkedForCSSCharset; + bool m_checkedForHeadCharset; + bool m_useLenientXMLDecoding; // Don't stop on XML decoding errors. + bool m_sawError; + bool m_usesEncodingDetector; + + OwnPtr<HTMLMetaCharsetParser> m_charsetParser; +}; + +} + +#endif diff --git a/Source/WebCore/loader/ThreadableLoader.cpp b/Source/WebCore/loader/ThreadableLoader.cpp new file mode 100644 index 0000000..720ba4e --- /dev/null +++ b/Source/WebCore/loader/ThreadableLoader.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ThreadableLoader.h" + +#include "ScriptExecutionContext.h" +#include "Document.h" +#include "DocumentThreadableLoader.h" +#include "WorkerContext.h" +#include "WorkerRunLoop.h" +#include "WorkerThreadableLoader.h" + +namespace WebCore { + +PassRefPtr<ThreadableLoader> ThreadableLoader::create(ScriptExecutionContext* context, ThreadableLoaderClient* client, const ResourceRequest& request, const ThreadableLoaderOptions& options) +{ + ASSERT(client); + ASSERT(context); + +#if ENABLE(WORKERS) + if (context->isWorkerContext()) + return WorkerThreadableLoader::create(static_cast<WorkerContext*>(context), client, WorkerRunLoop::defaultMode(), request, options); +#endif // ENABLE(WORKERS) + + ASSERT(context->isDocument()); + return DocumentThreadableLoader::create(static_cast<Document*>(context), client, request, options); +} + +void ThreadableLoader::loadResourceSynchronously(ScriptExecutionContext* context, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options) +{ + ASSERT(context); + +#if ENABLE(WORKERS) + if (context->isWorkerContext()) { + WorkerThreadableLoader::loadResourceSynchronously(static_cast<WorkerContext*>(context), request, client, options); + return; + } +#endif // ENABLE(WORKERS) + + ASSERT(context->isDocument()); + DocumentThreadableLoader::loadResourceSynchronously(static_cast<Document*>(context), request, client, options); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/ThreadableLoader.h b/Source/WebCore/loader/ThreadableLoader.h new file mode 100644 index 0000000..f41a774 --- /dev/null +++ b/Source/WebCore/loader/ThreadableLoader.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ThreadableLoader_h +#define ThreadableLoader_h + +#include <wtf/Noncopyable.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + + class ResourceError; + class ResourceRequest; + class ResourceResponse; + class ScriptExecutionContext; + class ThreadableLoaderClient; + + enum StoredCredentials { + AllowStoredCredentials, + DoNotAllowStoredCredentials + }; + + enum CrossOriginRequestPolicy { + DenyCrossOriginRequests, + UseAccessControl, + AllowCrossOriginRequests + }; + + struct ThreadableLoaderOptions { + ThreadableLoaderOptions() : sendLoadCallbacks(false), sniffContent(false), allowCredentials(false), forcePreflight(false), crossOriginRequestPolicy(DenyCrossOriginRequests) { } + bool sendLoadCallbacks; + bool sniffContent; + bool allowCredentials; // Whether HTTP credentials and cookies are sent with the request. + bool forcePreflight; // If AccessControl is used, whether to force a preflight. + CrossOriginRequestPolicy crossOriginRequestPolicy; + }; + + // Useful for doing loader operations from any thread (not threadsafe, + // just able to run on threads other than the main thread). + class ThreadableLoader : public Noncopyable { + public: + static void loadResourceSynchronously(ScriptExecutionContext*, const ResourceRequest&, ThreadableLoaderClient&, const ThreadableLoaderOptions&); + static PassRefPtr<ThreadableLoader> create(ScriptExecutionContext*, ThreadableLoaderClient*, const ResourceRequest&, const ThreadableLoaderOptions&); + + virtual void cancel() = 0; + void ref() { refThreadableLoader(); } + void deref() { derefThreadableLoader(); } + + protected: + virtual ~ThreadableLoader() { } + virtual void refThreadableLoader() = 0; + virtual void derefThreadableLoader() = 0; + }; + +} // namespace WebCore + +#endif // ThreadableLoader_h diff --git a/Source/WebCore/loader/ThreadableLoaderClient.h b/Source/WebCore/loader/ThreadableLoaderClient.h new file mode 100644 index 0000000..bcf68be --- /dev/null +++ b/Source/WebCore/loader/ThreadableLoaderClient.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ThreadableLoaderClient_h +#define ThreadableLoaderClient_h + +#include <wtf/Noncopyable.h> + +namespace WebCore { + + class ResourceError; + class ResourceResponse; + + class ThreadableLoaderClient : public Noncopyable { + public: + virtual void didSendData(unsigned long long /*bytesSent*/, unsigned long long /*totalBytesToBeSent*/) { } + + virtual void didReceiveResponse(const ResourceResponse&) { } + virtual void didReceiveData(const char*, int /*lengthReceived*/) { } + virtual void didFinishLoading(unsigned long /*identifier*/) { } + virtual void didFail(const ResourceError&) { } + virtual void didFailRedirectCheck() { } + + virtual void didReceiveAuthenticationCancellation(const ResourceResponse&) { } + + protected: + virtual ~ThreadableLoaderClient() { } + }; + +} // namespace WebCore + +#endif // ThreadableLoaderClient_h diff --git a/Source/WebCore/loader/ThreadableLoaderClientWrapper.h b/Source/WebCore/loader/ThreadableLoaderClientWrapper.h new file mode 100644 index 0000000..d3c1a9f --- /dev/null +++ b/Source/WebCore/loader/ThreadableLoaderClientWrapper.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ThreadableLoaderClientWrapper_h +#define ThreadableLoaderClientWrapper_h + +#include "ThreadableLoaderClient.h" +#include <wtf/Noncopyable.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + + class ThreadableLoaderClientWrapper : public ThreadSafeShared<ThreadableLoaderClientWrapper> { + public: + static PassRefPtr<ThreadableLoaderClientWrapper> create(ThreadableLoaderClient* client) + { + return adoptRef(new ThreadableLoaderClientWrapper(client)); + } + + void clearClient() + { + m_done = true; + m_client = 0; + } + + bool done() const + { + return m_done; + } + + void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) + { + if (m_client) + m_client->didSendData(bytesSent, totalBytesToBeSent); + } + + void didReceiveResponse(const ResourceResponse& response) + { + if (m_client) + m_client->didReceiveResponse(response); + } + + void didReceiveData(const char* data, int lengthReceived) + { + if (m_client) + m_client->didReceiveData(data, lengthReceived); + } + + void didFinishLoading(unsigned long identifier) + { + m_done = true; + if (m_client) + m_client->didFinishLoading(identifier); + } + + void didFail(const ResourceError& error) + { + m_done = true; + if (m_client) + m_client->didFail(error); + } + + void didFailRedirectCheck() + { + m_done = true; + if (m_client) + m_client->didFailRedirectCheck(); + } + + void didReceiveAuthenticationCancellation(const ResourceResponse& response) + { + if (m_client) + m_client->didReceiveResponse(response); + } + + protected: + ThreadableLoaderClientWrapper(ThreadableLoaderClient* client) + : m_client(client) + , m_done(false) + { + } + + ThreadableLoaderClient* m_client; + bool m_done; + }; + +} // namespace WebCore + +#endif // ThreadableLoaderClientWrapper_h diff --git a/Source/WebCore/loader/WorkerThreadableLoader.cpp b/Source/WebCore/loader/WorkerThreadableLoader.cpp new file mode 100644 index 0000000..4d18c28 --- /dev/null +++ b/Source/WebCore/loader/WorkerThreadableLoader.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2009, 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(WORKERS) + +#include "WorkerThreadableLoader.h" + +#include "CrossThreadTask.h" +#include "ResourceError.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "ThreadableLoader.h" +#include "WorkerContext.h" +#include "WorkerLoaderProxy.h" +#include "WorkerThread.h" +#include <wtf/OwnPtr.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> + +using namespace std; + +namespace WebCore { + +static const char loadResourceSynchronouslyMode[] = "loadResourceSynchronouslyMode"; + +WorkerThreadableLoader::WorkerThreadableLoader(WorkerContext* workerContext, ThreadableLoaderClient* client, const String& taskMode, const ResourceRequest& request, const ThreadableLoaderOptions& options) + : m_workerContext(workerContext) + , m_workerClientWrapper(ThreadableLoaderClientWrapper::create(client)) + , m_bridge(*(new MainThreadBridge(m_workerClientWrapper, m_workerContext->thread()->workerLoaderProxy(), taskMode, request, options))) +{ +} + +WorkerThreadableLoader::~WorkerThreadableLoader() +{ + m_bridge.destroy(); +} + +void WorkerThreadableLoader::loadResourceSynchronously(WorkerContext* workerContext, const ResourceRequest& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options) +{ + WorkerRunLoop& runLoop = workerContext->thread()->runLoop(); + + // Create a unique mode just for this synchronous resource load. + String mode = loadResourceSynchronouslyMode; + mode.append(String::number(runLoop.createUniqueId())); + + RefPtr<WorkerThreadableLoader> loader = WorkerThreadableLoader::create(workerContext, &client, mode, request, options); + MessageQueueWaitResult result = MessageQueueMessageReceived; + while (!loader->done() && result != MessageQueueTerminated) + result = runLoop.runInMode(workerContext, mode); + + if (!loader->done() && result == MessageQueueTerminated) + loader->cancel(); +} + +void WorkerThreadableLoader::cancel() +{ + m_bridge.cancel(); +} + +WorkerThreadableLoader::MainThreadBridge::MainThreadBridge(PassRefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, WorkerLoaderProxy& loaderProxy, const String& taskMode, + const ResourceRequest& request, const ThreadableLoaderOptions& options) + : m_workerClientWrapper(workerClientWrapper) + , m_loaderProxy(loaderProxy) + , m_taskMode(taskMode.crossThreadString()) +{ + ASSERT(m_workerClientWrapper.get()); + m_loaderProxy.postTaskToLoader(createCallbackTask(&MainThreadBridge::mainThreadCreateLoader, this, request, options)); +} + +WorkerThreadableLoader::MainThreadBridge::~MainThreadBridge() +{ +} + +void WorkerThreadableLoader::MainThreadBridge::mainThreadCreateLoader(ScriptExecutionContext* context, MainThreadBridge* thisPtr, PassOwnPtr<CrossThreadResourceRequestData> requestData, ThreadableLoaderOptions options) +{ + ASSERT(isMainThread()); + ASSERT(context->isDocument()); + + // FIXME: the created loader has no knowledge of the origin of the worker doing the load request. + // Basically every setting done in SubresourceLoader::create (including the contents of addExtraFieldsToRequest) + // needs to be examined for how it should take into account a different originator. + OwnPtr<ResourceRequest> request(ResourceRequest::adopt(requestData)); + // FIXME: If the a site requests a local resource, then this will return a non-zero value but the sync path + // will return a 0 value. Either this should return 0 or the other code path should do a callback with + // a failure. + thisPtr->m_mainThreadLoader = ThreadableLoader::create(context, thisPtr, *request, options); + ASSERT(thisPtr->m_mainThreadLoader); +} + +void WorkerThreadableLoader::MainThreadBridge::mainThreadDestroy(ScriptExecutionContext* context, MainThreadBridge* thisPtr) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + delete thisPtr; +} + +void WorkerThreadableLoader::MainThreadBridge::destroy() +{ + // Ensure that no more client callbacks are done in the worker context's thread. + clearClientWrapper(); + + // "delete this" and m_mainThreadLoader::deref() on the worker object's thread. + m_loaderProxy.postTaskToLoader(createCallbackTask(&MainThreadBridge::mainThreadDestroy, this)); +} + +void WorkerThreadableLoader::MainThreadBridge::mainThreadCancel(ScriptExecutionContext* context, MainThreadBridge* thisPtr) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + + if (!thisPtr->m_mainThreadLoader) + return; + thisPtr->m_mainThreadLoader->cancel(); + thisPtr->m_mainThreadLoader = 0; +} + +void WorkerThreadableLoader::MainThreadBridge::cancel() +{ + m_loaderProxy.postTaskToLoader(createCallbackTask(&MainThreadBridge::mainThreadCancel, this)); + ThreadableLoaderClientWrapper* clientWrapper = m_workerClientWrapper.get(); + if (!clientWrapper->done()) { + // If the client hasn't reached a termination state, then transition it by sending a cancellation error. + // Note: no more client callbacks will be done after this method -- the clearClientWrapper() call ensures that. + ResourceError error(String(), 0, String(), String()); + error.setIsCancellation(true); + clientWrapper->didFail(error); + } + clearClientWrapper(); +} + +void WorkerThreadableLoader::MainThreadBridge::clearClientWrapper() +{ + m_workerClientWrapper->clearClient(); +} + +static void workerContextDidSendData(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didSendData(bytesSent, totalBytesToBeSent); +} + +void WorkerThreadableLoader::MainThreadBridge::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent) +{ + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidSendData, m_workerClientWrapper, bytesSent, totalBytesToBeSent), m_taskMode); +} + +static void workerContextDidReceiveResponse(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, PassOwnPtr<CrossThreadResourceResponseData> responseData) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + OwnPtr<ResourceResponse> response(ResourceResponse::adopt(responseData)); + workerClientWrapper->didReceiveResponse(*response); +} + +void WorkerThreadableLoader::MainThreadBridge::didReceiveResponse(const ResourceResponse& response) +{ + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidReceiveResponse, m_workerClientWrapper, response), m_taskMode); +} + +static void workerContextDidReceiveData(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, PassOwnPtr<Vector<char> > vectorData) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didReceiveData(vectorData->data(), vectorData->size()); +} + +void WorkerThreadableLoader::MainThreadBridge::didReceiveData(const char* data, int lengthReceived) +{ + OwnPtr<Vector<char> > vector = adoptPtr(new Vector<char>(lengthReceived)); // needs to be an OwnPtr for usage with createCallbackTask. + memcpy(vector->data(), data, lengthReceived); + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidReceiveData, m_workerClientWrapper, vector.release()), m_taskMode); +} + +static void workerContextDidFinishLoading(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, unsigned long identifier) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didFinishLoading(identifier); +} + +void WorkerThreadableLoader::MainThreadBridge::didFinishLoading(unsigned long identifier) +{ + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidFinishLoading, m_workerClientWrapper, identifier), m_taskMode); +} + +static void workerContextDidFail(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, const ResourceError& error) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didFail(error); +} + +void WorkerThreadableLoader::MainThreadBridge::didFail(const ResourceError& error) +{ + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidFail, m_workerClientWrapper, error), m_taskMode); +} + +static void workerContextDidFailRedirectCheck(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didFailRedirectCheck(); +} + +void WorkerThreadableLoader::MainThreadBridge::didFailRedirectCheck() +{ + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidFailRedirectCheck, m_workerClientWrapper), m_taskMode); +} + +static void workerContextDidReceiveAuthenticationCancellation(ScriptExecutionContext* context, RefPtr<ThreadableLoaderClientWrapper> workerClientWrapper, PassOwnPtr<CrossThreadResourceResponseData> responseData) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + OwnPtr<ResourceResponse> response(ResourceResponse::adopt(responseData)); + workerClientWrapper->didReceiveAuthenticationCancellation(*response); +} + +void WorkerThreadableLoader::MainThreadBridge::didReceiveAuthenticationCancellation(const ResourceResponse& response) +{ + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidReceiveAuthenticationCancellation, m_workerClientWrapper, response), m_taskMode); +} + +} // namespace WebCore + +#endif // ENABLE(WORKERS) diff --git a/Source/WebCore/loader/WorkerThreadableLoader.h b/Source/WebCore/loader/WorkerThreadableLoader.h new file mode 100644 index 0000000..81da2e0 --- /dev/null +++ b/Source/WebCore/loader/WorkerThreadableLoader.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef WorkerThreadableLoader_h +#define WorkerThreadableLoader_h + +#if ENABLE(WORKERS) + +#include "PlatformString.h" +#include "ThreadableLoader.h" +#include "ThreadableLoaderClient.h" +#include "ThreadableLoaderClientWrapper.h" + +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + + class ResourceError; + class ResourceRequest; + class WorkerContext; + class WorkerLoaderProxy; + struct CrossThreadResourceResponseData; + struct CrossThreadResourceRequestData; + + class WorkerThreadableLoader : public RefCounted<WorkerThreadableLoader>, public ThreadableLoader { + public: + static void loadResourceSynchronously(WorkerContext*, const ResourceRequest&, ThreadableLoaderClient&, const ThreadableLoaderOptions&); + static PassRefPtr<WorkerThreadableLoader> create(WorkerContext* workerContext, ThreadableLoaderClient* client, const String& taskMode, const ResourceRequest& request, const ThreadableLoaderOptions& options) + { + return adoptRef(new WorkerThreadableLoader(workerContext, client, taskMode, request, options)); + } + + ~WorkerThreadableLoader(); + + virtual void cancel(); + + bool done() const { return m_workerClientWrapper->done(); } + + using RefCounted<WorkerThreadableLoader>::ref; + using RefCounted<WorkerThreadableLoader>::deref; + + protected: + virtual void refThreadableLoader() { ref(); } + virtual void derefThreadableLoader() { deref(); } + + private: + // Creates a loader on the main thread and bridges communication between + // the main thread and the worker context's thread where WorkerThreadableLoader runs. + // + // Regarding the bridge and lifetimes of items used in callbacks, there are a few cases: + // + // all cases. All tasks posted from the worker context's thread are ok because + // the last task posted always is "mainThreadDestroy", so MainThreadBridge is + // around for all tasks that use it on the main thread. + // + // case 1. worker.terminate is called. + // In this case, no more tasks are posted from the worker object's thread to the worker + // context's thread -- WorkerContextProxy implementation enforces this. + // + // case 2. xhr gets aborted and the worker context continues running. + // The ThreadableLoaderClientWrapper has the underlying client cleared, so no more calls + // go through it. All tasks posted from the worker object's thread to the worker context's + // thread do "ThreadableLoaderClientWrapper::ref" (automatically inside of the cross thread copy + // done in createCallbackTask), so the ThreadableLoaderClientWrapper instance is there until all + // tasks are executed. + class MainThreadBridge : public ThreadableLoaderClient { + public: + // All executed on the worker context's thread. + MainThreadBridge(PassRefPtr<ThreadableLoaderClientWrapper>, WorkerLoaderProxy&, const String& taskMode, const ResourceRequest&, const ThreadableLoaderOptions&); + void cancel(); + void destroy(); + + private: + // Executed on the worker context's thread. + void clearClientWrapper(); + + // All executed on the main thread. + static void mainThreadDestroy(ScriptExecutionContext*, MainThreadBridge*); + ~MainThreadBridge(); + + static void mainThreadCreateLoader(ScriptExecutionContext*, MainThreadBridge*, PassOwnPtr<CrossThreadResourceRequestData>, ThreadableLoaderOptions); + static void mainThreadCancel(ScriptExecutionContext*, MainThreadBridge*); + virtual void didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent); + virtual void didReceiveResponse(const ResourceResponse&); + virtual void didReceiveData(const char*, int lengthReceived); + virtual void didFinishLoading(unsigned long identifier); + virtual void didFail(const ResourceError&); + virtual void didFailRedirectCheck(); + virtual void didReceiveAuthenticationCancellation(const ResourceResponse&); + + // Only to be used on the main thread. + RefPtr<ThreadableLoader> m_mainThreadLoader; + + // ThreadableLoaderClientWrapper is to be used on the worker context thread. + // The ref counting is done on either thread. + RefPtr<ThreadableLoaderClientWrapper> m_workerClientWrapper; + + // May be used on either thread. + WorkerLoaderProxy& m_loaderProxy; + + // For use on the main thread. + String m_taskMode; + }; + + WorkerThreadableLoader(WorkerContext*, ThreadableLoaderClient*, const String& taskMode, const ResourceRequest&, const ThreadableLoaderOptions&); + + RefPtr<WorkerContext> m_workerContext; + RefPtr<ThreadableLoaderClientWrapper> m_workerClientWrapper; + MainThreadBridge& m_bridge; + }; + +} // namespace WebCore + +#endif // ENABLE(WORKERS) + +#endif // WorkerThreadableLoader_h diff --git a/Source/WebCore/loader/appcache/ApplicationCache.cpp b/Source/WebCore/loader/appcache/ApplicationCache.cpp new file mode 100644 index 0000000..2a93765 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCache.cpp @@ -0,0 +1,204 @@ +/* + * 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 <wtf/text/CString.h> +#include <stdio.h> + +namespace WebCore { + +ApplicationCache::ApplicationCache() + : m_group(0) + , m_manifest(0) + , m_estimatedSizeInStorage(0) + , m_storageID(0) +{ +} + +ApplicationCache::~ApplicationCache() +{ + if (m_group && !m_group->isCopy()) + m_group->cacheDestroyed(this); +} + +void ApplicationCache::setGroup(ApplicationCacheGroup* group) +{ + ASSERT(!m_group || group == m_group); + m_group = group; +} + +bool ApplicationCache::isComplete() const +{ + return !m_group->cacheIsBeingUpdated(this); +} + +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()); + ASSERT(resource->type() & ApplicationCacheResource::Master); + + // Add the resource to the storage. + cacheStorage().store(resource.get(), this); + } + + m_estimatedSizeInStorage += resource->estimatedSizeInStorage(); + + 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); + + m_estimatedSizeInStorage -= it->second->estimatedSizeInStorage(); + + return type; +} + +ApplicationCacheResource* ApplicationCache::resourceForURL(const String& url) +{ + ASSERT(!KURL(ParsedURLString, url).hasFragmentIdentifier()); + return m_resources.get(url).get(); +} + +bool ApplicationCache::requestIsHTTPOrHTTPSGet(const ResourceRequest& request) +{ + if (!request.url().protocolInHTTPFamily()) + 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 0; + + KURL url(request.url()); + if (url.hasFragmentIdentifier()) + url.removeFragmentIdentifier(); + + return resourceForURL(url); +} + +void ApplicationCache::setOnlineWhitelist(const Vector<KURL>& onlineWhitelist) +{ + ASSERT(m_onlineWhitelist.isEmpty()); + m_onlineWhitelist = onlineWhitelist; +} + +bool ApplicationCache::isURLInOnlineWhitelist(const KURL& url) +{ + if (m_allowAllNetworkRequests) + return true; + + size_t whitelistSize = m_onlineWhitelist.size(); + for (size_t i = 0; i < whitelistSize; ++i) { + if (protocolHostAndPortAreEqual(url, m_onlineWhitelist[i]) && url.string().startsWith(m_onlineWhitelist[i].string())) + return true; + } + return false; +} + +void ApplicationCache::setFallbackURLs(const FallbackURLVector& fallbackURLs) +{ + ASSERT(m_fallbackURLs.isEmpty()); + m_fallbackURLs = fallbackURLs; +} + +bool ApplicationCache::urlMatchesFallbackNamespace(const KURL& url, KURL* fallbackURL) +{ + size_t fallbackCount = m_fallbackURLs.size(); + for (size_t i = 0; i < fallbackCount; ++i) { + if (protocolHostAndPortAreEqual(url, m_fallbackURLs[i].first) && url.string().startsWith(m_fallbackURLs[i].first.string())) { + if (fallbackURL) + *fallbackURL = m_fallbackURLs[i].second; + return true; + } + } + return false; +} + +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/Source/WebCore/loader/appcache/ApplicationCache.h b/Source/WebCore/loader/appcache/ApplicationCache.h new file mode 100644 index 0000000..f073499 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCache.h @@ -0,0 +1,116 @@ +/* + * 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 "PlatformString.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class ApplicationCacheGroup; +class ApplicationCacheResource; +class DocumentLoader; +class KURL; +class ResourceRequest; + +typedef Vector<std::pair<KURL, KURL> > FallbackURLVector; + +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; } + + bool isComplete() const; + + ApplicationCacheResource* resourceForRequest(const ResourceRequest&); + ApplicationCacheResource* resourceForURL(const String& url); + + void setAllowsAllNetworkRequests(bool value) { m_allowAllNetworkRequests = value; } + bool allowsAllNetworkRequests() const { return m_allowAllNetworkRequests; } + void setOnlineWhitelist(const Vector<KURL>& onlineWhitelist); + const Vector<KURL>& onlineWhitelist() const { return m_onlineWhitelist; } + bool isURLInOnlineWhitelist(const KURL&); // There is an entry in online whitelist that has the same origin as the resource's URL and that is a prefix match for the resource's URL. + + void setFallbackURLs(const FallbackURLVector&); + const FallbackURLVector& fallbackURLs() const { return m_fallbackURLs; } + bool urlMatchesFallbackNamespace(const KURL&, KURL* fallbackURL = 0); + +#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&); + + int64_t estimatedSizeInStorage() const { return m_estimatedSizeInStorage; } + +private: + ApplicationCache(); + + ApplicationCacheGroup* m_group; + ResourceMap m_resources; + ApplicationCacheResource* m_manifest; + + bool m_allowAllNetworkRequests; + Vector<KURL> m_onlineWhitelist; + FallbackURLVector m_fallbackURLs; + + // The total size of the resources belonging to this Application Cache instance. + // This is an estimation of the size this Application Cache occupies in the + // database file. + int64_t m_estimatedSizeInStorage; + + unsigned m_storageID; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCache_h diff --git a/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp new file mode 100644 index 0000000..6454b90 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheGroup.cpp @@ -0,0 +1,1181 @@ +/* + * Copyright (C) 2008, 2009, 2010 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 "ApplicationCacheHost.h" +#include "ApplicationCacheResource.h" +#include "ApplicationCacheStorage.h" +#include "Chrome.h" +#include "ChromeClient.h" +#include "DocumentLoader.h" +#include "DOMApplicationCache.h" +#include "DOMWindow.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "MainResourceLoader.h" +#include "ManifestParser.h" +#include "Page.h" +#include "SecurityOrigin.h" +#include "Settings.h" +#include <wtf/HashMap.h> + +#if ENABLE(INSPECTOR) +#include "InspectorApplicationCacheAgent.h" +#include "InspectorController.h" +#include "ProgressTracker.h" +#else +#include <wtf/UnusedParam.h> +#endif + +namespace WebCore { + +ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy) + : m_manifestURL(manifestURL) + , m_origin(SecurityOrigin::create(manifestURL)) + , m_updateStatus(Idle) + , m_downloadingPendingMasterResourceLoadersCount(0) + , m_progressTotal(0) + , m_progressDone(0) + , m_frame(0) + , m_storageID(0) + , m_isObsolete(false) + , m_completionType(None) + , m_isCopy(isCopy) + , m_calledReachedMaxAppCacheSize(false) + , m_loadedSize(0) + , m_availableSpaceInQuota(ApplicationCacheStorage::unknownQuota()) + , m_originQuotaReached(false) +{ +} + +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_pendingMasterResourceLoaders.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*) +{ + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) + return 0; + + KURL url(request.url()); + if (url.hasFragmentIdentifier()) + url.removeFragmentIdentifier(); + + if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(url)) { + ASSERT(group->newestCache()); + ASSERT(!group->isObsolete()); + + return group->newestCache(); + } + + return 0; +} + +ApplicationCache* ApplicationCacheGroup::fallbackCacheForMainRequest(const ResourceRequest& request, DocumentLoader*) +{ + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) + return 0; + + KURL url(request.url()); + if (url.hasFragmentIdentifier()) + url.removeFragmentIdentifier(); + + if (ApplicationCacheGroup* group = cacheStorage().fallbackCacheGroupForURL(url)) { + ASSERT(group->newestCache()); + ASSERT(!group->isObsolete()); + + return group->newestCache(); + } + + return 0; +} + +void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& passedManifestURL) +{ + ASSERT(frame && frame->page()); + + if (!frame->settings()->offlineWebApplicationCacheEnabled()) + return; + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + ASSERT(!documentLoader->applicationCacheHost()->applicationCache()); + + if (passedManifestURL.isNull()) { + selectCacheWithoutManifestURL(frame); + return; + } + + KURL manifestURL(passedManifestURL); + if (manifestURL.hasFragmentIdentifier()) + manifestURL.removeFragmentIdentifier(); + + ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache(); + + if (mainResourceCache) { + if (manifestURL == mainResourceCache->group()->m_manifestURL) { + // The cache may have gotten obsoleted after we've loaded from it, but before we parsed the document and saw cache manifest. + if (mainResourceCache->group()->isObsolete()) + return; + mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache); + mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext); + } else { + // The main resource was loaded from cache, so the cache must have an entry for it. Mark it as foreign. + KURL resourceURL(documentLoader->responseURL()); + if (resourceURL.hasFragmentIdentifier()) + resourceURL.removeFragmentIdentifier(); + ApplicationCacheResource* resource = mainResourceCache->resourceForURL(resourceURL); + bool inStorage = resource->storageID(); + resource->addType(ApplicationCacheResource::Foreign); + if (inStorage) + cacheStorage().storeUpdatedType(resource, mainResourceCache); + + // Restart the current navigation from the top of the navigation algorithm, undoing any changes that were made + // as part of the initial load. + // The navigation will not result in the same resource being loaded, because "foreign" entries are never picked during navigation. + frame->navigationScheduler()->scheduleLocationChange(frame->document()->securityOrigin(), documentLoader->url(), frame->loader()->referrer(), true); + } + + 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)) + return; + + // Check that the resource URL has the same scheme/host/port as the manifest URL. + if (!protocolHostAndPortAreEqual(manifestURL, request.url())) + return; + + // Don't change anything on disk if private browsing is enabled. + if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) { + postListenerTask(ApplicationCacheHost::CHECKING_EVENT, documentLoader); + postListenerTask(ApplicationCacheHost::ERROR_EVENT, documentLoader); + return; + } + + ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL); + + documentLoader->applicationCacheHost()->setCandidateApplicationCacheGroup(group); + group->m_pendingMasterResourceLoaders.add(documentLoader); + group->m_downloadingPendingMasterResourceLoadersCount++; + + ASSERT(!group->m_cacheBeingUpdated || group->m_updateStatus != Idle); + group->update(frame, ApplicationCacheUpdateWithBrowsingContext); +} + +void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame) +{ + if (!frame->settings()->offlineWebApplicationCacheEnabled()) + return; + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + ASSERT(!documentLoader->applicationCacheHost()->applicationCache()); + + ApplicationCache* mainResourceCache = documentLoader->applicationCacheHost()->mainResourceApplicationCache(); + + if (mainResourceCache) { + mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache); + mainResourceCache->group()->update(frame, ApplicationCacheUpdateWithBrowsingContext); + } +} + +void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader) +{ + ASSERT(m_pendingMasterResourceLoaders.contains(loader)); + ASSERT(m_completionType == None || m_pendingEntries.isEmpty()); + KURL url = loader->url(); + if (url.hasFragmentIdentifier()) + url.removeFragmentIdentifier(); + + switch (m_completionType) { + case None: + // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later. + return; + case NoUpdate: + ASSERT(!m_cacheBeingUpdated); + associateDocumentLoaderWithCache(loader, m_newestCache.get()); + + if (ApplicationCacheResource* resource = m_newestCache->resourceForURL(url)) { + if (!(resource->type() & ApplicationCacheResource::Master)) { + resource->addType(ApplicationCacheResource::Master); + ASSERT(!resource->storageID()); + } + } else + m_newestCache->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData())); + + break; + case Failure: + // Cache update has been a failure, so there is no reason to keep the document associated with the incomplete cache + // (its main resource was not cached yet, so it is likely that the application changed significantly server-side). + ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading(). + loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too. + m_associatedDocumentLoaders.remove(loader); + postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader); + break; + case Completed: + ASSERT(m_associatedDocumentLoaders.contains(loader)); + + if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) { + if (!(resource->type() & ApplicationCacheResource::Master)) { + resource->addType(ApplicationCacheResource::Master); + ASSERT(!resource->storageID()); + } + } else + m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Master, loader->mainResourceData())); + // The "cached" event will be posted to all associated documents once update is complete. + break; + } + + m_downloadingPendingMasterResourceLoadersCount--; + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader) +{ + ASSERT(m_pendingMasterResourceLoaders.contains(loader)); + ASSERT(m_completionType == None || m_pendingEntries.isEmpty()); + + switch (m_completionType) { + case None: + // The main resource finished loading before the manifest was ready. It will be handled via dispatchMainResources() later. + return; + case NoUpdate: + ASSERT(!m_cacheBeingUpdated); + + // The manifest didn't change, and we have a relevant cache - but the main resource download failed mid-way, so it cannot be stored to the cache, + // and the loader does not get associated to it. If there are other main resources being downloaded for this cache group, they may still succeed. + postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader); + + break; + case Failure: + // Cache update failed, too. + ASSERT(!m_cacheBeingUpdated); // Already cleared out by stopLoading(). + ASSERT(!loader->applicationCacheHost()->applicationCache() || loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated); + + loader->applicationCacheHost()->setApplicationCache(0); // Will unset candidate, too. + m_associatedDocumentLoaders.remove(loader); + postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader); + break; + case Completed: + // The cache manifest didn't list this main resource, and all cache entries were already updated successfully - but the main resource failed to load, + // so it cannot be stored to the cache. If there are other main resources being downloaded for this cache group, they may still succeed. + ASSERT(m_associatedDocumentLoaders.contains(loader)); + ASSERT(loader->applicationCacheHost()->applicationCache() == m_cacheBeingUpdated); + ASSERT(!loader->applicationCacheHost()->candidateApplicationCacheGroup()); + m_associatedDocumentLoaders.remove(loader); + loader->applicationCacheHost()->setApplicationCache(0); + + postListenerTask(ApplicationCacheHost::ERROR_EVENT, loader); + + break; + } + + m_downloadingPendingMasterResourceLoadersCount--; + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::stopLoading() +{ + if (m_manifestHandle) { + ASSERT(!m_currentHandle); + + 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; + m_pendingEntries.clear(); +} + +void ApplicationCacheGroup::disassociateDocumentLoader(DocumentLoader* loader) +{ + HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader); + if (it != m_associatedDocumentLoaders.end()) + m_associatedDocumentLoaders.remove(it); + + m_pendingMasterResourceLoaders.remove(loader); + + loader->applicationCacheHost()->setApplicationCache(0); // Will set candidate to 0, too. + + if (!m_associatedDocumentLoaders.isEmpty() || !m_pendingMasterResourceLoaders.isEmpty()) + return; + + if (m_caches.isEmpty()) { + // There is an initial cache attempt in progress. + ASSERT(!m_newestCache); + // Delete ourselves, causing the cache attempt to be stopped. + delete this; + return; + } + + ASSERT(m_caches.contains(m_newestCache.get())); + + // Release our reference to the newest cache. This could cause us to be deleted. + // Any ongoing updates will be stopped from destructor. + m_newestCache.release(); +} + +void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache) +{ + if (!m_caches.contains(cache)) + return; + + m_caches.remove(cache); + + if (m_caches.isEmpty()) { + ASSERT(m_associatedDocumentLoaders.isEmpty()); + ASSERT(m_pendingMasterResourceLoaders.isEmpty()); + delete this; + } +} + +void ApplicationCacheGroup::stopLoadingInFrame(Frame* frame) +{ + if (frame != m_frame) + return; + + stopLoading(); +} + +#if ENABLE(INSPECTOR) +static void inspectorUpdateApplicationCacheStatus(Frame* frame) +{ + if (!frame) + return; + + if (Page *page = frame->page()) { + if (InspectorApplicationCacheAgent* applicationCacheAgent = page->inspectorController()->applicationCacheAgent()) { + ApplicationCacheHost::Status status = frame->loader()->documentLoader()->applicationCacheHost()->status(); + applicationCacheAgent->updateApplicationCacheStatus(status); + } + } +} +#endif + +void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache) +{ + m_newestCache = newestCache; + + m_caches.add(m_newestCache.get()); + m_newestCache->setGroup(this); +#if ENABLE(INSPECTOR) + inspectorUpdateApplicationCacheStatus(m_frame); +#endif +} + +void ApplicationCacheGroup::makeObsolete() +{ + if (isObsolete()) + return; + + m_isObsolete = true; + cacheStorage().cacheGroupMadeObsolete(this); + ASSERT(!m_storageID); +#if ENABLE(INSPECTOR) + inspectorUpdateApplicationCacheStatus(m_frame); +#endif +} + +void ApplicationCacheGroup::update(Frame* frame, ApplicationCacheUpdateOption updateOption) +{ + if (m_updateStatus == Checking || m_updateStatus == Downloading) { + if (updateOption == ApplicationCacheUpdateWithBrowsingContext) { + postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader()); + if (m_updateStatus == Downloading) + postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, frame->loader()->documentLoader()); + } + return; + } + + // Don't change anything on disk if private browsing is enabled. + if (!frame->settings() || frame->settings()->privateBrowsingEnabled()) { + ASSERT(m_pendingMasterResourceLoaders.isEmpty()); + ASSERT(m_pendingEntries.isEmpty()); + ASSERT(!m_cacheBeingUpdated); + postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader()); + postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, frame->loader()->documentLoader()); + return; + } + + ASSERT(!m_frame); + m_frame = frame; + + setUpdateStatus(Checking); + + postListenerTask(ApplicationCacheHost::CHECKING_EVENT, m_associatedDocumentLoaders); + if (!m_newestCache) { + ASSERT(updateOption == ApplicationCacheUpdateWithBrowsingContext); + postListenerTask(ApplicationCacheHost::CHECKING_EVENT, frame->loader()->documentLoader()); + } + + ASSERT(!m_manifestHandle); + ASSERT(!m_manifestResource); + ASSERT(m_completionType == None); + + // FIXME: Handle defer loading + m_manifestHandle = createResourceHandle(m_manifestURL, m_newestCache ? m_newestCache->manifestResource() : 0); +} + +PassRefPtr<ResourceHandle> ApplicationCacheGroup::createResourceHandle(const KURL& url, ApplicationCacheResource* newestCachedResource) +{ + ResourceRequest request(url); + m_frame->loader()->applyUserAgent(request); + request.setHTTPHeaderField("Cache-Control", "max-age=0"); + + if (newestCachedResource) { + const String& lastModified = newestCachedResource->response().httpHeaderField("Last-Modified"); + const String& eTag = newestCachedResource->response().httpHeaderField("ETag"); + if (!lastModified.isEmpty() || !eTag.isEmpty()) { + if (!lastModified.isEmpty()) + request.setHTTPHeaderField("If-Modified-Since", lastModified); + if (!eTag.isEmpty()) + request.setHTTPHeaderField("If-None-Match", eTag); + } + } + + RefPtr<ResourceHandle> handle = ResourceHandle::create(m_frame->loader()->networkingContext(), request, this, false, true); +#if ENABLE(INSPECTOR) + // Because willSendRequest only gets called during redirects, we initialize + // the identifier and the first willSendRequest here. + m_currentResourceIdentifier = m_frame->page()->progress()->createUniqueIdentifier(); + if (Page* page = m_frame->page()) { + InspectorController* inspectorController = page->inspectorController(); + inspectorController->identifierForInitialRequest(m_currentResourceIdentifier, m_frame->loader()->documentLoader(), handle->firstRequest()); + ResourceResponse redirectResponse = ResourceResponse(); + inspectorController->willSendRequest(m_currentResourceIdentifier, request, redirectResponse); + } +#endif + return handle; +} + +#if ENABLE(INSPECTOR) +void ApplicationCacheGroup::willSendRequest(ResourceHandle*, ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + // This only gets called by ResourceHandleMac if there is a redirect. + if (Page* page = m_frame->page()) + page->inspectorController()->willSendRequest(m_currentResourceIdentifier, request, redirectResponse); +} +#endif + +void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response) +{ +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) { + if (handle == m_manifestHandle) { + if (InspectorApplicationCacheAgent* applicationCacheAgent = page->inspectorController()->applicationCacheAgent()) + applicationCacheAgent->didReceiveManifestResponse(m_currentResourceIdentifier, response); + } else + page->inspectorController()->didReceiveResponse(m_currentResourceIdentifier, m_frame->loader()->documentLoader(), response); + } +#endif + + if (handle == m_manifestHandle) { + didReceiveManifestResponse(response); + return; + } + + ASSERT(handle == m_currentHandle); + + KURL url(handle->firstRequest().url()); + if (url.hasFragmentIdentifier()) + url.removeFragmentIdentifier(); + + 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 master resources delivered here. + if (!m_newestCache) + ASSERT(!(type & ApplicationCacheResource::Master)); + + if (m_newestCache && response.httpStatusCode() == 304) { // Not modified. + ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url); + if (newestCachedResource) { + m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data())); + m_pendingEntries.remove(m_currentHandle->firstRequest().url()); + m_currentHandle->cancel(); + m_currentHandle = 0; + // Load the next resource, if any. + startLoadingEntry(); + return; + } + // The server could return 304 for an unconditional request - in this case, we handle the response as a normal error. + } + + if (response.httpStatusCode() / 100 != 2 || response.url() != m_currentHandle->firstRequest().url()) { + if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) { + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); + } else if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) { + // Skip this resource. It is dropped from the cache. + m_currentHandle->cancel(); + m_currentHandle = 0; + m_pendingEntries.remove(url); + // Load the next resource, if any. + startLoadingEntry(); + } else { + // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act + // as if that was the fetched resource, ignoring the resource obtained from the network. + ASSERT(m_newestCache); + ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(handle->firstRequest().url()); + ASSERT(newestCachedResource); + m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data())); + m_pendingEntries.remove(m_currentHandle->firstRequest().url()); + m_currentHandle->cancel(); + m_currentHandle = 0; + // Load the next resource, if any. + startLoadingEntry(); + } + return; + } + + m_currentResource = ApplicationCacheResource::create(url, response, type); +} + +void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived) +{ +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didReceiveContentLength(m_currentResourceIdentifier, lengthReceived); +#else + UNUSED_PARAM(lengthReceived); +#endif + + if (handle == m_manifestHandle) { + didReceiveManifestData(data, length); + return; + } + + ASSERT(handle == m_currentHandle); + + ASSERT(m_currentResource); + m_currentResource->data()->append(data, length); + + m_loadedSize += length; +} + +void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle, double finishTime) +{ +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didFinishLoading(m_currentResourceIdentifier, finishTime); +#endif + + if (handle == m_manifestHandle) { + didFinishLoadingManifest(); + return; + } + + // After finishing the loading of any resource, we check if it will + // fit in our last known quota limit. + if (m_availableSpaceInQuota == ApplicationCacheStorage::unknownQuota()) { + // Failed to determine what is left in the quota. Fallback to allowing anything. + if (!cacheStorage().remainingSizeForOriginExcludingCache(m_origin.get(), m_newestCache.get(), m_availableSpaceInQuota)) + m_availableSpaceInQuota = ApplicationCacheStorage::noQuota(); + } + + // Check each resource, as it loads, to see if it would fit in our + // idea of the available quota space. + if (m_availableSpaceInQuota < m_loadedSize) { + m_currentResource = 0; + cacheUpdateFailedDueToOriginQuota(); + return; + } + + ASSERT(m_currentHandle == handle); + ASSERT(m_pendingEntries.contains(handle->firstRequest().url())); + + m_pendingEntries.remove(handle->firstRequest().url()); + + ASSERT(m_cacheBeingUpdated); + + m_cacheBeingUpdated->addResource(m_currentResource.release()); + m_currentHandle = 0; + + // Load the next resource, if any. + startLoadingEntry(); +} + +void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError& error) +{ +#if ENABLE(INSPECTOR) + if (Page* page = m_frame->page()) + page->inspectorController()->didFailLoading(m_currentResourceIdentifier, error); +#else + UNUSED_PARAM(error); +#endif + + if (handle == m_manifestHandle) { + cacheUpdateFailed(); + return; + } + + unsigned type = m_currentResource ? m_currentResource->type() : m_pendingEntries.get(handle->firstRequest().url()); + KURL url(handle->firstRequest().url()); + if (url.hasFragmentIdentifier()) + url.removeFragmentIdentifier(); + + ASSERT(!m_currentResource || !m_pendingEntries.contains(url)); + m_currentResource = 0; + m_pendingEntries.remove(url); + + if ((type & ApplicationCacheResource::Explicit) || (type & ApplicationCacheResource::Fallback)) { + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); + } else { + // Copy the resource and its metadata from the newest application cache in cache group whose completeness flag is complete, and act + // as if that was the fetched resource, ignoring the resource obtained from the network. + ASSERT(m_newestCache); + ApplicationCacheResource* newestCachedResource = m_newestCache->resourceForURL(url); + ASSERT(newestCachedResource); + m_cacheBeingUpdated->addResource(ApplicationCacheResource::create(url, newestCachedResource->response(), type, newestCachedResource->data())); + // Load the next resource, if any. + startLoadingEntry(); + } +} + +void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response) +{ + ASSERT(!m_manifestResource); + ASSERT(m_manifestHandle); + + if (response.httpStatusCode() == 404 || response.httpStatusCode() == 410) { + manifestNotFound(); + return; + } + + if (response.httpStatusCode() == 304) + return; + + if (response.httpStatusCode() / 100 != 2 || response.url() != m_manifestHandle->firstRequest().url() || !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) { + cacheUpdateFailed(); + return; + } + + m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->firstRequest().url(), response, ApplicationCacheResource::Manifest); +} + +void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length) +{ + if (m_manifestResource) + m_manifestResource->data()->append(data, length); +} + +void ApplicationCacheGroup::didFinishLoadingManifest() +{ + bool isUpgradeAttempt = m_newestCache; + + if (!isUpgradeAttempt && !m_manifestResource) { + // The server returned 304 Not Modified even though we didn't send a conditional request. + cacheUpdateFailed(); + return; + } + + m_manifestHandle = 0; + + // Check if the manifest was not modified. + if (isUpgradeAttempt) { + ApplicationCacheResource* newestManifest = m_newestCache->manifestResource(); + ASSERT(newestManifest); + + if (!m_manifestResource || // The resource will be null if HTTP response was 304 Not Modified. + (newestManifest->data()->size() == m_manifestResource->data()->size() && !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size()))) { + + m_completionType = NoUpdate; + m_manifestResource = 0; + deliverDelayedMainResources(); + + return; + } + } + + Manifest manifest; + if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) { + cacheUpdateFailed(); + return; + } + + ASSERT(!m_cacheBeingUpdated); + m_cacheBeingUpdated = ApplicationCache::create(); + m_cacheBeingUpdated->setGroup(this); + + HashSet<DocumentLoader*>::const_iterator masterEnd = m_pendingMasterResourceLoaders.end(); + for (HashSet<DocumentLoader*>::const_iterator iter = m_pendingMasterResourceLoaders.begin(); iter != masterEnd; ++iter) + associateDocumentLoaderWithCache(*iter, m_cacheBeingUpdated.get()); + + // We have the manifest, now download the resources. + setUpdateStatus(Downloading); + + postListenerTask(ApplicationCacheHost::DOWNLOADING_EVENT, m_associatedDocumentLoaders); + + ASSERT(m_pendingEntries.isEmpty()); + + if (isUpgradeAttempt) { + 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::Master) + 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); + + size_t fallbackCount = manifest.fallbackURLs.size(); + for (size_t i = 0; i < fallbackCount; ++i) + addEntry(manifest.fallbackURLs[i].second, ApplicationCacheResource::Fallback); + + m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs); + m_cacheBeingUpdated->setFallbackURLs(manifest.fallbackURLs); + m_cacheBeingUpdated->setAllowsAllNetworkRequests(manifest.allowAllNetworkRequests); + + m_progressTotal = m_pendingEntries.size(); + m_progressDone = 0; + + startLoadingEntry(); +} + +void ApplicationCacheGroup::didReachMaxAppCacheSize() +{ + ASSERT(m_frame); + ASSERT(m_cacheBeingUpdated); + m_frame->page()->chrome()->client()->reachedMaxAppCacheSize(cacheStorage().spaceNeeded(m_cacheBeingUpdated->estimatedSizeInStorage())); + m_calledReachedMaxAppCacheSize = true; + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::didReachOriginQuota(PassRefPtr<Frame> frame) +{ + // Inform the client the origin quota has been reached, + // they may decide to increase the quota. + frame->page()->chrome()->client()->reachedApplicationCacheOriginQuota(m_origin.get()); +} + +void ApplicationCacheGroup::cacheUpdateFailed() +{ + stopLoading(); + m_manifestResource = 0; + + // Wait for master resource loads to finish. + m_completionType = Failure; + deliverDelayedMainResources(); +} + +void ApplicationCacheGroup::cacheUpdateFailedDueToOriginQuota() +{ + if (!m_originQuotaReached) { + m_originQuotaReached = true; + scheduleReachedOriginQuotaCallback(); + } + + cacheUpdateFailed(); +} + +void ApplicationCacheGroup::manifestNotFound() +{ + makeObsolete(); + + postListenerTask(ApplicationCacheHost::OBSOLETE_EVENT, m_associatedDocumentLoaders); + postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_pendingMasterResourceLoaders); + + stopLoading(); + + ASSERT(m_pendingEntries.isEmpty()); + m_manifestResource = 0; + + while (!m_pendingMasterResourceLoaders.isEmpty()) { + HashSet<DocumentLoader*>::iterator it = m_pendingMasterResourceLoaders.begin(); + + ASSERT((*it)->applicationCacheHost()->candidateApplicationCacheGroup() == this); + ASSERT(!(*it)->applicationCacheHost()->applicationCache()); + (*it)->applicationCacheHost()->setCandidateApplicationCacheGroup(0); + m_pendingMasterResourceLoaders.remove(it); + } + + m_downloadingPendingMasterResourceLoadersCount = 0; + setUpdateStatus(Idle); + m_frame = 0; + + if (m_caches.isEmpty()) { + ASSERT(m_associatedDocumentLoaders.isEmpty()); + ASSERT(!m_cacheBeingUpdated); + delete this; + } +} + +void ApplicationCacheGroup::checkIfLoadIsComplete() +{ + if (m_manifestHandle || !m_pendingEntries.isEmpty() || m_downloadingPendingMasterResourceLoadersCount) + return; + + // We're done, all resources have finished downloading (successfully or not). + + bool isUpgradeAttempt = m_newestCache; + + switch (m_completionType) { + case None: + ASSERT_NOT_REACHED(); + return; + case NoUpdate: + ASSERT(isUpgradeAttempt); + ASSERT(!m_cacheBeingUpdated); + + // The storage could have been manually emptied by the user. + if (!m_storageID) + cacheStorage().storeNewestCache(this); + + postListenerTask(ApplicationCacheHost::NOUPDATE_EVENT, m_associatedDocumentLoaders); + break; + case Failure: + ASSERT(!m_cacheBeingUpdated); + postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders); + if (m_caches.isEmpty()) { + ASSERT(m_associatedDocumentLoaders.isEmpty()); + delete this; + return; + } + break; + case Completed: { + // FIXME: Fetch the resource from manifest URL again, and check whether it is identical to the one used for update (in case the application was upgraded server-side in the meanwhile). (<rdar://problem/6467625>) + + ASSERT(m_cacheBeingUpdated); + if (m_manifestResource) + m_cacheBeingUpdated->setManifestResource(m_manifestResource.release()); + else { + // We can get here as a result of retrying the Complete step, following + // a failure of the cache storage to save the newest cache due to hitting + // the maximum size. In such a case, m_manifestResource may be 0, as + // the manifest was already set on the newest cache object. + ASSERT(cacheStorage().isMaximumSizeReached() && m_calledReachedMaxAppCacheSize); + } + + ApplicationCacheStorage::FailureReason failureReason; + RefPtr<ApplicationCache> oldNewestCache = (m_newestCache == m_cacheBeingUpdated) ? 0 : m_newestCache; + setNewestCache(m_cacheBeingUpdated.release()); + if (cacheStorage().storeNewestCache(this, oldNewestCache.get(), failureReason)) { + // New cache stored, now remove the old cache. + if (oldNewestCache) + cacheStorage().remove(oldNewestCache.get()); + + // Fire the final progress event. + ASSERT(m_progressDone == m_progressTotal); + postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders); + + // Fire the success event. + postListenerTask(isUpgradeAttempt ? ApplicationCacheHost::UPDATEREADY_EVENT : ApplicationCacheHost::CACHED_EVENT, m_associatedDocumentLoaders); + // It is clear that the origin quota was not reached, so clear the flag if it was set. + m_originQuotaReached = false; + } else { + if (failureReason == ApplicationCacheStorage::OriginQuotaReached) { + // We ran out of space for this origin. Roll back to previous state. + if (oldNewestCache) + setNewestCache(oldNewestCache.release()); + cacheUpdateFailedDueToOriginQuota(); + return; + } + + if (failureReason == ApplicationCacheStorage::TotalQuotaReached && !m_calledReachedMaxAppCacheSize) { + // We ran out of space. All the changes in the cache storage have + // been rolled back. We roll back to the previous state in here, + // as well, call the chrome client asynchronously and retry to + // save the new cache. + + // Save a reference to the new cache. + m_cacheBeingUpdated = m_newestCache.release(); + if (oldNewestCache) { + // Reinstate the oldNewestCache. + setNewestCache(oldNewestCache.release()); + } + scheduleReachedMaxAppCacheSizeCallback(); + return; + } + + // Run the "cache failure steps" + // Fire the error events to all pending master entries, as well any other cache hosts + // currently associated with a cache in this group. + postListenerTask(ApplicationCacheHost::ERROR_EVENT, m_associatedDocumentLoaders); + // Disassociate the pending master entries from the failed new cache. Note that + // all other loaders in the m_associatedDocumentLoaders are still associated with + // some other cache in this group. They are not associated with the failed new cache. + + // Need to copy loaders, because the cache group may be destroyed at the end of iteration. + Vector<DocumentLoader*> loaders; + copyToVector(m_pendingMasterResourceLoaders, loaders); + size_t count = loaders.size(); + for (size_t i = 0; i != count; ++i) + disassociateDocumentLoader(loaders[i]); // This can delete this group. + + // Reinstate the oldNewestCache, if there was one. + if (oldNewestCache) { + // This will discard the failed new cache. + setNewestCache(oldNewestCache.release()); + } else { + // We must have been deleted by the last call to disassociateDocumentLoader(). + return; + } + } + break; + } + } + + // Empty cache group's list of pending master entries. + m_pendingMasterResourceLoaders.clear(); + m_completionType = None; + setUpdateStatus(Idle); + m_frame = 0; + m_loadedSize = 0; + m_availableSpaceInQuota = ApplicationCacheStorage::unknownQuota(); + m_calledReachedMaxAppCacheSize = false; +} + +void ApplicationCacheGroup::startLoadingEntry() +{ + ASSERT(m_cacheBeingUpdated); + + if (m_pendingEntries.isEmpty()) { + m_completionType = Completed; + deliverDelayedMainResources(); + return; + } + + EntryMap::const_iterator it = m_pendingEntries.begin(); + + postListenerTask(ApplicationCacheHost::PROGRESS_EVENT, m_progressTotal, m_progressDone, m_associatedDocumentLoaders); + m_progressDone++; + + ASSERT(!m_currentHandle); + + m_currentHandle = createResourceHandle(KURL(ParsedURLString, it->first), m_newestCache ? m_newestCache->resourceForURL(it->first) : 0); +} + +void ApplicationCacheGroup::deliverDelayedMainResources() +{ + // Need to copy loaders, because the cache group may be destroyed at the end of iteration. + Vector<DocumentLoader*> loaders; + copyToVector(m_pendingMasterResourceLoaders, loaders); + size_t count = loaders.size(); + for (size_t i = 0; i != count; ++i) { + DocumentLoader* loader = loaders[i]; + if (loader->isLoadingMainResource()) + continue; + + const ResourceError& error = loader->mainDocumentError(); + if (error.isNull()) + finishedLoadingMainResource(loader); + else + failedLoadingMainResource(loader); + } + if (!count) + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::addEntry(const String& url, unsigned type) +{ + ASSERT(m_cacheBeingUpdated); + ASSERT(!KURL(ParsedURLString, url).hasFragmentIdentifier()); + + // Don't add the URL if we already have an master resource in the cache + // (i.e., the main resource finished loading before the manifest). + if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) { + ASSERT(resource->type() & ApplicationCacheResource::Master); + ASSERT(!m_frame->loader()->documentLoader()->isLoadingMainResource()); + + resource->addType(type); + return; + } + + // Don't add the URL if it's the same as the manifest URL. + ASSERT(m_manifestResource); + if (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) +{ + // If teardown started already, revive the group. + if (!m_newestCache && !m_cacheBeingUpdated) + m_newestCache = cache; + + ASSERT(!m_isObsolete); + + loader->applicationCacheHost()->setApplicationCache(cache); + + ASSERT(!m_associatedDocumentLoaders.contains(loader)); + m_associatedDocumentLoaders.add(loader); +} + +class ChromeClientCallbackTimer: public TimerBase { +public: + ChromeClientCallbackTimer(ApplicationCacheGroup* cacheGroup) + : m_cacheGroup(cacheGroup) + { + } + +private: + virtual void fired() + { + m_cacheGroup->didReachMaxAppCacheSize(); + delete this; + } + // Note that there is no need to use a RefPtr here. The ApplicationCacheGroup instance is guaranteed + // to be alive when the timer fires since invoking the ChromeClient callback is part of its normal + // update machinery and nothing can yet cause it to get deleted. + ApplicationCacheGroup* m_cacheGroup; +}; + +void ApplicationCacheGroup::scheduleReachedMaxAppCacheSizeCallback() +{ + ASSERT(isMainThread()); + ChromeClientCallbackTimer* timer = new ChromeClientCallbackTimer(this); + timer->startOneShot(0); + // The timer will delete itself once it fires. +} + +void ApplicationCacheGroup::scheduleReachedOriginQuotaCallback() +{ + // FIXME: it might be nice to run this asynchronously, because there is no return value to wait for. + didReachOriginQuota(m_frame); +} + +class CallCacheListenerTask : public ScriptExecutionContext::Task { +public: + static PassOwnPtr<CallCacheListenerTask> create(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone) + { + return adoptPtr(new CallCacheListenerTask(loader, eventID, progressTotal, progressDone)); + } + + virtual void performTask(ScriptExecutionContext* context) + { + + ASSERT_UNUSED(context, context->isDocument()); + Frame* frame = m_documentLoader->frame(); + if (!frame) + return; + + ASSERT(frame->loader()->documentLoader() == m_documentLoader.get()); + + m_documentLoader->applicationCacheHost()->notifyDOMApplicationCache(m_eventID, m_progressTotal, m_progressDone); + } + +private: + CallCacheListenerTask(PassRefPtr<DocumentLoader> loader, ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone) + : m_documentLoader(loader) + , m_eventID(eventID) + , m_progressTotal(progressTotal) + , m_progressDone(progressDone) + { + } + + RefPtr<DocumentLoader> m_documentLoader; + ApplicationCacheHost::EventID m_eventID; + int m_progressTotal; + int m_progressDone; +}; + +void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, const HashSet<DocumentLoader*>& loaderSet) +{ + HashSet<DocumentLoader*>::const_iterator loaderSetEnd = loaderSet.end(); + for (HashSet<DocumentLoader*>::const_iterator iter = loaderSet.begin(); iter != loaderSetEnd; ++iter) + postListenerTask(eventID, progressTotal, progressDone, *iter); +} + +void ApplicationCacheGroup::postListenerTask(ApplicationCacheHost::EventID eventID, int progressTotal, int progressDone, DocumentLoader* loader) +{ + Frame* frame = loader->frame(); + if (!frame) + return; + + ASSERT(frame->loader()->documentLoader() == loader); + + frame->document()->postTask(CallCacheListenerTask::create(loader, eventID, progressTotal, progressDone)); +} + +void ApplicationCacheGroup::setUpdateStatus(UpdateStatus status) +{ + m_updateStatus = status; +#if ENABLE(INSPECTOR) + inspectorUpdateApplicationCacheStatus(m_frame); +#endif +} + +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/Source/WebCore/loader/appcache/ApplicationCacheGroup.h b/Source/WebCore/loader/appcache/ApplicationCacheGroup.h new file mode 100644 index 0000000..29d0749 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheGroup.h @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2008, 2009, 2010 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 "DOMApplicationCache.h" +#include "KURL.h" +#include "PlatformString.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "SharedBuffer.h" + +#include <wtf/Noncopyable.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> + +namespace WebCore { + +class ApplicationCache; +class ApplicationCacheResource; +class Document; +class DocumentLoader; +class Frame; +class SecurityOrigin; + +enum ApplicationCacheUpdateOption { + ApplicationCacheUpdateWithBrowsingContext, + ApplicationCacheUpdateWithoutBrowsingContext +}; + +class ApplicationCacheGroup : public Noncopyable, ResourceHandleClient { +public: + ApplicationCacheGroup(const KURL& manifestURL, bool isCopy = false); + ~ApplicationCacheGroup(); + + enum UpdateStatus { Idle, Checking, Downloading }; + + static ApplicationCache* cacheForMainRequest(const ResourceRequest&, DocumentLoader*); + static ApplicationCache* fallbackCacheForMainRequest(const ResourceRequest&, DocumentLoader*); + + static void selectCache(Frame*, const KURL& manifestURL); + static void selectCacheWithoutManifestURL(Frame*); + + const KURL& manifestURL() const { return m_manifestURL; } + const SecurityOrigin* origin() const { return m_origin.get(); } + UpdateStatus updateStatus() const { return m_updateStatus; } + void setUpdateStatus(UpdateStatus status); + + void setStorageID(unsigned storageID) { m_storageID = storageID; } + unsigned storageID() const { return m_storageID; } + void clearStorageID(); + + void update(Frame*, ApplicationCacheUpdateOption); // FIXME: Frame should not be needed when updating without browsing context. + void cacheDestroyed(ApplicationCache*); + + bool cacheIsBeingUpdated(const ApplicationCache* cache) const { return cache == m_cacheBeingUpdated; } + + void stopLoadingInFrame(Frame*); + + ApplicationCache* newestCache() const { return m_newestCache.get(); } + void setNewestCache(PassRefPtr<ApplicationCache>); + + void makeObsolete(); + bool isObsolete() const { return m_isObsolete; } + + void finishedLoadingMainResource(DocumentLoader*); + void failedLoadingMainResource(DocumentLoader*); + + void disassociateDocumentLoader(DocumentLoader*); + + bool isCopy() const { return m_isCopy; } + +private: + static void postListenerTask(ApplicationCacheHost::EventID id, const HashSet<DocumentLoader*>& set) { postListenerTask(id, 0, 0, set); } + static void postListenerTask(ApplicationCacheHost::EventID id, DocumentLoader* loader) { postListenerTask(id, 0, 0, loader); } + static void postListenerTask(ApplicationCacheHost::EventID, int progressTotal, int progressDone, const HashSet<DocumentLoader*>&); + static void postListenerTask(ApplicationCacheHost::EventID, int progressTotal, int progressDone, DocumentLoader*); + + void scheduleReachedMaxAppCacheSizeCallback(); + void scheduleReachedOriginQuotaCallback(); + + PassRefPtr<ResourceHandle> createResourceHandle(const KURL&, ApplicationCacheResource* newestCachedResource); + + // For normal resource loading, WebKit client is asked about each resource individually. Since application cache does not belong to any particular document, + // the existing client callback cannot be used, so assume that any client that enables application cache also wants it to use credential storage. + virtual bool shouldUseCredentialStorage(ResourceHandle*) { return true; } + +#if ENABLE(INSPECTOR) + virtual void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&); +#endif + virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); + virtual void didReceiveData(ResourceHandle*, const char*, int length, int lengthReceived); + virtual void didFinishLoading(ResourceHandle*, double finishTime); + virtual void didFail(ResourceHandle*, const ResourceError&); + + void didReceiveManifestResponse(const ResourceResponse&); + void didReceiveManifestData(const char*, int); + void didFinishLoadingManifest(); + void didReachMaxAppCacheSize(); + void didReachOriginQuota(PassRefPtr<Frame> frame); + + void startLoadingEntry(); + void deliverDelayedMainResources(); + void checkIfLoadIsComplete(); + void cacheUpdateFailed(); + void cacheUpdateFailedDueToOriginQuota(); + void manifestNotFound(); + + void addEntry(const String&, unsigned type); + + void associateDocumentLoaderWithCache(DocumentLoader*, ApplicationCache*); + + void stopLoading(); + + KURL m_manifestURL; + RefPtr<SecurityOrigin> m_origin; + UpdateStatus m_updateStatus; + + // This is the newest complete cache in the group. + RefPtr<ApplicationCache> m_newestCache; + + // All complete caches in this cache group. + HashSet<ApplicationCache*> m_caches; + + // The cache being updated (if any). Note that cache updating does not immediately create a new + // ApplicationCache object, so this may be null even when update status is not Idle. + RefPtr<ApplicationCache> m_cacheBeingUpdated; + + // List of pending master entries, used during the update process to ensure that new master entries are cached. + HashSet<DocumentLoader*> m_pendingMasterResourceLoaders; + // How many of the above pending master entries have not yet finished downloading. + int m_downloadingPendingMasterResourceLoadersCount; + + // 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; + + // The total number of items to be processed to update the cache group and the number that have been done. + int m_progressTotal; + int m_progressDone; + + // Frame used for fetching resources when updating. + // FIXME: An update started by a particular frame should not stop if it is destroyed, but there are other frames associated with the same cache group. + Frame* m_frame; + + // An obsolete cache group is never stored, but the opposite is not true - storing may fail for multiple reasons, such as exceeding disk quota. + unsigned m_storageID; + bool m_isObsolete; + + // During update, this is used to handle asynchronously arriving results. + enum CompletionType { + None, + NoUpdate, + Failure, + Completed + }; + CompletionType m_completionType; + + // Whether this cache group is a copy that's only used for transferring the cache to another file. + bool m_isCopy; + + // This flag is set immediately after the ChromeClient::reachedMaxAppCacheSize() callback is invoked as a result of the storage layer failing to save a cache + // due to reaching the maximum size of the application cache database file. This flag is used by ApplicationCacheGroup::checkIfLoadIsComplete() to decide + // the course of action in case of this failure (i.e. call the ChromeClient callback or run the failure steps). + bool m_calledReachedMaxAppCacheSize; + + RefPtr<ResourceHandle> m_currentHandle; + RefPtr<ApplicationCacheResource> m_currentResource; + +#if ENABLE(INSPECTOR) + unsigned long m_currentResourceIdentifier; +#endif + + RefPtr<ApplicationCacheResource> m_manifestResource; + RefPtr<ResourceHandle> m_manifestHandle; + + int64_t m_loadedSize; + int64_t m_availableSpaceInQuota; + bool m_originQuotaReached; + + friend class ChromeClientCallbackTimer; + friend class OriginQuotaReachedCallbackTimer; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheGroup_h diff --git a/Source/WebCore/loader/appcache/ApplicationCacheHost.cpp b/Source/WebCore/loader/appcache/ApplicationCacheHost.cpp new file mode 100644 index 0000000..d5707cf --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheHost.cpp @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2008, 2009 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 "ApplicationCacheHost.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCache.h" +#include "ApplicationCacheGroup.h" +#include "ApplicationCacheResource.h" +#include "DocumentLoader.h" +#include "DOMApplicationCache.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "MainResourceLoader.h" +#include "ProgressEvent.h" +#include "ResourceLoader.h" +#include "ResourceRequest.h" +#include "Settings.h" + +namespace WebCore { + +ApplicationCacheHost::ApplicationCacheHost(DocumentLoader* documentLoader) + : m_domApplicationCache(0) + , m_documentLoader(documentLoader) + , m_defersEvents(true) + , m_candidateApplicationCacheGroup(0) +{ + ASSERT(m_documentLoader); +} + +ApplicationCacheHost::~ApplicationCacheHost() +{ + ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup); + + if (m_applicationCache) + m_applicationCache->group()->disassociateDocumentLoader(m_documentLoader); + else if (m_candidateApplicationCacheGroup) + m_candidateApplicationCacheGroup->disassociateDocumentLoader(m_documentLoader); +} + +void ApplicationCacheHost::selectCacheWithoutManifest() +{ + ApplicationCacheGroup::selectCacheWithoutManifestURL(m_documentLoader->frame()); +} + +void ApplicationCacheHost::selectCacheWithManifest(const KURL& manifestURL) +{ + ApplicationCacheGroup::selectCache(m_documentLoader->frame(), manifestURL); +} + +void ApplicationCacheHost::maybeLoadMainResource(ResourceRequest& request, SubstituteData& substituteData) +{ + // Check if this request should be loaded from the application cache + if (!substituteData.isValid() && isApplicationCacheEnabled()) { + ASSERT(!m_mainResourceApplicationCache); + + m_mainResourceApplicationCache = ApplicationCacheGroup::cacheForMainRequest(request, m_documentLoader); + + if (m_mainResourceApplicationCache) { + // Get the resource from the application cache. By definition, cacheForMainRequest() returns a cache that contains the resource. + ApplicationCacheResource* resource = m_mainResourceApplicationCache->resourceForRequest(request); + substituteData = SubstituteData(resource->data(), + resource->response().mimeType(), + resource->response().textEncodingName(), KURL()); + } + } +} + +void ApplicationCacheHost::maybeLoadMainResourceForRedirect(ResourceRequest& request, SubstituteData& substituteData) +{ + ASSERT(status() == UNCACHED); + maybeLoadMainResource(request, substituteData); +} + +bool ApplicationCacheHost::maybeLoadFallbackForMainResponse(const ResourceRequest& request, const ResourceResponse& r) +{ + if (r.httpStatusCode() / 100 == 4 || r.httpStatusCode() / 100 == 5) { + ASSERT(!m_mainResourceApplicationCache); + if (isApplicationCacheEnabled()) { + m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, documentLoader()); + + if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get())) + return true; + } + } + return false; +} + +bool ApplicationCacheHost::maybeLoadFallbackForMainError(const ResourceRequest& request, const ResourceError& error) +{ + if (!error.isCancellation()) { + ASSERT(!m_mainResourceApplicationCache); + if (isApplicationCacheEnabled()) { + m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, m_documentLoader); + + if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get())) + return true; + } + } + return false; +} + +void ApplicationCacheHost::mainResourceDataReceived(const char*, int, long long, bool) +{ + // This method is here to facilitate alternate implemetations of this interface by the host browser. +} + +void ApplicationCacheHost::failedLoadingMainResource() +{ + ApplicationCacheGroup* group = m_candidateApplicationCacheGroup; + if (!group && m_applicationCache) { + if (mainResourceApplicationCache()) { + // Even when the main resource is being loaded from an application cache, loading can fail if aborted. + return; + } + group = m_applicationCache->group(); + } + + if (group) + group->failedLoadingMainResource(m_documentLoader); +} + +void ApplicationCacheHost::finishedLoadingMainResource() +{ + ApplicationCacheGroup* group = candidateApplicationCacheGroup(); + if (!group && applicationCache() && !mainResourceApplicationCache()) + group = applicationCache()->group(); + + if (group) + group->finishedLoadingMainResource(m_documentLoader); +} + +bool ApplicationCacheHost::maybeLoadResource(ResourceLoader* loader, ResourceRequest& request, const KURL& originalURL) +{ + if (!isApplicationCacheEnabled()) + return false; + + if (request.url() != originalURL) + return false; + + ApplicationCacheResource* resource; + if (!shouldLoadResourceFromApplicationCache(request, resource)) + return false; + + m_documentLoader->m_pendingSubstituteResources.set(loader, resource); + m_documentLoader->deliverSubstituteResourcesAfterDelay(); + + return true; +} + +bool ApplicationCacheHost::maybeLoadFallbackForRedirect(ResourceLoader* resourceLoader, ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + if (!redirectResponse.isNull() && !protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) + if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader)) + return true; + return false; +} + +bool ApplicationCacheHost::maybeLoadFallbackForResponse(ResourceLoader* resourceLoader, const ResourceResponse& response) +{ + if (response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5) + if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader)) + return true; + return false; +} + +bool ApplicationCacheHost::maybeLoadFallbackForError(ResourceLoader* resourceLoader, const ResourceError& error) +{ + if (!error.isCancellation()) + if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader)) + return true; + return false; +} + +bool ApplicationCacheHost::maybeLoadSynchronously(ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data) +{ + ApplicationCacheResource* resource; + if (shouldLoadResourceFromApplicationCache(request, resource)) { + if (resource) { + response = resource->response(); + data.append(resource->data()->data(), resource->data()->size()); + } else { + error = documentLoader()->frameLoader()->client()->cannotShowURLError(request); + } + return true; + } + return false; +} + +void ApplicationCacheHost::maybeLoadFallbackSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data) +{ + // If normal loading results in a redirect to a resource with another origin (indicative of a captive portal), or a 4xx or 5xx status code or equivalent, + // or if there were network errors (but not if the user canceled the download), then instead get, from the cache, the resource of the fallback entry + // corresponding to the matched namespace. + if ((!error.isNull() && !error.isCancellation()) + || response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5 + || !protocolHostAndPortAreEqual(request.url(), response.url())) { + ApplicationCacheResource* resource; + if (getApplicationCacheFallbackResource(request, resource)) { + response = resource->response(); + data.clear(); + data.append(resource->data()->data(), resource->data()->size()); + } + } +} + +bool ApplicationCacheHost::canCacheInPageCache() const +{ + return !applicationCache() && !candidateApplicationCacheGroup(); +} + +void ApplicationCacheHost::setDOMApplicationCache(DOMApplicationCache* domApplicationCache) +{ + ASSERT(!m_domApplicationCache || !domApplicationCache); + m_domApplicationCache = domApplicationCache; +} + +void ApplicationCacheHost::notifyDOMApplicationCache(EventID id, int total, int done) +{ + if (m_defersEvents) { + // Event dispatching is deferred until document.onload has fired. + m_deferredEvents.append(DeferredEvent(id, total, done)); + return; + } + dispatchDOMEvent(id, total, done); +} + +void ApplicationCacheHost::stopLoadingInFrame(Frame* frame) +{ + ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup); + + if (m_candidateApplicationCacheGroup) + m_candidateApplicationCacheGroup->stopLoadingInFrame(frame); + else if (m_applicationCache) + m_applicationCache->group()->stopLoadingInFrame(frame); +} + +void ApplicationCacheHost::stopDeferringEvents() +{ + RefPtr<DocumentLoader> protect(documentLoader()); + for (unsigned i = 0; i < m_deferredEvents.size(); ++i) { + const DeferredEvent& deferred = m_deferredEvents[i]; + dispatchDOMEvent(deferred.eventID, deferred.progressTotal, deferred.progressDone); + } + m_deferredEvents.clear(); + m_defersEvents = false; +} + +#if ENABLE(INSPECTOR) +void ApplicationCacheHost::fillResourceList(ResourceInfoList* resources) +{ + ApplicationCache* cache = applicationCache(); + if (!cache || !cache->isComplete()) + return; + + ApplicationCache::ResourceMap::const_iterator end = cache->end(); + for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { + RefPtr<ApplicationCacheResource> resource = it->second; + unsigned type = resource->type(); + bool isMaster = type & ApplicationCacheResource::Master; + bool isManifest = type & ApplicationCacheResource::Manifest; + bool isExplicit = type & ApplicationCacheResource::Explicit; + bool isForeign = type & ApplicationCacheResource::Foreign; + bool isFallback = type & ApplicationCacheResource::Fallback; + resources->append(ResourceInfo(resource->url(), isMaster, isManifest, isFallback, isForeign, isExplicit, resource->estimatedSizeInStorage())); + } +} + +ApplicationCacheHost::CacheInfo ApplicationCacheHost::applicationCacheInfo() +{ + ApplicationCache* cache = applicationCache(); + if (!cache || !cache->isComplete()) + return CacheInfo(KURL(), 0, 0, 0); + + // FIXME: Add "Creation Time" and "Update Time" to Application Caches. + return CacheInfo(cache->manifestResource()->url(), 0, 0, cache->estimatedSizeInStorage()); +} +#endif + +void ApplicationCacheHost::dispatchDOMEvent(EventID id, int total, int done) +{ + if (m_domApplicationCache) { + const AtomicString& eventType = DOMApplicationCache::toEventType(id); + ExceptionCode ec = 0; + RefPtr<Event> event; + if (id == PROGRESS_EVENT) + event = ProgressEvent::create(eventType, true, done, total); + else + event = Event::create(eventType, false, false); + m_domApplicationCache->dispatchEvent(event, ec); + ASSERT(!ec); + } +} + +void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group) +{ + ASSERT(!m_applicationCache); + m_candidateApplicationCacheGroup = group; +} + +void ApplicationCacheHost::setApplicationCache(PassRefPtr<ApplicationCache> applicationCache) +{ + if (m_candidateApplicationCacheGroup) { + ASSERT(!m_applicationCache); + m_candidateApplicationCacheGroup = 0; + } + + m_applicationCache = applicationCache; +} + +bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& request, ApplicationCacheResource*& resource) +{ + ApplicationCache* cache = applicationCache(); + if (!cache || !cache->isComplete()) + return false; + + // If the resource is not to be fetched using the HTTP GET mechanism or equivalent, or if its URL has a different + // <scheme> component than the application cache's manifest, then fetch the resource normally. + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request) || !equalIgnoringCase(request.url().protocol(), cache->manifestResource()->url().protocol())) + return false; + + // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry + // in the application cache, then get the resource from the cache (instead of fetching it). + resource = cache->resourceForURL(request.url()); + + // Resources that match fallback namespaces or online whitelist entries are fetched from the network, + // unless they are also cached. + if (!resource && (cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url()))) + return false; + + // Resources that are not present in the manifest will always fail to load (at least, after the + // cache has been primed the first time), making the testing of offline applications simpler. + return true; +} + +bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache) +{ + if (!cache) { + cache = applicationCache(); + if (!cache) + return false; + } + if (!cache->isComplete()) + return false; + + // If the resource is not a HTTP/HTTPS GET, then abort + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) + return false; + + KURL fallbackURL; + if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL)) + return false; + + resource = cache->resourceForURL(fallbackURL); + ASSERT(resource); + + return true; +} + +bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache) +{ + if (!isApplicationCacheEnabled()) + return false; + + ApplicationCacheResource* resource; + if (!getApplicationCacheFallbackResource(loader->request(), resource, cache)) + return false; + + m_documentLoader->m_pendingSubstituteResources.set(loader, resource); + m_documentLoader->deliverSubstituteResourcesAfterDelay(); + + loader->handle()->cancel(); + + return true; +} + +ApplicationCacheHost::Status ApplicationCacheHost::status() const +{ + ApplicationCache* cache = applicationCache(); + if (!cache) + return UNCACHED; + + switch (cache->group()->updateStatus()) { + case ApplicationCacheGroup::Checking: + return CHECKING; + case ApplicationCacheGroup::Downloading: + return DOWNLOADING; + case ApplicationCacheGroup::Idle: { + if (cache->group()->isObsolete()) + return OBSOLETE; + if (cache != cache->group()->newestCache()) + return UPDATEREADY; + return IDLE; + } + } + + ASSERT_NOT_REACHED(); + return UNCACHED; +} + +bool ApplicationCacheHost::update() +{ + ApplicationCache* cache = applicationCache(); + if (!cache) + return false; + cache->group()->update(m_documentLoader->frame(), ApplicationCacheUpdateWithoutBrowsingContext); + return true; +} + +bool ApplicationCacheHost::swapCache() +{ + ApplicationCache* cache = applicationCache(); + if (!cache) + return false; + + // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache. + if (cache->group()->isObsolete()) { + cache->group()->disassociateDocumentLoader(m_documentLoader); + return true; + } + + // If there is no newer cache, raise an INVALID_STATE_ERR exception. + ApplicationCache* newestCache = cache->group()->newestCache(); + if (cache == newestCache) + return false; + + ASSERT(cache->group() == newestCache->group()); + setApplicationCache(newestCache); + + return true; +} + +bool ApplicationCacheHost::isApplicationCacheEnabled() +{ + return m_documentLoader->frame()->settings() + && m_documentLoader->frame()->settings()->offlineWebApplicationCacheEnabled(); +} + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/Source/WebCore/loader/appcache/ApplicationCacheHost.h b/Source/WebCore/loader/appcache/ApplicationCacheHost.h new file mode 100644 index 0000000..8ac5357 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheHost.h @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2009, Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ApplicationCacheHost_h +#define ApplicationCacheHost_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "KURL.h" +#include <wtf/Deque.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + class DOMApplicationCache; + class DocumentLoader; + class Frame; + class ResourceLoader; + class ResourceError; + class ResourceRequest; + class ResourceResponse; + class SubstituteData; +#if PLATFORM(CHROMIUM) + class ApplicationCacheHostInternal; +#else + class ApplicationCache; + class ApplicationCacheGroup; + class ApplicationCacheResource; + class ApplicationCacheStorage; +#endif + + class ApplicationCacheHost : public Noncopyable { + public: + // The Status numeric values are specified in the HTML5 spec. + enum Status { + UNCACHED = 0, + IDLE = 1, + CHECKING = 2, + DOWNLOADING = 3, + UPDATEREADY = 4, + OBSOLETE = 5 + }; + + enum EventID { + CHECKING_EVENT = 0, + ERROR_EVENT, + NOUPDATE_EVENT, + DOWNLOADING_EVENT, + PROGRESS_EVENT, + UPDATEREADY_EVENT, + CACHED_EVENT, + OBSOLETE_EVENT // Must remain the last value, this is used to size arrays. + }; + +#if ENABLE(INSPECTOR) + struct CacheInfo { + CacheInfo(const KURL& manifest, double creationTime, double updateTime, long long size) + : m_manifest(manifest) + , m_creationTime(creationTime) + , m_updateTime(updateTime) + , m_size(size) { } + KURL m_manifest; + double m_creationTime; + double m_updateTime; + long long m_size; + }; + + struct ResourceInfo { + ResourceInfo(const KURL& resource, bool isMaster, bool isManifest, bool isFallback, bool isForeign, bool isExplicit, long long size) + : m_resource(resource) + , m_isMaster(isMaster) + , m_isManifest(isManifest) + , m_isFallback(isFallback) + , m_isForeign(isForeign) + , m_isExplicit(isExplicit) + , m_size(size) { } + KURL m_resource; + bool m_isMaster; + bool m_isManifest; + bool m_isFallback; + bool m_isForeign; + bool m_isExplicit; + long long m_size; + }; + + typedef Vector<ResourceInfo> ResourceInfoList; +#endif + + ApplicationCacheHost(DocumentLoader*); + ~ApplicationCacheHost(); + + void selectCacheWithoutManifest(); + void selectCacheWithManifest(const KURL& manifestURL); + + void maybeLoadMainResource(ResourceRequest&, SubstituteData&); + void maybeLoadMainResourceForRedirect(ResourceRequest&, SubstituteData&); + bool maybeLoadFallbackForMainResponse(const ResourceRequest&, const ResourceResponse&); + bool maybeLoadFallbackForMainError(const ResourceRequest&, const ResourceError&); + void mainResourceDataReceived(const char* data, int length, long long lengthReceived, bool allAtOnce); + void finishedLoadingMainResource(); + void failedLoadingMainResource(); + + bool maybeLoadResource(ResourceLoader*, ResourceRequest&, const KURL& originalURL); + bool maybeLoadFallbackForRedirect(ResourceLoader*, ResourceRequest&, const ResourceResponse&); + bool maybeLoadFallbackForResponse(ResourceLoader*, const ResourceResponse&); + bool maybeLoadFallbackForError(ResourceLoader*, const ResourceError&); + + bool maybeLoadSynchronously(ResourceRequest&, ResourceError&, ResourceResponse&, Vector<char>& data); + void maybeLoadFallbackSynchronously(const ResourceRequest&, ResourceError&, ResourceResponse&, Vector<char>& data); + + bool canCacheInPageCache() const; + + Status status() const; + bool update(); + bool swapCache(); + + void setDOMApplicationCache(DOMApplicationCache*); + void notifyDOMApplicationCache(EventID, int progressTotal, int progressDone); + + void stopLoadingInFrame(Frame*); + + void stopDeferringEvents(); // Also raises the events that have been queued up. + +#if ENABLE(INSPECTOR) + void fillResourceList(ResourceInfoList*); + CacheInfo applicationCacheInfo(); +#endif + + private: + bool isApplicationCacheEnabled(); + DocumentLoader* documentLoader() const { return m_documentLoader; } + + struct DeferredEvent { + EventID eventID; + int progressTotal; + int progressDone; + DeferredEvent(EventID id, int total, int done) : eventID(id), progressTotal(total), progressDone(done) { } + }; + + DOMApplicationCache* m_domApplicationCache; + DocumentLoader* m_documentLoader; + bool m_defersEvents; // Events are deferred until after document onload. + Vector<DeferredEvent> m_deferredEvents; + + void dispatchDOMEvent(EventID, int progressTotal, int progressDone); + +#if PLATFORM(CHROMIUM) + friend class ApplicationCacheHostInternal; + OwnPtr<ApplicationCacheHostInternal> m_internal; +#else + friend class ApplicationCacheGroup; + friend class ApplicationCacheStorage; + + bool scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader*, ApplicationCache* = 0); + bool shouldLoadResourceFromApplicationCache(const ResourceRequest&, ApplicationCacheResource*&); + bool getApplicationCacheFallbackResource(const ResourceRequest&, ApplicationCacheResource*&, ApplicationCache* = 0); + void setCandidateApplicationCacheGroup(ApplicationCacheGroup* group); + ApplicationCacheGroup* candidateApplicationCacheGroup() const { return m_candidateApplicationCacheGroup; } + void setApplicationCache(PassRefPtr<ApplicationCache> applicationCache); + ApplicationCache* applicationCache() const { return m_applicationCache.get(); } + ApplicationCache* mainResourceApplicationCache() const { return m_mainResourceApplicationCache.get(); } + + + // 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; + + // This is the application cache the main resource was loaded from (if any). + RefPtr<ApplicationCache> m_mainResourceApplicationCache; +#endif + }; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) +#endif // ApplicationCacheHost_h diff --git a/Source/WebCore/loader/appcache/ApplicationCacheResource.cpp b/Source/WebCore/loader/appcache/ApplicationCacheResource.cpp new file mode 100644 index 0000000..03c5c83 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheResource.cpp @@ -0,0 +1,90 @@ +/* + * 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) + , m_estimatedSizeInStorage(0) +{ +} + +void ApplicationCacheResource::addType(unsigned type) +{ + // Caller should take care of storing the new type in database. + m_type |= type; +} + +int64_t ApplicationCacheResource::estimatedSizeInStorage() +{ + if (m_estimatedSizeInStorage) + return m_estimatedSizeInStorage; + + if (data()) + m_estimatedSizeInStorage = data()->size(); + + HTTPHeaderMap::const_iterator end = response().httpHeaderFields().end(); + for (HTTPHeaderMap::const_iterator it = response().httpHeaderFields().begin(); it != end; ++it) + m_estimatedSizeInStorage += (it->first.length() + it->second.length() + 2) * sizeof(UChar); + + m_estimatedSizeInStorage += url().string().length() * sizeof(UChar); + m_estimatedSizeInStorage += sizeof(int); // response().m_httpStatusCode + m_estimatedSizeInStorage += response().url().string().length() * sizeof(UChar); + m_estimatedSizeInStorage += sizeof(unsigned); // dataId + m_estimatedSizeInStorage += response().mimeType().length() * sizeof(UChar); + m_estimatedSizeInStorage += response().textEncodingName().length() * sizeof(UChar); + + return m_estimatedSizeInStorage; +} + +#ifndef NDEBUG +void ApplicationCacheResource::dumpType(unsigned type) +{ + if (type & Master) + printf("master "); + if (type & Manifest) + printf("manifest "); + if (type & Explicit) + printf("explicit "); + if (type & Foreign) + printf("foreign "); + if (type & Fallback) + printf("fallback "); + + printf("\n"); +} +#endif + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/Source/WebCore/loader/appcache/ApplicationCacheResource.h b/Source/WebCore/loader/appcache/ApplicationCacheResource.h new file mode 100644 index 0000000..2ca7846 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheResource.h @@ -0,0 +1,75 @@ +/* + * 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 { + Master = 1 << 0, + Manifest = 1 << 1, + Explicit = 1 << 2, + Foreign = 1 << 3, + Fallback = 1 << 4 + }; + + static PassRefPtr<ApplicationCacheResource> create(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> buffer = SharedBuffer::create()) + { + ASSERT(!url.hasFragmentIdentifier()); + 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; } + int64_t estimatedSizeInStorage(); + +#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; + int64_t m_estimatedSizeInStorage; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheResource_h diff --git a/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp new file mode 100644 index 0000000..7b20775 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheStorage.cpp @@ -0,0 +1,1306 @@ +/* + * Copyright (C) 2008, 2009, 2010 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 "ApplicationCacheHost.h" +#include "ApplicationCacheResource.h" +#include "FileSystem.h" +#include "KURL.h" +#include "SQLiteStatement.h" +#include "SQLiteTransaction.h" +#include "SecurityOrigin.h" +#include <wtf/text/CString.h> +#include <wtf/StdLibExtras.h> +#include <wtf/StringExtras.h> + +using namespace std; + +namespace WebCore { + +template <class T> +class StorageIDJournal { +public: + ~StorageIDJournal() + { + size_t size = m_records.size(); + for (size_t i = 0; i < size; ++i) + m_records[i].restore(); + } + + void add(T* resource, unsigned storageID) + { + m_records.append(Record(resource, storageID)); + } + + void commit() + { + m_records.clear(); + } + +private: + class Record { + public: + Record() : m_resource(0), m_storageID(0) { } + Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { } + + void restore() + { + m_resource->setStorageID(m_storageID); + } + + private: + T* m_resource; + unsigned m_storageID; + }; + + Vector<Record> m_records; +}; + +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 = static_cast<unsigned>(statement.getColumnInt64(2)); + + RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID); + if (!cache) + return 0; + + ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); + + group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); + group->setNewestCache(cache.release()); + + return group; +} + +ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL) +{ + ASSERT(!manifestURL.hasFragmentIdentifier()); + + 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; + + while (statement.step() == SQLResultRow) + m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0))); +} + +ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url) +{ + ASSERT(!url.hasFragmentIdentifier()); + + 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; + + ASSERT(!group->isObsolete()); + + if (!protocolHostAndPortAreEqual(url, group->manifestURL())) + continue; + + if (ApplicationCache* cache = group->newestCache()) { + ApplicationCacheResource* resource = cache->resourceForURL(url); + if (!resource) + continue; + if (resource->type() & ApplicationCacheResource::Foreign) + continue; + 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(ParsedURLString, statement.getColumnText(1)); + + if (m_cachesInMemory.contains(manifestURL)) + continue; + + 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 = static_cast<unsigned>(statement.getColumnInt64(2)); + RefPtr<ApplicationCache> cache = loadCache(newestCacheID); + if (!cache) + continue; + + ApplicationCacheResource* resource = cache->resourceForURL(url); + if (!resource) + continue; + if (resource->type() & ApplicationCacheResource::Foreign) + continue; + + ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); + + group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); + group->setNewestCache(cache.release()); + + 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; +} + +ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url) +{ + ASSERT(!url.hasFragmentIdentifier()); + + // Check if an appropriate 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; + + ASSERT(!group->isObsolete()); + + if (ApplicationCache* cache = group->newestCache()) { + KURL fallbackURL; + if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) + continue; + if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) + continue; + 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(ParsedURLString, statement.getColumnText(1)); + + if (m_cachesInMemory.contains(manifestURL)) + continue; + + // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match. + if (!protocolHostAndPortAreEqual(url, manifestURL)) + continue; + + // We found a cache group that matches. Now check if the newest cache has a resource with + // a matching fallback namespace. + unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2)); + RefPtr<ApplicationCache> cache = loadCache(newestCacheID); + + KURL fallbackURL; + if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL)) + continue; + if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign) + continue; + + ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); + + group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0))); + group->setNewestCache(cache.release()); + + 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) +{ + if (group->isObsolete()) { + ASSERT(!group->storageID()); + ASSERT(m_cachesInMemory.get(group->manifestURL()) != group); + return; + } + + ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); + + m_cachesInMemory.remove(group->manifestURL()); + + // If the cache group is half-created, we don't want it in the saved set (as it is not stored in database). + if (!group->storageID()) + m_cacheHostSet.remove(urlHostHash(group->manifestURL())); +} + +void ApplicationCacheStorage::cacheGroupMadeObsolete(ApplicationCacheGroup* group) +{ + ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); + ASSERT(m_cacheHostSet.contains(urlHostHash(group->manifestURL()))); + + if (ApplicationCache* newestCache = group->newestCache()) + remove(newestCache); + + m_cachesInMemory.remove(group->manifestURL()); + m_cacheHostSet.remove(urlHostHash(group->manifestURL())); +} + +void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory) +{ + ASSERT(m_cacheDirectory.isNull()); + ASSERT(!cacheDirectory.isNull()); + + m_cacheDirectory = cacheDirectory; +} + +const String& ApplicationCacheStorage::cacheDirectory() const +{ + return m_cacheDirectory; +} + +void ApplicationCacheStorage::setMaximumSize(int64_t size) +{ + m_maximumSize = size; +} + +int64_t ApplicationCacheStorage::maximumSize() const +{ + return m_maximumSize; +} + +bool ApplicationCacheStorage::isMaximumSizeReached() const +{ + return m_isMaximumSizeReached; +} + +int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave) +{ + int64_t spaceNeeded = 0; + long long fileSize = 0; + if (!getFileSize(m_cacheFile, fileSize)) + return 0; + + int64_t currentSize = fileSize; + + // Determine the amount of free space we have available. + int64_t totalAvailableSize = 0; + if (m_maximumSize < currentSize) { + // The max size is smaller than the actual size of the app cache file. + // This can happen if the client previously imposed a larger max size + // value and the app cache file has already grown beyond the current + // max size value. + // The amount of free space is just the amount of free space inside + // the database file. Note that this is always 0 if SQLite is compiled + // with AUTO_VACUUM = 1. + totalAvailableSize = m_database.freeSpaceSize(); + } else { + // The max size is the same or larger than the current size. + // The amount of free space available is the amount of free space + // inside the database file plus the amount we can grow until we hit + // the max size. + totalAvailableSize = (m_maximumSize - currentSize) + m_database.freeSpaceSize(); + } + + // The space needed to be freed in order to accommodate the failed cache is + // the size of the failed cache minus any already available free space. + spaceNeeded = cacheToSave - totalAvailableSize; + // The space needed value must be positive (or else the total already + // available free space would be larger than the size of the failed cache and + // saving of the cache should have never failed). + ASSERT(spaceNeeded); + return spaceNeeded; +} + +void ApplicationCacheStorage::setDefaultOriginQuota(int64_t quota) +{ + m_defaultOriginQuota = quota; +} + +bool ApplicationCacheStorage::quotaForOrigin(const SecurityOrigin* origin, int64_t& quota) +{ + // If an Origin record doesn't exist, then the COUNT will be 0 and quota will be 0. + // Using the count to determine if a record existed or not is a safe way to determine + // if a quota of 0 is real, from the record, or from null. + SQLiteStatement statement(m_database, "SELECT COUNT(quota), quota FROM Origins WHERE origin=?"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, origin->databaseIdentifier()); + int result = statement.step(); + + // Return the quota, or if it was null the default. + if (result == SQLResultRow) { + bool wasNoRecord = statement.getColumnInt64(0) == 0; + quota = wasNoRecord ? m_defaultOriginQuota : statement.getColumnInt64(1); + return true; + } + + LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg()); + return false; +} + +bool ApplicationCacheStorage::usageForOrigin(const SecurityOrigin* origin, int64_t& usage) +{ + // If an Origins record doesn't exist, then the SUM will be null, + // which will become 0, as expected, when converting to a number. + SQLiteStatement statement(m_database, "SELECT SUM(Caches.size)" + " FROM CacheGroups" + " INNER JOIN Origins ON CacheGroups.origin = Origins.origin" + " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup" + " WHERE Origins.origin=?"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, origin->databaseIdentifier()); + int result = statement.step(); + + if (result == SQLResultRow) { + usage = statement.getColumnInt64(0); + return true; + } + + LOG_ERROR("Could not get the quota of an origin, error \"%s\"", m_database.lastErrorMsg()); + return false; +} + +bool ApplicationCacheStorage::remainingSizeForOriginExcludingCache(const SecurityOrigin* origin, ApplicationCache* cache, int64_t& remainingSize) +{ + openDatabase(false); + if (!m_database.isOpen()) + return false; + + // Remaining size = total origin quota - size of all caches with origin excluding the provided cache. + // Keep track of the number of caches so we can tell if the result was a calculation or not. + const char* query; + int64_t excludingCacheIdentifier = cache ? cache->storageID() : 0; + if (excludingCacheIdentifier != 0) { + query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)" + " FROM CacheGroups" + " INNER JOIN Origins ON CacheGroups.origin = Origins.origin" + " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup" + " WHERE Origins.origin=?" + " AND Caches.id!=?"; + } else { + query = "SELECT COUNT(Caches.size), Origins.quota - SUM(Caches.size)" + " FROM CacheGroups" + " INNER JOIN Origins ON CacheGroups.origin = Origins.origin" + " INNER JOIN Caches ON CacheGroups.id = Caches.cacheGroup" + " WHERE Origins.origin=?"; + } + + SQLiteStatement statement(m_database, query); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, origin->databaseIdentifier()); + if (excludingCacheIdentifier != 0) + statement.bindInt64(2, excludingCacheIdentifier); + int result = statement.step(); + + // If the count was 0 that then we have to query the origin table directly + // for its quota. Otherwise we can use the calculated value. + if (result == SQLResultRow) { + int64_t numberOfCaches = statement.getColumnInt64(0); + if (numberOfCaches == 0) + quotaForOrigin(origin, remainingSize); + else + remainingSize = statement.getColumnInt64(1); + return true; + } + + LOG_ERROR("Could not get the remaining size of an origin's quota, error \"%s\"", m_database.lastErrorMsg()); + return false; +} + +bool ApplicationCacheStorage::storeUpdatedQuotaForOrigin(const SecurityOrigin* origin, int64_t quota) +{ + openDatabase(true); + if (!m_database.isOpen()) + return false; + + if (!ensureOriginRecord(origin)) + return false; + + SQLiteStatement updateStatement(m_database, "UPDATE Origins SET quota=? WHERE origin=?"); + if (updateStatement.prepare() != SQLResultOk) + return false; + + updateStatement.bindInt64(1, quota); + updateStatement.bindText(2, origin->databaseIdentifier()); + + return executeStatement(updateStatement); +} + +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; +} + +// Update the schemaVersion when the schema of any the Application Cache +// SQLite tables changes. This allows the database to be rebuilt when +// a new, incompatible change has been introduced to the database schema. +static const int schemaVersion = 6; + +void ApplicationCacheStorage::verifySchemaVersion() +{ + int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0); + if (version == schemaVersion) + return; + + m_database.clearAllTables(); + + // Update user version. + SQLiteTransaction setDatabaseVersion(m_database); + setDatabaseVersion.begin(); + + char userVersionSQL[32]; + int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion); + ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes); + + SQLiteStatement statement(m_database, userVersionSQL); + if (statement.prepare() != SQLResultOk) + return; + + executeStatement(statement); + setDatabaseVersion.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; + + m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); + if (!createIfDoesNotExist && !fileExists(m_cacheFile)) + return; + + makeAllDirectories(m_cacheDirectory); + m_database.open(m_cacheFile); + + 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, origin TEXT)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size 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 CacheAllowsAllNetworkRequests (wildcard INTEGER 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)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS Origins (origin TEXT UNIQUE ON CONFLICT IGNORE, quota INTEGER NOT NULL ON CONFLICT FAIL)"); + + // 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 CacheAllowsAllNetworkRequests WHERE cache = OLD.id;" + " DELETE FROM FallbackURLs WHERE cache = OLD.id;" + " END"); + + // When a cache entry is deleted, its resource should also be deleted. + executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries" + " FOR EACH ROW BEGIN" + " DELETE FROM CacheResources WHERE id = OLD.resource;" + " 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, GroupStorageIDJournal* journal) +{ + ASSERT(group->storageID() == 0); + ASSERT(journal); + + SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL, origin) VALUES (?, ?, ?)"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, urlHostHash(group->manifestURL())); + statement.bindText(2, group->manifestURL()); + statement.bindText(3, group->origin()->databaseIdentifier()); + + if (!executeStatement(statement)) + return false; + + unsigned groupStorageID = static_cast<unsigned>(m_database.lastInsertRowID()); + + if (!ensureOriginRecord(group->origin())) + return false; + + group->setStorageID(groupStorageID); + journal->add(group, 0); + return true; +} + +bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJournal* storageIDJournal) +{ + ASSERT(cache->storageID() == 0); + ASSERT(cache->group()->storageID() != 0); + ASSERT(storageIDJournal); + + SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, cache->group()->storageID()); + statement.bindInt64(2, cache->estimatedSizeInStorage()); + + if (!executeStatement(statement)) + return false; + + unsigned cacheStorageID = static_cast<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) { + unsigned oldStorageID = it->second->storageID(); + if (!store(it->second.get(), cacheStorageID)) + return false; + + // Storing the resource succeeded. Log its old storageID in case + // it needs to be restored later. + storageIDJournal->add(it->second.get(), oldStorageID); + } + } + + // Store the online whitelist + const Vector<KURL>& onlineWhitelist = cache->onlineWhitelist(); + { + size_t whitelistSize = onlineWhitelist.size(); + for (size_t i = 0; i < whitelistSize; ++i) { + SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)"); + statement.prepare(); + + statement.bindText(1, onlineWhitelist[i]); + statement.bindInt64(2, cacheStorageID); + + if (!executeStatement(statement)) + return false; + } + } + + // Store online whitelist wildcard flag. + { + SQLiteStatement statement(m_database, "INSERT INTO CacheAllowsAllNetworkRequests (wildcard, cache) VALUES (?, ?)"); + statement.prepare(); + + statement.bindInt64(1, cache->allowsAllNetworkRequests()); + statement.bindInt64(2, cacheStorageID); + + if (!executeStatement(statement)) + return false; + } + + // Store fallback URLs. + const FallbackURLVector& fallbackURLs = cache->fallbackURLs(); + { + size_t fallbackCount = fallbackURLs.size(); + for (size_t i = 0; i < fallbackCount; ++i) { + SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)"); + statement.prepare(); + + statement.bindText(1, fallbackURLs[i].first); + statement.bindText(2, fallbackURLs[i].second); + statement.bindInt64(3, 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); + + // openDatabase(true) could still fail, for example when cacheStorage is full or no longer available. + if (!m_database.isOpen()) + return false; + + // 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 = static_cast<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; + + // The same ApplicationCacheResource are used in ApplicationCacheResource::size() + // to calculate the approximate size of an ApplicationCacheResource object. If + // you change the code below, please also change ApplicationCacheResource::size(). + 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 = static_cast<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; +} + +bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache) +{ + ASSERT_UNUSED(cache, cache->storageID()); + ASSERT(resource->storageID()); + + // First, insert the data + SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?"); + if (entryStatement.prepare() != SQLResultOk) + return false; + + entryStatement.bindInt64(1, resource->type()); + entryStatement.bindInt64(2, resource->storageID()); + + return executeStatement(entryStatement); +} + +bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) +{ + ASSERT(cache->storageID()); + + openDatabase(true); + + if (!m_database.isOpen()) + return false; + + m_isMaximumSizeReached = false; + m_database.setMaximumSize(m_maximumSize); + + SQLiteTransaction storeResourceTransaction(m_database); + storeResourceTransaction.begin(); + + if (!store(resource, cache->storageID())) { + checkForMaxSizeReached(); + return false; + } + + // A resource was added to the cache. Update the total data size for the cache. + SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?"); + if (sizeUpdateStatement.prepare() != SQLResultOk) + return false; + + sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage()); + sizeUpdateStatement.bindInt64(2, cache->storageID()); + + if (!executeStatement(sizeUpdateStatement)) + return false; + + storeResourceTransaction.commit(); + return true; +} + +bool ApplicationCacheStorage::ensureOriginRecord(const SecurityOrigin* origin) +{ + SQLiteStatement insertOriginStatement(m_database, "INSERT INTO Origins (origin, quota) VALUES (?, ?)"); + if (insertOriginStatement.prepare() != SQLResultOk) + return false; + + insertOriginStatement.bindText(1, origin->databaseIdentifier()); + insertOriginStatement.bindInt64(2, m_defaultOriginQuota); + if (!executeStatement(insertOriginStatement)) + return false; + + return true; +} + +bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group, ApplicationCache* oldCache, FailureReason& failureReason) +{ + openDatabase(true); + + if (!m_database.isOpen()) + return false; + + m_isMaximumSizeReached = false; + m_database.setMaximumSize(m_maximumSize); + + SQLiteTransaction storeCacheTransaction(m_database); + + storeCacheTransaction.begin(); + + // Check if this would reach the per-origin quota. + int64_t remainingSpaceInOrigin; + if (remainingSizeForOriginExcludingCache(group->origin(), oldCache, remainingSpaceInOrigin)) { + if (remainingSpaceInOrigin < group->newestCache()->estimatedSizeInStorage()) { + failureReason = OriginQuotaReached; + return false; + } + } + + GroupStorageIDJournal groupStorageIDJournal; + if (!group->storageID()) { + // Store the group + if (!store(group, &groupStorageIDJournal)) { + checkForMaxSizeReached(); + failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure; + return false; + } + } + + ASSERT(group->newestCache()); + ASSERT(!group->isObsolete()); + ASSERT(!group->newestCache()->storageID()); + + // Log the storageID changes to the in-memory resource objects. The journal + // object will roll them back automatically in case a database operation + // fails and this method returns early. + ResourceStorageIDJournal resourceStorageIDJournal; + + // Store the newest cache + if (!store(group->newestCache(), &resourceStorageIDJournal)) { + checkForMaxSizeReached(); + failureReason = isMaximumSizeReached() ? TotalQuotaReached : DiskOrOperationFailure; + return false; + } + + // Update the newest cache in the group. + + SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?"); + if (statement.prepare() != SQLResultOk) { + failureReason = DiskOrOperationFailure; + return false; + } + + statement.bindInt64(1, group->newestCache()->storageID()); + statement.bindInt64(2, group->storageID()); + + if (!executeStatement(statement)) { + failureReason = DiskOrOperationFailure; + return false; + } + + groupStorageIDJournal.commit(); + resourceStorageIDJournal.commit(); + storeCacheTransaction.commit(); + return true; +} + +bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) +{ + // Ignore the reason for failing, just attempt the store. + FailureReason ignoredFailureReason; + return storeNewestCache(group, 0, ignoredFailureReason); +} + +static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response) +{ + size_t pos = find(header, headerLength, ':'); + ASSERT(pos != notFound); + + AtomicString headerName = AtomicString(header, pos); + String headerValue = String(header + pos + 1, headerLength - pos - 1); + + response.setHTTPHeaderField(headerName, headerValue); +} + +static inline void parseHeaders(const String& headers, ResourceResponse& response) +{ + unsigned startPos = 0; + size_t endPos; + while ((endPos = headers.find('\n', startPos)) != notFound) { + ASSERT(startPos != endPos); + + parseHeader(headers.characters() + startPos, endPos - startPos, response); + + startPos = endPos + 1; + } + + if (startPos != 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(ParsedURLString, cacheStatement.getColumnText(0)); + + unsigned type = static_cast<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); + + Vector<KURL> whitelist; + while ((result = whitelistStatement.step()) == SQLResultRow) + whitelist.append(KURL(ParsedURLString, whitelistStatement.getColumnText(0))); + + if (result != SQLResultDone) + LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg()); + + cache->setOnlineWhitelist(whitelist); + + // Load online whitelist wildcard flag. + SQLiteStatement whitelistWildcardStatement(m_database, "SELECT wildcard FROM CacheAllowsAllNetworkRequests WHERE cache=?"); + if (whitelistWildcardStatement.prepare() != SQLResultOk) + return 0; + whitelistWildcardStatement.bindInt64(1, storageID); + + result = whitelistWildcardStatement.step(); + if (result != SQLResultRow) + LOG_ERROR("Could not load cache online whitelist wildcard flag, error \"%s\"", m_database.lastErrorMsg()); + + cache->setAllowsAllNetworkRequests(whitelistWildcardStatement.getColumnInt64(0)); + + if (whitelistWildcardStatement.step() != SQLResultDone) + LOG_ERROR("Too many rows for online whitelist wildcard flag"); + + // Load fallback URLs. + SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?"); + if (fallbackStatement.prepare() != SQLResultOk) + return 0; + fallbackStatement.bindInt64(1, storageID); + + FallbackURLVector fallbackURLs; + while ((result = fallbackStatement.step()) == SQLResultRow) + fallbackURLs.append(make_pair(KURL(ParsedURLString, fallbackStatement.getColumnText(0)), KURL(ParsedURLString, fallbackStatement.getColumnText(1)))); + + if (result != SQLResultDone) + LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg()); + + cache->setFallbackURLs(fallbackURLs); + + cache->setStorageID(storageID); + + return cache.release(); +} + +void ApplicationCacheStorage::remove(ApplicationCache* cache) +{ + if (!cache->storageID()) + return; + + openDatabase(false); + if (!m_database.isOpen()) + return; + + ASSERT(cache->group()); + ASSERT(cache->group()->storageID()); + + // All associated data will be deleted by database triggers. + SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?"); + if (statement.prepare() != SQLResultOk) + return; + + statement.bindInt64(1, cache->storageID()); + executeStatement(statement); + + cache->clearStorageID(); + + if (cache->group()->newestCache() == cache) { + // Currently, there are no triggers on the cache group, which is why the cache had to be removed separately above. + SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?"); + if (groupStatement.prepare() != SQLResultOk) + return; + + groupStatement.bindInt64(1, cache->group()->storageID()); + executeStatement(groupStatement); + + cache->group()->clearStorageID(); + } +} + +void ApplicationCacheStorage::empty() +{ + openDatabase(false); + + if (!m_database.isOpen()) + return; + + // Clear cache groups, caches, cache resources, and origins. + executeSQLCommand("DELETE FROM CacheGroups"); + executeSQLCommand("DELETE FROM Caches"); + executeSQLCommand("DELETE FROM Origins"); + + // 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, ApplicationCacheHost* cacheHost) +{ + ApplicationCache* cache = cacheHost->applicationCache(); + if (!cache) + return true; + + // Create a new cache. + RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create(); + + cacheCopy->setOnlineWhitelist(cache->onlineWhitelist()); + cacheCopy->setFallbackURLs(cache->fallbackURLs()); + + // 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(adoptPtr(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()); +} + +bool ApplicationCacheStorage::manifestURLs(Vector<KURL>* urls) +{ + ASSERT(urls); + openDatabase(false); + if (!m_database.isOpen()) + return false; + + SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups"); + + if (selectURLs.prepare() != SQLResultOk) + return false; + + while (selectURLs.step() == SQLResultRow) + urls->append(KURL(ParsedURLString, selectURLs.getColumnText(0))); + + return true; +} + +bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size) +{ + ASSERT(size); + openDatabase(false); + if (!m_database.isOpen()) + return false; + + SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindText(1, manifestURL); + + int result = statement.step(); + if (result == SQLResultDone) + return false; + + if (result != SQLResultRow) { + LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg()); + return false; + } + + *size = statement.getColumnInt64(0); + return true; +} + +bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL) +{ + SQLiteTransaction deleteTransaction(m_database); + // Check to see if the group is in memory. + ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL); + if (group) + cacheGroupMadeObsolete(group); + else { + // The cache group is not in memory, so remove it from the disk. + openDatabase(false); + if (!m_database.isOpen()) + return false; + + SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?"); + if (idStatement.prepare() != SQLResultOk) + return false; + + idStatement.bindText(1, manifestURL); + + int result = idStatement.step(); + if (result == SQLResultDone) + return false; + + if (result != SQLResultRow) { + LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg()); + return false; + } + + int64_t groupId = idStatement.getColumnInt64(0); + + SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?"); + if (cacheStatement.prepare() != SQLResultOk) + return false; + + SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?"); + if (groupStatement.prepare() != SQLResultOk) + return false; + + cacheStatement.bindInt64(1, groupId); + executeStatement(cacheStatement); + groupStatement.bindInt64(1, groupId); + executeStatement(groupStatement); + } + + deleteTransaction.commit(); + return true; +} + +void ApplicationCacheStorage::vacuumDatabaseFile() +{ + openDatabase(false); + if (!m_database.isOpen()) + return; + + m_database.runVacuumCommand(); +} + +void ApplicationCacheStorage::checkForMaxSizeReached() +{ + if (m_database.lastError() == SQLResultFull) + m_isMaximumSizeReached = true; +} + +ApplicationCacheStorage::ApplicationCacheStorage() + : m_maximumSize(ApplicationCacheStorage::noQuota()) + , m_isMaximumSizeReached(false) + , m_defaultOriginQuota(ApplicationCacheStorage::noQuota()) +{ +} + +ApplicationCacheStorage& cacheStorage() +{ + DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ()); + + return storage; +} + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/Source/WebCore/loader/appcache/ApplicationCacheStorage.h b/Source/WebCore/loader/appcache/ApplicationCacheStorage.h new file mode 100644 index 0000000..7db34e6 --- /dev/null +++ b/Source/WebCore/loader/appcache/ApplicationCacheStorage.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2008, 2010 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 <wtf/HashCountedSet.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class ApplicationCache; +class ApplicationCacheGroup; +class ApplicationCacheHost; +class ApplicationCacheResource; +class KURL; +template <class T> +class StorageIDJournal; +class SecurityOrigin; + +class ApplicationCacheStorage : public Noncopyable { +public: + enum FailureReason { + OriginQuotaReached, + TotalQuotaReached, + DiskOrOperationFailure + }; + + void setCacheDirectory(const String&); + const String& cacheDirectory() const; + + void setMaximumSize(int64_t size); + int64_t maximumSize() const; + bool isMaximumSizeReached() const; + int64_t spaceNeeded(int64_t cacheToSave); + + int64_t defaultOriginQuota() const { return m_defaultOriginQuota; } + void setDefaultOriginQuota(int64_t quota); + bool usageForOrigin(const SecurityOrigin*, int64_t& usage); + bool quotaForOrigin(const SecurityOrigin*, int64_t& quota); + bool remainingSizeForOriginExcludingCache(const SecurityOrigin*, ApplicationCache*, int64_t& remainingSize); + bool storeUpdatedQuotaForOrigin(const SecurityOrigin*, int64_t quota); + + ApplicationCacheGroup* cacheGroupForURL(const KURL&); // Cache to load a main resource from. + ApplicationCacheGroup* fallbackCacheGroupForURL(const KURL&); // Cache that has a fallback entry to load a main resource from if normal loading fails. + + ApplicationCacheGroup* findOrCreateCacheGroup(const KURL& manifestURL); + void cacheGroupDestroyed(ApplicationCacheGroup*); + void cacheGroupMadeObsolete(ApplicationCacheGroup*); + + bool storeNewestCache(ApplicationCacheGroup*, ApplicationCache* oldCache, FailureReason& failureReason); + bool storeNewestCache(ApplicationCacheGroup*); // Updates the cache group, but doesn't remove old cache. + bool store(ApplicationCacheResource*, ApplicationCache*); + bool storeUpdatedType(ApplicationCacheResource*, ApplicationCache*); + + // Removes the group if the cache to be removed is the newest one (so, storeNewestCache() needs to be called beforehand when updating). + void remove(ApplicationCache*); + + void empty(); + + static bool storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost*); + + bool manifestURLs(Vector<KURL>* urls); + bool cacheGroupSize(const String& manifestURL, int64_t* size); + bool deleteCacheGroup(const String& manifestURL); + void vacuumDatabaseFile(); + + static int64_t unknownQuota() { return -1; } + static int64_t noQuota() { return std::numeric_limits<int64_t>::max(); } +private: + ApplicationCacheStorage(); + PassRefPtr<ApplicationCache> loadCache(unsigned storageID); + ApplicationCacheGroup* loadCacheGroup(const KURL& manifestURL); + + typedef StorageIDJournal<ApplicationCacheResource> ResourceStorageIDJournal; + typedef StorageIDJournal<ApplicationCacheGroup> GroupStorageIDJournal; + + bool store(ApplicationCacheGroup*, GroupStorageIDJournal*); + bool store(ApplicationCache*, ResourceStorageIDJournal*); + bool store(ApplicationCacheResource*, unsigned cacheStorageID); + + bool ensureOriginRecord(const SecurityOrigin*); + + void loadManifestHostHashes(); + + void verifySchemaVersion(); + + void openDatabase(bool createIfDoesNotExist); + + bool executeStatement(SQLiteStatement&); + bool executeSQLCommand(const String&); + + void checkForMaxSizeReached(); + + String m_cacheDirectory; + String m_cacheFile; + + int64_t m_maximumSize; + bool m_isMaximumSizeReached; + + int64_t m_defaultOriginQuota; + + SQLiteDatabase m_database; + + // In order to quickly determine if a given resource exists in an application cache, + // we keep a hash set of the hosts of the manifest URLs of all non-obsolete cache groups. + HashCountedSet<unsigned, AlreadyHashed> m_cacheHostSet; + + typedef HashMap<String, ApplicationCacheGroup*> CacheGroupMap; + CacheGroupMap m_cachesInMemory; // Excludes obsolete cache groups. + + friend ApplicationCacheStorage& cacheStorage(); +}; + +ApplicationCacheStorage& cacheStorage(); + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheStorage_h diff --git a/Source/WebCore/loader/appcache/DOMApplicationCache.cpp b/Source/WebCore/loader/appcache/DOMApplicationCache.cpp new file mode 100644 index 0000000..b9297b1 --- /dev/null +++ b/Source/WebCore/loader/appcache/DOMApplicationCache.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2008, 2009 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 "ApplicationCacheHost.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) +{ + ApplicationCacheHost* cacheHost = applicationCacheHost(); + if (cacheHost) + cacheHost->setDOMApplicationCache(this); +} + +void DOMApplicationCache::disconnectFrame() +{ + ApplicationCacheHost* cacheHost = applicationCacheHost(); + if (cacheHost) + cacheHost->setDOMApplicationCache(0); + m_frame = 0; +} + +ApplicationCacheHost* DOMApplicationCache::applicationCacheHost() const +{ + if (!m_frame || !m_frame->loader()->documentLoader()) + return 0; + return m_frame->loader()->documentLoader()->applicationCacheHost(); +} + +unsigned short DOMApplicationCache::status() const +{ + ApplicationCacheHost* cacheHost = applicationCacheHost(); + if (!cacheHost) + return ApplicationCacheHost::UNCACHED; + return cacheHost->status(); +} + +void DOMApplicationCache::update(ExceptionCode& ec) +{ + ApplicationCacheHost* cacheHost = applicationCacheHost(); + if (!cacheHost || !cacheHost->update()) + ec = INVALID_STATE_ERR; +} + +void DOMApplicationCache::swapCache(ExceptionCode& ec) +{ + ApplicationCacheHost* cacheHost = applicationCacheHost(); + if (!cacheHost || !cacheHost->swapCache()) + ec = INVALID_STATE_ERR; +} + +ScriptExecutionContext* DOMApplicationCache::scriptExecutionContext() const +{ + if (m_frame) + return m_frame->document(); + return 0; +} + +const AtomicString& DOMApplicationCache::toEventType(ApplicationCacheHost::EventID id) +{ + switch (id) { + case ApplicationCacheHost::CHECKING_EVENT: + return eventNames().checkingEvent; + case ApplicationCacheHost::ERROR_EVENT: + return eventNames().errorEvent; + case ApplicationCacheHost::NOUPDATE_EVENT: + return eventNames().noupdateEvent; + case ApplicationCacheHost::DOWNLOADING_EVENT: + return eventNames().downloadingEvent; + case ApplicationCacheHost::PROGRESS_EVENT: + return eventNames().progressEvent; + case ApplicationCacheHost::UPDATEREADY_EVENT: + return eventNames().updatereadyEvent; + case ApplicationCacheHost::CACHED_EVENT: + return eventNames().cachedEvent; + case ApplicationCacheHost::OBSOLETE_EVENT: + return eventNames().obsoleteEvent; + } + ASSERT_NOT_REACHED(); + return eventNames().errorEvent; +} + +EventTargetData* DOMApplicationCache::eventTargetData() +{ + return &m_eventTargetData; +} + +EventTargetData* DOMApplicationCache::ensureEventTargetData() +{ + return &m_eventTargetData; +} + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/Source/WebCore/loader/appcache/DOMApplicationCache.h b/Source/WebCore/loader/appcache/DOMApplicationCache.h new file mode 100644 index 0000000..2a806fa --- /dev/null +++ b/Source/WebCore/loader/appcache/DOMApplicationCache.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2008, 2009 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 "ApplicationCacheHost.h" +#include "EventListener.h" +#include "EventNames.h" +#include "EventTarget.h" +#include <wtf/Forward.h> +#include <wtf/HashMap.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> +#include <wtf/text/AtomicStringHash.h> + +namespace WebCore { + +class Frame; +class KURL; + +class DOMApplicationCache : public RefCounted<DOMApplicationCache>, public EventTarget { +public: + static PassRefPtr<DOMApplicationCache> create(Frame* frame) { return adoptRef(new DOMApplicationCache(frame)); } + ~DOMApplicationCache() { ASSERT(!m_frame); } + + Frame* frame() const { return m_frame; } + void disconnectFrame(); + + unsigned short status() const; + void update(ExceptionCode&); + void swapCache(ExceptionCode&); + + // EventTarget impl + + using RefCounted<DOMApplicationCache>::ref; + using RefCounted<DOMApplicationCache>::deref; + + // Explicitly named attribute event listener helpers + + DEFINE_ATTRIBUTE_EVENT_LISTENER(checking); + DEFINE_ATTRIBUTE_EVENT_LISTENER(error); + DEFINE_ATTRIBUTE_EVENT_LISTENER(noupdate); + DEFINE_ATTRIBUTE_EVENT_LISTENER(downloading); + DEFINE_ATTRIBUTE_EVENT_LISTENER(progress); + DEFINE_ATTRIBUTE_EVENT_LISTENER(updateready); + DEFINE_ATTRIBUTE_EVENT_LISTENER(cached); + DEFINE_ATTRIBUTE_EVENT_LISTENER(obsolete); + + virtual ScriptExecutionContext* scriptExecutionContext() const; + DOMApplicationCache* toDOMApplicationCache() { return this; } + + static const AtomicString& toEventType(ApplicationCacheHost::EventID id); + +private: + DOMApplicationCache(Frame*); + + virtual void refEventTarget() { ref(); } + virtual void derefEventTarget() { deref(); } + virtual EventTargetData* eventTargetData(); + virtual EventTargetData* ensureEventTargetData(); + + ApplicationCacheHost* applicationCacheHost() const; + + Frame* m_frame; + EventTargetData m_eventTargetData; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // DOMApplicationCache_h diff --git a/Source/WebCore/loader/appcache/DOMApplicationCache.idl b/Source/WebCore/loader/appcache/DOMApplicationCache.idl new file mode 100644 index 0000000..9113ffa --- /dev/null +++ b/Source/WebCore/loader/appcache/DOMApplicationCache.idl @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008, 2009 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, + EventTarget, + OmitConstructor, + DontCheckEnums + ] 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; + const unsigned short OBSOLETE = 5; + readonly attribute unsigned short status; + + void update() + raises(DOMException); + void swapCache() + raises(DOMException); + + // events + attribute EventListener onchecking; + attribute EventListener onerror; + attribute EventListener onnoupdate; + attribute EventListener ondownloading; + attribute EventListener onprogress; + attribute EventListener onupdateready; + attribute EventListener oncached; + attribute EventListener onobsolete; + + // EventTarget interface + void addEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + void removeEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + boolean dispatchEvent(in Event evt) + raises(EventException); + }; + +} diff --git a/Source/WebCore/loader/appcache/ManifestParser.cpp b/Source/WebCore/loader/appcache/ManifestParser.cpp new file mode 100644 index 0000000..f58a55d --- /dev/null +++ b/Source/WebCore/loader/appcache/ManifestParser.cpp @@ -0,0 +1,188 @@ +/* + * 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 "TextResourceDecoder.h" + +using namespace std; + +namespace WebCore { + +enum Mode { Explicit, Fallback, OnlineWhitelist, Unknown }; + +bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest& manifest) +{ + ASSERT(manifest.explicitURLs.isEmpty()); + ASSERT(manifest.onlineWhitelistedURLs.isEmpty()); + ASSERT(manifest.fallbackURLs.isEmpty()); + manifest.allowAllNetworkRequests = false; + + Mode mode = Explicit; + + RefPtr<TextResourceDecoder> decoder = TextResourceDecoder::create("text/cache-manifest", "UTF-8"); + String s = decoder->decode(data, length); + s += decoder->flush(); + + // Look for the magic signature: "^\xFEFF?CACHE MANIFEST[ \t]?" (the BOM is removed by TextResourceDecoder). + // Example: "CACHE MANIFEST #comment" is a valid signature. + // Example: "CACHE MANIFEST;V2" is not. + if (!s.startsWith("CACHE MANIFEST")) + return false; + + const UChar* end = s.characters() + s.length(); + const UChar* p = s.characters() + 14; // "CACHE MANIFEST" is 14 characters. + + if (p < end && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') + return false; + + // Skip to the end of the line. + while (p < end && *p != '\r' && *p != '\n') + p++; + + 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 (line.endsWith(":")) + mode = Unknown; + else if (mode == Unknown) + continue; + else if (mode == Explicit || mode == OnlineWhitelist) { + const UChar* p = line.characters(); + const UChar* lineEnd = p + line.length(); + + // Look for whitespace separating the URL from subsequent ignored tokens. + while (p < lineEnd && *p != '\t' && *p != ' ') + p++; + + if (mode == OnlineWhitelist && p - line.characters() == 1 && *line.characters() == '*') { + // Wildcard was found. + manifest.allowAllNetworkRequests = true; + continue; + } + + KURL url(manifestURL, String(line.characters(), p - line.characters())); + + if (!url.isValid()) + continue; + + if (url.hasFragmentIdentifier()) + url.removeFragmentIdentifier(); + + if (!equalIgnoringCase(url.protocol(), manifestURL.protocol())) + continue; + + if (mode == Explicit && manifestURL.protocolIs("https") && !protocolHostAndPortAreEqual(manifestURL, url)) + continue; + + if (mode == Explicit) + manifest.explicitURLs.add(url.string()); + else + manifest.onlineWhitelistedURLs.append(url); + + } 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; + if (namespaceURL.hasFragmentIdentifier()) + namespaceURL.removeFragmentIdentifier(); + + if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL)) + continue; + + // Skip whitespace separating fallback namespace from URL. + while (p < lineEnd && (*p == '\t' || *p == ' ')) + p++; + + // Look for whitespace separating the URL from subsequent ignored tokens. + const UChar* fallbackStart = p; + while (p < lineEnd && *p != '\t' && *p != ' ') + p++; + + KURL fallbackURL(manifestURL, String(fallbackStart, p - fallbackStart)); + if (!fallbackURL.isValid()) + continue; + if (fallbackURL.hasFragmentIdentifier()) + fallbackURL.removeFragmentIdentifier(); + + if (!protocolHostAndPortAreEqual(manifestURL, fallbackURL)) + continue; + + manifest.fallbackURLs.append(make_pair(namespaceURL, fallbackURL)); + } else + ASSERT_NOT_REACHED(); + } + + return true; +} + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/Source/WebCore/loader/appcache/ManifestParser.h b/Source/WebCore/loader/appcache/ManifestParser.h new file mode 100644 index 0000000..f0369ee --- /dev/null +++ b/Source/WebCore/loader/appcache/ManifestParser.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. + * + * 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 "ApplicationCache.h" + +namespace WebCore { + + class KURL; + + struct Manifest { + Vector<KURL> onlineWhitelistedURLs; + HashSet<String> explicitURLs; + FallbackURLVector fallbackURLs; + bool allowAllNetworkRequests; // Wildcard found in NETWORK section. + }; + + bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest&); + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ManifestParser_h diff --git a/Source/WebCore/loader/archive/Archive.h b/Source/WebCore/loader/archive/Archive.h new file mode 100644 index 0000000..af3d8b1 --- /dev/null +++ b/Source/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/Source/WebCore/loader/archive/ArchiveFactory.cpp b/Source/WebCore/loader/archive/ArchiveFactory.cpp new file mode 100644 index 0000000..5926690 --- /dev/null +++ b/Source/WebCore/loader/archive/ArchiveFactory.cpp @@ -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. + * 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" + +#include "MIMETypeRegistry.h" +#include "PlatformString.h" + +#if PLATFORM(CF) && !PLATFORM(QT) +#include "LegacyWebArchive.h" +#elif PLATFORM(ANDROID) +#include "WebArchiveAndroid.h" +#endif + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/StdLibExtras.h> + +namespace WebCore { + +typedef PassRefPtr<Archive> RawDataCreationFunction(SharedBuffer*); +typedef HashMap<String, RawDataCreationFunction*, CaseFoldingHash> ArchiveMIMETypesMap; + +// 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 ArchiveMIMETypesMap& archiveMIMETypes() +{ + DEFINE_STATIC_LOCAL(ArchiveMIMETypesMap, mimeTypes, ()); + static bool initialized = false; + + if (initialized) + return mimeTypes; + +#if PLATFORM(CF) && !PLATFORM(QT) + mimeTypes.set("application/x-webarchive", archiveFactoryCreate<LegacyWebArchive>); +#elif PLATFORM(ANDROID) + mimeTypes.set("application/x-webarchive-xml", archiveFactoryCreate<WebArchiveAndroid>); +#endif + + initialized = true; + return mimeTypes; +} + +bool ArchiveFactory::isArchiveMimeType(const String& mimeType) +{ + return !mimeType.isEmpty() && archiveMIMETypes().contains(mimeType); +} + +PassRefPtr<Archive> ArchiveFactory::create(SharedBuffer* data, const String& mimeType) +{ + RawDataCreationFunction* function = mimeType.isEmpty() ? 0 : archiveMIMETypes().get(mimeType); + return function ? function(data) : 0; +} + +void ArchiveFactory::registerKnownArchiveMIMETypes() +{ + HashSet<String>& mimeTypes = MIMETypeRegistry::getSupportedNonImageMIMETypes(); + ArchiveMIMETypesMap::iterator i = archiveMIMETypes().begin(); + ArchiveMIMETypesMap::iterator end = archiveMIMETypes().end(); + + for (; i != end; ++i) + mimeTypes.add(i->first); +} + +} diff --git a/Source/WebCore/loader/archive/ArchiveFactory.h b/Source/WebCore/loader/archive/ArchiveFactory.h new file mode 100644 index 0000000..c3b9464 --- /dev/null +++ b/Source/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/Forward.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class SharedBuffer; + +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/Source/WebCore/loader/archive/ArchiveResource.cpp b/Source/WebCore/loader/archive/ArchiveResource.cpp new file mode 100644 index 0000000..7dedc93 --- /dev/null +++ b/Source/WebCore/loader/archive/ArchiveResource.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008, 2010 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 { + +inline ArchiveResource::ArchiveResource(PassRefPtr<SharedBuffer> data, const KURL& url, const String& mimeType, const String& textEncoding, const String& frameName, const ResourceResponse& response) + : SubstituteResource(url, response, data) + , m_mimeType(mimeType) + , m_textEncoding(textEncoding) + , m_frameName(frameName) + , m_shouldIgnoreWhenUnarchiving(false) +{ +} + +PassRefPtr<ArchiveResource> ArchiveResource::create(PassRefPtr<SharedBuffer> data, const KURL& url, const String& mimeType, const String& textEncoding, const String& frameName, const ResourceResponse& response) +{ + if (!data) + return 0; + if (response.isNull()) { + unsigned dataSize = data->size(); + return adoptRef(new ArchiveResource(data, url, mimeType, textEncoding, frameName, + ResourceResponse(url, mimeType, dataSize, textEncoding, String()))); + } + return adoptRef(new ArchiveResource(data, url, mimeType, textEncoding, frameName, response)); +} + +PassRefPtr<ArchiveResource> ArchiveResource::create(PassRefPtr<SharedBuffer> data, const KURL& url, const ResourceResponse& response) +{ + return create(data, url, response.mimeType(), response.textEncodingName(), String(), response); +} + +} diff --git a/Source/WebCore/loader/archive/ArchiveResource.h b/Source/WebCore/loader/archive/ArchiveResource.h new file mode 100644 index 0000000..97d6e32 --- /dev/null +++ b/Source/WebCore/loader/archive/ArchiveResource.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008, 2010 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" + +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, + const ResourceResponse& = 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 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/Source/WebCore/loader/archive/ArchiveResourceCollection.cpp b/Source/WebCore/loader/archive/ArchiveResourceCollection.cpp new file mode 100644 index 0000000..6eb1237 --- /dev/null +++ b/Source/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/Source/WebCore/loader/archive/ArchiveResourceCollection.h b/Source/WebCore/loader/archive/ArchiveResourceCollection.h new file mode 100644 index 0000000..9d630d1 --- /dev/null +++ b/Source/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 : public 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/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp b/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp new file mode 100644 index 0000000..ddd564e --- /dev/null +++ b/Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2008, 2009 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 "MemoryCache.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "HTMLFrameOwnerElement.h" +#include "HTMLNames.h" +#include "IconDatabase.h" +#include "Image.h" +#include "KURLHash.h" +#include "Logging.h" +#include "markup.h" +#include "Node.h" +#include "Range.h" +#include "SelectionController.h" +#include "SharedBuffer.h" +#include <wtf/text/CString.h> +#include <wtf/text/StringConcatenate.h> +#include <wtf/ListHashSet.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"); + +RetainPtr<CFDictionaryRef> LegacyWebArchive::createPropertyListRepresentation(ArchiveResource* resource, MainResourceStatus isMainResource) +{ + if (!resource) { + // The property list representation of a null/empty WebResource has the following 3 objects stored as nil. + // FIXME: 0 is not serializable. Presumably we need to use kCFNull here instead for compatibility. + // FIXME: But why do we need to support a resource of 0? Who relies on that? + 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() || isMainResource) { + 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 (!isMainResource) { + RetainPtr<CFDataRef> resourceResponseData = createPropertyListRepresentation(resource->response()); + if (resourceResponseData) + CFDictionarySetValue(propertyList.get(), LegacyWebArchiveResourceResponseKey, resourceResponseData.get()); + } + + return propertyList; +} + +RetainPtr<CFDictionaryRef> LegacyWebArchive::createPropertyListRepresentation(Archive* archive) +{ + RetainPtr<CFMutableDictionaryRef> propertyList(AdoptCF, CFDictionaryCreateMutable(0, 3, 0, &kCFTypeDictionaryValueCallBacks)); + + RetainPtr<CFDictionaryRef> mainResourceDict = createPropertyListRepresentation(archive->mainResource(), MainResource); + ASSERT(mainResourceDict); + 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 = createPropertyListRepresentation(subresources[i].get(), Subresource); + 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 = createPropertyListRepresentation(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; +} + +ResourceResponse LegacyWebArchive::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" web archive that we + // can parse well in a cross platform manner If it doesn't exist, we will assume this is an "old" web archive with, + // NSURLResponse objects in it and parse the ResourceResponse as such. + if (!responseDataType) + return createResourceResponseFromMacArchivedData(data); + + // FIXME: Parse the "new" format that the above comment references here. This format doesn't exist yet. + return ResourceResponse(); +} + +PassRefPtr<ArchiveResource> LegacyWebArchive::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::wrapCFData(resourceData), KURL(KURL(), url), mimeType, textEncoding, frameName, response); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create() +{ + return adoptRef(new LegacyWebArchive); +} + +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(); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::create(SharedBuffer* data) +{ + LOG(Archives, "LegacyWebArchive - Creating from raw data"); + + RefPtr<LegacyWebArchive> archive = create(); + + ASSERT(data); + if (!data) + return 0; + + RetainPtr<CFDataRef> cfData(AdoptCF, data->createCFData()); + if (!cfData) + return 0; + + 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 0; + } + + if (CFGetTypeID(plist.get()) != CFDictionaryGetTypeID()) { + LOG(Archives, "LegacyWebArchive - Archive property list is not the expected CFDictionary, aborting invalid WebArchive"); + return 0; + } + + if (!archive->extract(plist.get())) + return 0; + + return archive.release(); +} + +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 = createPropertyListRepresentation(this); + ASSERT(propertyList); + if (!propertyList) { + LOG(Archives, "LegacyWebArchive - Failed to create property list for archive, returning no data"); + return 0; + } + + RetainPtr<CFWriteStreamRef> stream(AdoptCF, CFWriteStreamCreateWithAllocatedBuffers(0, 0)); + + CFWriteStreamOpen(stream.get()); + CFPropertyListWriteToStream(propertyList.get(), stream.get(), kCFPropertyListBinaryFormat_v1_0, 0); + + RetainPtr<CFDataRef> plistData(AdoptCF, static_cast<CFDataRef>(CFWriteStreamCopyProperty(stream.get(), kCFStreamPropertyDataWritten))); + ASSERT(plistData); + + CFWriteStreamClose(stream.get()); + + if (!plistData) { + LOG(Archives, "LegacyWebArchive - Failed to convert property list into raw data, returning no data"); + return 0; + } + + return plistData; +} + +#if !PLATFORM(MAC) + +ResourceResponse LegacyWebArchive::createResourceResponseFromMacArchivedData(CFDataRef responseData) +{ + // FIXME: If is is possible to parse in a serialized NSURLResponse manually, without using + // NSKeyedUnarchiver, manipulating plists directly, then we want to do that here. + // Until then, this can be done on Mac only. + return ResourceResponse(); +} + +RetainPtr<CFDataRef> LegacyWebArchive::createPropertyListRepresentation(const ResourceResponse& response) +{ + // FIXME: Write out the "new" format described in createResourceResponseFromPropertyListData once we invent it. + 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 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, const 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(ParsedURLString, ""); + + PassRefPtr<ArchiveResource> mainResource = ArchiveResource::create(utf8Buffer(markupString), responseURL, response.mimeType(), "UTF-8", frame->tree()->uniqueName()); + + Vector<PassRefPtr<LegacyWebArchive> > subframeArchives; + Vector<PassRefPtr<ArchiveResource> > subresources; + HashSet<KURL> uniqueSubresources; + + size_t nodesSize = nodes.size(); + for (size_t i = 0; i < nodesSize; ++i) { + Node* node = nodes[i]; + Frame* childFrame; + if ((node->hasTagName(HTMLNames::frameTag) || node->hasTagName(HTMLNames::iframeTag) || node->hasTagName(HTMLNames::objectTag)) && + (childFrame = static_cast<HTMLFrameOwnerElement*>(node)->contentFrame())) { + RefPtr<LegacyWebArchive> subframeArchive = create(childFrame->document()); + + if (subframeArchive) + subframeArchives.append(subframeArchive); + else + LOG_ERROR("Unabled to archive subframe %s", childFrame->tree()->uniqueName().string().utf8().data()); + } else { + ListHashSet<KURL> subresourceURLs; + node->getSubresourceURLs(subresourceURLs); + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + ListHashSet<KURL>::iterator iterEnd = subresourceURLs.end(); + for (ListHashSet<KURL>::iterator iter = subresourceURLs.begin(); iter != iterEnd; ++iter) { + const KURL& subresourceURL = *iter; + if (uniqueSubresources.contains(subresourceURL)) + continue; + + uniqueSubresources.add(subresourceURL); + + RefPtr<ArchiveResource> resource = documentLoader->subresource(subresourceURL); + if (resource) { + subresources.append(resource.release()); + continue; + } + + CachedResource *cachedResource = cache()->resourceForURL(subresourceURL); + if (cachedResource) { + resource = ArchiveResource::create(cachedResource->data(), subresourceURL, cachedResource->response()); + if (resource) { + subresources.append(resource.release()); + continue; + } + } + + // FIXME: should do something better than spew to console here + LOG_ERROR("Failed to archive subresource for %s", subresourceURL.string().utf8().data()); + } + } + } + + // Add favicon if one exists for this page, if we are archiving the entire page. + if (nodesSize && nodes[0]->isDocumentNode() && iconDatabase() && iconDatabase()->isEnabled()) { + const String& iconURL = iconDatabase()->iconURLForPageURL(responseURL); + if (!iconURL.isEmpty() && iconDatabase()->iconDataKnownForIconURL(iconURL)) { + if (Image* iconImage = iconDatabase()->iconForPageURL(responseURL, IntSize(16, 16))) { + if (RefPtr<ArchiveResource> resource = ArchiveResource::create(iconImage->data(), KURL(ParsedURLString, iconURL), "image/x-icon", "", "")) + subresources.append(resource.release()); + } + } + } + + return create(mainResource, subresources, subframeArchives); +} + +PassRefPtr<LegacyWebArchive> LegacyWebArchive::createFromSelection(Frame* frame) +{ + if (!frame) + return 0; + + RefPtr<Range> selectionRange = frame->selection()->toNormalizedRange(); + Vector<Node*> nodeList; + String markupString = frame->documentTypeString() + createMarkup(selectionRange.get(), &nodeList, AnnotateForInterchange); + + RefPtr<LegacyWebArchive> archive = create(markupString, frame, nodeList); + + if (!frame->document() || !frame->document()->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 = makeString("<iframe frameborder=\"no\" marginwidth=\"0\" marginheight=\"0\" width=\"98%%\" height=\"98%%\" src=\"", + frame->loader()->documentLoader()->response().url().string(), "\"></iframe>"); + 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 = create(iframeResource.release(), subresources, subframeArchives); + + return archive.release(); +} + +} diff --git a/Source/WebCore/loader/archive/cf/LegacyWebArchive.h b/Source/WebCore/loader/archive/cf/LegacyWebArchive.h new file mode 100644 index 0000000..8c8f2e4 --- /dev/null +++ b/Source/WebCore/loader/archive/cf/LegacyWebArchive.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2008, 2009 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" + +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*); + static PassRefPtr<LegacyWebArchive> create(Range*); + + RetainPtr<CFDataRef> rawDataRepresentation(); + +private: + LegacyWebArchive() { } + + enum MainResourceStatus { Subresource, MainResource }; + + static PassRefPtr<LegacyWebArchive> create(const String& markupString, Frame*, const Vector<Node*>& nodes); + static PassRefPtr<ArchiveResource> createResource(CFDictionaryRef); + static ResourceResponse createResourceResponseFromMacArchivedData(CFDataRef); + static ResourceResponse createResourceResponseFromPropertyListData(CFDataRef, CFStringRef responseDataType); + static RetainPtr<CFDataRef> createPropertyListRepresentation(const ResourceResponse&); + static RetainPtr<CFDictionaryRef> createPropertyListRepresentation(Archive*); + static RetainPtr<CFDictionaryRef> createPropertyListRepresentation(ArchiveResource*, MainResourceStatus); + + bool extract(CFDictionaryRef); +}; + +} + +#endif // Archive diff --git a/Source/WebCore/loader/archive/cf/LegacyWebArchiveMac.mm b/Source/WebCore/loader/archive/cf/LegacyWebArchiveMac.mm new file mode 100644 index 0000000..6a35753 --- /dev/null +++ b/Source/WebCore/loader/archive/cf/LegacyWebArchiveMac.mm @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008, 2009 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 NSString * const LegacyWebArchiveResourceResponseKey = @"WebResourceResponse"; + +// FIXME: If is is possible to parse in a serialized NSURLResponse manually, without using +// NSKeyedUnarchiver, manipulating plists directly, we would prefer to do that instead. +ResourceResponse LegacyWebArchive::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> LegacyWebArchive::createPropertyListRepresentation(const ResourceResponse& response) +{ + NSURLResponse *nsResponse = response.nsURLResponse(); + ASSERT(nsResponse); + if (!nsResponse) + return 0; + + CFMutableDataRef responseData = CFDataCreateMutable(0, 0); + + NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:(NSMutableData *)responseData]; + [archiver encodeObject:nsResponse forKey:LegacyWebArchiveResourceResponseKey]; + [archiver finishEncoding]; + [archiver release]; + + return RetainPtr<CFDataRef>(AdoptCF, responseData); +} + +} diff --git a/Source/WebCore/loader/cache/CachePolicy.h b/Source/WebCore/loader/cache/CachePolicy.h new file mode 100644 index 0000000..0b9010b --- /dev/null +++ b/Source/WebCore/loader/cache/CachePolicy.h @@ -0,0 +1,41 @@ +/* + * 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, + CachePolicyRevalidate, + CachePolicyReload, + CachePolicyHistoryBuffer + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp b/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp new file mode 100644 index 0000000..ae7a03c --- /dev/null +++ b/Source/WebCore/loader/cache/CachedCSSStyleSheet.cpp @@ -0,0 +1,151 @@ +/* + 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 "MemoryCache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "HTTPParsers.h" +#include "TextResourceDecoder.h" +#include "SharedBuffer.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::didAddClient(CachedResourceClient *c) +{ + if (!isLoading()) + c->setCSSStyleSheet(m_url, m_response.url(), m_decoder->encoding().name(), this); +} + +void CachedCSSStyleSheet::allClientsRemoved() +{ + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() && isSafeToMakePurgeable()) + makePurgeable(true); +} + +void CachedCSSStyleSheet::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedCSSStyleSheet::encoding() const +{ + return m_decoder->encoding().name(); +} + +const String CachedCSSStyleSheet::sheetText(bool enforceMIMEType, bool* hasValidMIMEType) const +{ + ASSERT(!isPurgeable()); + + if (!m_data || m_data->isEmpty() || !canUseSheet(enforceMIMEType, hasValidMIMEType)) + return String(); + + if (!m_decodedSheetText.isNull()) + return m_decodedSheetText; + + // Don't cache the decoded text, regenerating is cheap and it can use quite a bit of memory + String sheetText = m_decoder->decode(m_data->data(), m_data->size()); + sheetText += m_decoder->flush(); + return sheetText; +} + +void CachedCSSStyleSheet::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + // Decode the data to find out the encoding and keep the sheet text around during checkNotify() + if (m_data) { + m_decodedSheetText = m_decoder->decode(m_data->data(), m_data->size()); + m_decodedSheetText += m_decoder->flush(); + } + setLoading(false); + checkNotify(); + // Clear the decoded text as it is unlikely to be needed immediately again and is cheap to regenerate. + m_decodedSheetText = String(); +} + +void CachedCSSStyleSheet::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->setCSSStyleSheet(m_url, m_response.url(), m_decoder->encoding().name(), this); +} + +void CachedCSSStyleSheet::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +bool CachedCSSStyleSheet::canUseSheet(bool enforceMIMEType, bool* hasValidMIMEType) const +{ + if (errorOccurred()) + return false; + + if (!enforceMIMEType && !hasValidMIMEType) + return true; + + // This check exactly matches Firefox. Note that we grab the Content-Type + // header directly because we want to see what the value is BEFORE content + // sniffing. Firefox does this by setting a "type hint" on the channel. + // This implementation should be observationally equivalent. + // + // This code defaults to allowing the stylesheet for non-HTTP protocols so + // folks can use standards mode for local HTML documents. + String mimeType = extractMIMETypeFromMediaType(response().httpHeaderField("Content-Type")); + bool typeOK = mimeType.isEmpty() || equalIgnoringCase(mimeType, "text/css") || equalIgnoringCase(mimeType, "application/x-unknown-content-type"); + if (hasValidMIMEType) + *hasValidMIMEType = typeOK; + if (!enforceMIMEType) + return true; + return typeOK; +} + +} diff --git a/Source/WebCore/loader/cache/CachedCSSStyleSheet.h b/Source/WebCore/loader/cache/CachedCSSStyleSheet.h new file mode 100644 index 0000000..a982e03 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedCSSStyleSheet.h @@ -0,0 +1,67 @@ +/* + 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, 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 CachedCSSStyleSheet_h +#define CachedCSSStyleSheet_h + +#include "CachedResource.h" +#include "TextEncoding.h" +#include <wtf/Vector.h> + +namespace WebCore { + + class CachedResourceLoader; + class TextResourceDecoder; + + class CachedCSSStyleSheet : public CachedResource { + public: + CachedCSSStyleSheet(const String& URL, const String& charset); + virtual ~CachedCSSStyleSheet(); + + const String sheetText(bool enforceMIMEType = true, bool* hasValidMIMEType = 0) const; + + virtual void didAddClient(CachedResourceClient*); + + virtual void allClientsRemoved(); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + void checkNotify(); + + private: + bool canUseSheet(bool enforceMIMEType, bool* hasValidMIMEType) const; + virtual PurgePriority purgePriority() const { return PurgeLast; } + + protected: + RefPtr<TextResourceDecoder> m_decoder; + String m_decodedSheetText; + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedFont.cpp b/Source/WebCore/loader/cache/CachedFont.cpp new file mode 100644 index 0000000..d6967bf --- /dev/null +++ b/Source/WebCore/loader/cache/CachedFont.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Torch Mobile, Inc. + * + * 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" + +#if PLATFORM(CG) || PLATFORM(QT) || PLATFORM(GTK) || (PLATFORM(CHROMIUM) && (OS(WINDOWS) || OS(LINUX) || OS(FREEBSD))) || PLATFORM(HAIKU) || OS(WINCE) || PLATFORM(ANDROID) || PLATFORM(BREWMP) +#define STORE_FONT_CUSTOM_PLATFORM_DATA +#endif + +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "CachedResourceLoader.h" +#include "FontPlatformData.h" +#include "MemoryCache.h" +#include "SharedBuffer.h" +#include "TextResourceDecoder.h" +#include <wtf/Vector.h> + +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA +#include "FontCustomPlatformData.h" +#endif + +#if ENABLE(SVG_FONTS) +#include "NodeList.h" +#include "SVGElement.h" +#include "SVGFontElement.h" +#include "SVGGElement.h" +#include "SVGNames.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() +{ +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA + delete m_fontData; +#endif +} + +void CachedFont::load(CachedResourceLoader*) +{ + // Don't load the file yet. Wait for an access before triggering the load. + setLoading(true); +} + +void CachedFont::didAddClient(CachedResourceClient* c) +{ + if (!isLoading()) + 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); + setLoading(false); + checkNotify(); +} + +void CachedFont::beginLoadIfNeeded(CachedResourceLoader* dl) +{ + if (!m_loadInitiated) { + m_loadInitiated = true; + dl->load(this, false); + } +} + +bool CachedFont::ensureCustomFontData() +{ +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA +#if ENABLE(SVG_FONTS) + ASSERT(!m_isSVGFont); +#endif + if (!m_fontData && !errorOccurred() && !isLoading() && m_data) { + m_fontData = createFontCustomPlatformData(m_data.get()); + if (!m_fontData) + setStatus(DecodeError); + } +#endif + return m_fontData; +} + +FontPlatformData CachedFont::platformDataFromCustomData(float size, bool bold, bool italic, FontOrientation orientation, FontRenderingMode renderingMode) +{ +#if ENABLE(SVG_FONTS) + if (m_externalSVGDocument) + return FontPlatformData(size, bold, italic); +#endif +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA + ASSERT(m_fontData); + return m_fontData->fontPlatformData(static_cast<int>(size), bold, italic, orientation, renderingMode); +#else + return FontPlatformData(); +#endif +} + +#if ENABLE(SVG_FONTS) +bool CachedFont::ensureSVGFontData() +{ + ASSERT(m_isSVGFont); + if (!m_externalSVGDocument && !errorOccurred() && !isLoading() && m_data) { + m_externalSVGDocument = SVGDocument::create(0, KURL()); + m_externalSVGDocument->open(); + + RefPtr<TextResourceDecoder> decoder = TextResourceDecoder::create("application/xml"); + m_externalSVGDocument->write(decoder->decode(m_data->data(), m_data->size())); + m_externalSVGDocument->write(decoder->flush()); + 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->getElementsByTagNameNS(SVGNames::fontTag.namespaceURI(), SVGNames::fontTag.localName()); + if (!list) + return 0; + + unsigned listLength = list->length(); + if (!listLength) + return 0; + +#ifndef NDEBUG + for (unsigned i = 0; i < listLength; ++i) { + ASSERT(list->item(i)); + ASSERT(list->item(i)->hasTagName(SVGNames::fontTag)); + } +#endif + + if (fontName.isEmpty()) + return static_cast<SVGFontElement*>(list->item(0)); + + for (unsigned i = 0; i < listLength; ++i) { + SVGFontElement* element = static_cast<SVGFontElement*>(list->item(i)); + if (element->getIdAttribute() == fontName) + return element; + } + + return 0; +} +#endif + +void CachedFont::allClientsRemoved() +{ +#ifdef STORE_FONT_CUSTOM_PLATFORM_DATA + if (m_fontData) { + delete m_fontData; + m_fontData = 0; + } +#endif +} + +void CachedFont::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->fontLoaded(this); +} + + +void CachedFont::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +} diff --git a/Source/WebCore/loader/cache/CachedFont.h b/Source/WebCore/loader/cache/CachedFont.h new file mode 100644 index 0000000..5814087 --- /dev/null +++ b/Source/WebCore/loader/cache/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 "FontOrientation.h" +#include "FontRenderingMode.h" +#include <wtf/Vector.h> + +#if ENABLE(SVG_FONTS) +#include "SVGElement.h" +#include "SVGDocument.h" +#endif + +namespace WebCore { + +class CachedResourceLoader; +class MemoryCache; +class FontPlatformData; +class SVGFontElement; + +struct FontCustomPlatformData; + +class CachedFont : public CachedResource { +public: + CachedFont(const String& url); + virtual ~CachedFont(); + + virtual void load(CachedResourceLoader* cachedResourceLoader); + + virtual void didAddClient(CachedResourceClient*); + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + virtual void allClientsRemoved(); + + void checkNotify(); + + void beginLoadIfNeeded(CachedResourceLoader* dl); + + bool ensureCustomFontData(); + FontPlatformData platformDataFromCustomData(float size, bool bold, bool italic, FontOrientation = Horizontal, 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 MemoryCache; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedImage.cpp b/Source/WebCore/loader/cache/CachedImage.cpp new file mode 100644 index 0000000..c550eec --- /dev/null +++ b/Source/WebCore/loader/cache/CachedImage.cpp @@ -0,0 +1,386 @@ +/* + 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 "MemoryCache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "CachedResourceLoader.h" +#include "CachedResourceRequest.h" +#include "Frame.h" +#include "FrameLoaderTypes.h" +#include "FrameView.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include <wtf/CurrentTime.h> +#include <wtf/StdLibExtras.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) +{ + setStatus(Unknown); +} + +CachedImage::CachedImage(Image* image) + : CachedResource(String(), ImageResource) + , m_image(image) + , m_decodedDataDeletionTimer(this, &CachedImage::decodedDataDeletionTimerFired) +{ + setStatus(Cached); + setLoading(false); +} + +CachedImage::~CachedImage() +{ +} + +void CachedImage::decodedDataDeletionTimerFired(Timer<CachedImage>*) +{ + ASSERT(!hasClients()); + destroyDecodedData(); +} + +void CachedImage::load(CachedResourceLoader* cachedResourceLoader) +{ +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + if (!cachedResourceLoader || (cachedResourceLoader->autoLoadImages() && !cachedResourceLoader->shouldBlockNetworkImage(m_url))) +#else + if (!cachedResourceLoader || cachedResourceLoader->autoLoadImages()) +#endif + CachedResource::load(cachedResourceLoader, true, DoSecurityCheck, true); + else + setLoading(false); +} + +void CachedImage::didAddClient(CachedResourceClient* c) +{ + if (m_decodedDataDeletionTimer.isActive()) + m_decodedDataDeletionTimer.stop(); + + if (m_data && !m_image && !errorOccurred()) { + createImage(); + m_image->setData(m_data, true); + } + + if (m_image && !m_image->isNull()) + c->imageChanged(this); + + if (!isLoading()) + c->notifyFinished(this); +} + +void CachedImage::allClientsRemoved() +{ + if (m_image && !errorOccurred()) + m_image->resetAnimation(); + if (double interval = cache()->deadDecodedDataDeletionInterval()) + m_decodedDataDeletionTimer.startOneShot(interval); +} + +static Image* brokenImage() +{ + DEFINE_STATIC_LOCAL(RefPtr<Image>, brokenImage, (Image::loadPlatformResource("missingImage"))); + return brokenImage.get(); +} + +Image* CachedImage::image() const +{ + ASSERT(!isPurgeable()); + + if (errorOccurred()) + return brokenImage(); + + if (m_image) + return m_image.get(); + + return Image::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 +{ + ASSERT(!isPurgeable()); + + 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 +{ + ASSERT(!isPurgeable()); + + 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(const IntRect* changeRect) +{ + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->imageChanged(this, changeRect); +} + +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) && !USE(WEBKIT_IMAGE_DECODERS) + 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(ANDROID) + m_image->setURL(url()); +#endif +} + +size_t CachedImage::maximumDecodedImageSize() +{ + Frame* frame = m_request ? m_request->cachedResourceLoader()->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(errorOccurred() ? status() : DecodeError); + if (inCache()) + cache()->remove(this); + return; + } + + // It would be nice to only redraw the decoded band of the image, but with the current design + // (decoding delayed until painting) that seems hard. + notifyObservers(); + + if (m_image) + setEncodedSize(m_image->data() ? m_image->data()->size() : 0); + } + + if (allDataReceived) { + setLoading(false); + checkNotify(); + } +} + +void CachedImage::error(CachedResource::Status status) +{ + clear(); + setStatus(status); + ASSERT(errorOccurred()); + m_data.clear(); + notifyObservers(); + setLoading(false); + checkNotify(); +} + +void CachedImage::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedImage::destroyDecodedData() +{ + bool canDeleteImage = !m_image || (m_image->hasOneRef() && m_image->isBitmapImage()); + if (isSafeToMakePurgeable() && canDeleteImage && !isLoading()) { + // Image refs the data buffer so we should not make it purgeable while the image is alive. + // Invoking addClient() will reconstruct the image object. + m_image = 0; + setDecodedSize(0); + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction()) + makePurgeable(true); + } else if (m_image && !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(); +} + +void CachedImage::changedInRect(const Image* image, const IntRect& rect) +{ + if (image == m_image) + notifyObservers(&rect); +} + +} //namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedImage.h b/Source/WebCore/loader/cache/CachedImage.h new file mode 100644 index 0000000..345d1e7 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedImage.h @@ -0,0 +1,105 @@ +/* + 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 "IntRect.h" +#include "Timer.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class CachedResourceLoader; +class MemoryCache; + +class CachedImage : public CachedResource, public ImageObserver { + friend class MemoryCache; + +public: + CachedImage(const String& url); + CachedImage(Image*); + virtual ~CachedImage(); + + virtual void load(CachedResourceLoader* cachedResourceLoader); + + Image* image() const; // Returns the nullImage() if the image is not available yet. + bool hasImage() const { return m_image.get(); } + + 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 didAddClient(CachedResourceClient*); + + virtual void allClientsRemoved(); + virtual void destroyDecodedData(); + + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + // For compatibility, images keep loading even if there are HTTP errors. + virtual bool shouldIgnoreHTTPStatusCodeErrors() const { return true; } + + void checkNotify(); + + virtual bool isImage() const { return true; } + + void clear(); + + bool stillNeedsLoad() const { return !errorOccurred() && status() == Unknown && !isLoading(); } + 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*); + virtual void changedInRect(const Image*, const IntRect&); + +private: + void createImage(); + size_t maximumDecodedImageSize(); + // If not null, changeRect is the changed part of the image. + void notifyObservers(const IntRect* changeRect = 0); + void decodedDataDeletionTimerFired(Timer<CachedImage>*); + virtual PurgePriority purgePriority() const { return PurgeFirst; } + + RefPtr<Image> m_image; + Timer<CachedImage> m_decodedDataDeletionTimer; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResource.cpp b/Source/WebCore/loader/cache/CachedResource.cpp new file mode 100644 index 0000000..a9d9b0a --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResource.cpp @@ -0,0 +1,596 @@ +/* + 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 "CachedResource.h" + +#include "MemoryCache.h" +#include "CachedMetadata.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "CachedResourceHandle.h" +#include "CachedResourceLoader.h" +#include "CachedResourceRequest.h" +#include "Frame.h" +#include "FrameLoaderClient.h" +#include "KURL.h" +#include "Logging.h" +#include "PurgeableBuffer.h" +#include "ResourceHandle.h" +#include "SharedBuffer.h" +#include <wtf/CurrentTime.h> +#include <wtf/MathExtras.h> +#include <wtf/RefCountedLeakCounter.h> +#include <wtf/StdLibExtras.h> +#include <wtf/Vector.h> + +using namespace WTF; + +namespace WebCore { + +static ResourceLoadPriority defaultPriorityForResourceType(CachedResource::Type type) +{ + switch (type) { + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif + return ResourceLoadPriorityHigh; + case CachedResource::Script: + case CachedResource::FontResource: + return ResourceLoadPriorityMedium; + case CachedResource::ImageResource: + return ResourceLoadPriorityLow; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return ResourceLoadPriorityVeryLow; +#endif + } + ASSERT_NOT_REACHED(); + return ResourceLoadPriorityLow; +} + +#ifndef NDEBUG +static RefCountedLeakCounter cachedResourceLeakCounter("CachedResource"); +#endif + +CachedResource::CachedResource(const String& url, Type type) + : m_url(url) + , m_request(0) + , m_loadPriority(defaultPriorityForResourceType(type)) + , m_responseTimestamp(currentTime()) + , m_lastDecodedAccessTime(0) + , m_encodedSize(0) + , m_decodedSize(0) + , m_accessCount(0) + , m_handleCount(0) + , m_preloadCount(0) + , m_preloadResult(PreloadNotReferenced) + , m_inLiveDecodedResourcesList(false) + , m_requestedFromNetworkingLayer(false) + , m_sendResourceLoadCallbacks(true) + , m_inCache(false) + , m_loading(false) + , m_type(type) + , m_status(Pending) +#ifndef NDEBUG + , m_deleted(false) + , m_lruIndex(0) +#endif + , m_nextInAllResourcesList(0) + , m_prevInAllResourcesList(0) + , m_nextInLiveResourcesList(0) + , m_prevInLiveResourcesList(0) + , m_owningCachedResourceLoader(0) + , m_resourceToRevalidate(0) + , m_proxyResource(0) +{ +#ifndef NDEBUG + cachedResourceLeakCounter.increment(); +#endif +} + +CachedResource::~CachedResource() +{ + ASSERT(!m_resourceToRevalidate); // Should be true because canDelete() checks this. + ASSERT(canDelete()); + ASSERT(!inCache()); + ASSERT(!m_deleted); + ASSERT(url().isNull() || cache()->resourceForURL(KURL(ParsedURLString, url())) != this); +#ifndef NDEBUG + m_deleted = true; + cachedResourceLeakCounter.decrement(); +#endif + + if (m_owningCachedResourceLoader) + m_owningCachedResourceLoader->removeCachedResource(this); +} + +void CachedResource::load(CachedResourceLoader* cachedResourceLoader, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) +{ + m_sendResourceLoadCallbacks = sendResourceLoadCallbacks; + cachedResourceLoader->load(this, incremental, securityCheck, sendResourceLoadCallbacks); + m_loading = true; +} + +void CachedResource::data(PassRefPtr<SharedBuffer>, bool allDataReceived) +{ + if (!allDataReceived) + return; + + setLoading(false); + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedResource::finish() +{ + m_status = Cached; +} + +bool CachedResource::isExpired() const +{ + if (m_response.isNull()) + return false; + + return currentAge() > freshnessLifetime(); +} + +double CachedResource::currentAge() const +{ + // RFC2616 13.2.3 + // No compensation for latency as that is not terribly important in practice + double dateValue = m_response.date(); + double apparentAge = isfinite(dateValue) ? max(0., m_responseTimestamp - dateValue) : 0; + double ageValue = m_response.age(); + double correctedReceivedAge = isfinite(ageValue) ? max(apparentAge, ageValue) : apparentAge; + double residentTime = currentTime() - m_responseTimestamp; + return correctedReceivedAge + residentTime; +} + +double CachedResource::freshnessLifetime() const +{ + // Cache non-http resources liberally + if (!m_response.url().protocolInHTTPFamily()) + return std::numeric_limits<double>::max(); + + // RFC2616 13.2.4 + double maxAgeValue = m_response.cacheControlMaxAge(); + if (isfinite(maxAgeValue)) + return maxAgeValue; + double expiresValue = m_response.expires(); + double dateValue = m_response.date(); + double creationTime = isfinite(dateValue) ? dateValue : m_responseTimestamp; + if (isfinite(expiresValue)) + return expiresValue - creationTime; + double lastModifiedValue = m_response.lastModified(); + if (isfinite(lastModifiedValue)) + return (creationTime - lastModifiedValue) * 0.1; + // If no cache headers are present, the specification leaves the decision to the UA. Other browsers seem to opt for 0. + return 0; +} + +void CachedResource::setResponse(const ResourceResponse& response) +{ + m_response = response; + m_responseTimestamp = currentTime(); +} + +void CachedResource::setSerializedCachedMetadata(const char* data, size_t size) +{ + // We only expect to receive cached metadata from the platform once. + // If this triggers, it indicates an efficiency problem which is most + // likely unexpected in code designed to improve performance. + ASSERT(!m_cachedMetadata); + + m_cachedMetadata = CachedMetadata::deserialize(data, size); +} + +void CachedResource::setCachedMetadata(unsigned dataTypeID, const char* data, size_t size) +{ + // Currently, only one type of cached metadata per resource is supported. + // If the need arises for multiple types of metadata per resource this could + // be enhanced to store types of metadata in a map. + ASSERT(!m_cachedMetadata); + + m_cachedMetadata = CachedMetadata::create(dataTypeID, data, size); + ResourceHandle::cacheMetadata(m_response, m_cachedMetadata->serialize()); +} + +CachedMetadata* CachedResource::cachedMetadata(unsigned dataTypeID) const +{ + if (!m_cachedMetadata || m_cachedMetadata->dataTypeID() != dataTypeID) + return 0; + return m_cachedMetadata.get(); +} + +void CachedResource::setRequest(CachedResourceRequest* request) +{ + if (request && !m_request) + m_status = Pending; + m_request = request; + if (canDelete() && !inCache()) + delete this; +} + +void CachedResource::addClient(CachedResourceClient* client) +{ + addClientToSet(client); + didAddClient(client); +} + +void CachedResource::didAddClient(CachedResourceClient* c) +{ + if (!isLoading()) + c->notifyFinished(this); +} + +void CachedResource::addClientToSet(CachedResourceClient* client) +{ + ASSERT(!isPurgeable()); + + 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(client); +} + +void CachedResource::removeClient(CachedResourceClient* client) +{ + ASSERT(m_clients.contains(client)); + m_clients.remove(client); + + if (canDelete() && !inCache()) + delete this; + else if (!hasClients() && inCache()) { + cache()->removeFromLiveResourcesSize(this); + cache()->removeFromLiveDecodedResourcesList(this); + allClientsRemoved(); + if (response().cacheControlContainsNoStore()) { + // RFC2616 14.9.2: + // "no-store: ... MUST make a best-effort attempt to remove the information from volatile storage as promptly as possible" + // "... History buffers MAY store such responses as part of their normal operation." + // We allow non-secure content to be reused in history, but we do not allow secure content to be reused. + if (protocolIs(url(), "https")) + cache()->remove(this); + } else + cache()->prune(); + } + // This object may be dead here. +} + +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. + // When inserting into the LiveDecodedResourcesList it is possible + // that the m_lastDecodedAccessTime is still zero or smaller than + // the m_lastDecodedAccessTime of the current list head. This is a + // violation of the invariant that the list is to be kept sorted + // by access time. The weakening of the invariant does not pose + // a problem. For more details please see: https://bugs.webkit.org/show_bug.cgi?id=30209 + 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(m_handlesToRevalidate.isEmpty()); + ASSERT(resource->type() == type()); + + LOG(ResourceLoading, "CachedResource %p setResourceToRevalidate %p", this, resource); + + // The following assert should be investigated whenever it occurs. Although it should never fire, it currently does in rare circumstances. + // https://bugs.webkit.org/show_bug.cgi?id=28604. + // So the code needs to be robust to this assert failing thus the "if (m_resourceToRevalidate->m_proxyResource == this)" in CachedResource::clearResourceToRevalidate. + ASSERT(!resource->m_proxyResource); + + resource->m_proxyResource = this; + m_resourceToRevalidate = resource; +} + +void CachedResource::clearResourceToRevalidate() +{ + ASSERT(m_resourceToRevalidate); + // A resource may start revalidation before this method has been called, so check that this resource is still the proxy resource before clearing it out. + if (m_resourceToRevalidate->m_proxyResource == this) { + m_resourceToRevalidate->m_proxyResource = 0; + m_resourceToRevalidate->deleteIfPossible(); + } + m_handlesToRevalidate.clear(); + m_resourceToRevalidate = 0; + deleteIfPossible(); +} + +void CachedResource::switchClientsToRevalidatedResource() +{ + ASSERT(m_resourceToRevalidate); + ASSERT(m_resourceToRevalidate->inCache()); + ASSERT(!inCache()); + + LOG(ResourceLoading, "CachedResource %p switchClientsToRevalidatedResource %p", this, m_resourceToRevalidate); + + 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->addClientToSet(clientsToMove[n]); + for (unsigned n = 0; n < moveCount; ++n) { + // Calling didAddClient for a client may end up removing another client. In that case it won't be in the set anymore. + if (m_resourceToRevalidate->m_clients.contains(clientsToMove[n])) + m_resourceToRevalidate->didAddClient(clientsToMove[n]); + } +} + +void CachedResource::updateResponseAfterRevalidation(const ResourceResponse& validatingResponse) +{ + m_responseTimestamp = currentTime(); + + DEFINE_STATIC_LOCAL(const AtomicString, contentHeaderPrefix, ("content-")); + // RFC2616 10.3.5 + // Update cached headers from the 304 response + const HTTPHeaderMap& newHeaders = validatingResponse.httpHeaderFields(); + HTTPHeaderMap::const_iterator end = newHeaders.end(); + for (HTTPHeaderMap::const_iterator it = newHeaders.begin(); it != end; ++it) { + // Don't allow 304 response to update content headers, these can't change but some servers send wrong values. + if (it->first.startsWith(contentHeaderPrefix, false)) + continue; + m_response.setHTTPHeaderField(it->first, it->second); + } +} + +void CachedResource::registerHandle(CachedResourceHandleBase* h) +{ + ++m_handleCount; + if (m_resourceToRevalidate) + m_handlesToRevalidate.add(h); +} + +void CachedResource::unregisterHandle(CachedResourceHandleBase* h) +{ + ASSERT(m_handleCount > 0); + --m_handleCount; + + if (m_resourceToRevalidate) + m_handlesToRevalidate.remove(h); + + if (!m_handleCount) + deleteIfPossible(); +} + +bool CachedResource::canUseCacheValidator() const +{ + if (m_loading || errorOccurred()) + return false; + + if (m_response.cacheControlContainsNoStore()) + return false; + + DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified")); + DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag")); + return !m_response.httpHeaderField(lastModifiedHeader).isEmpty() || !m_response.httpHeaderField(eTagHeader).isEmpty(); +} + +bool CachedResource::mustRevalidateDueToCacheHeaders(CachePolicy cachePolicy) const +{ + ASSERT(cachePolicy == CachePolicyRevalidate || cachePolicy == CachePolicyCache || cachePolicy == CachePolicyVerify); + + if (cachePolicy == CachePolicyRevalidate) + return true; + + if (m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()) { + LOG(ResourceLoading, "CachedResource %p mustRevalidate because of m_response.cacheControlContainsNoCache() || m_response.cacheControlContainsNoStore()\n", this); + return true; + } + + if (cachePolicy == CachePolicyCache) { + if (m_response.cacheControlContainsMustRevalidate() && isExpired()) { + LOG(ResourceLoading, "CachedResource %p mustRevalidate because of cachePolicy == CachePolicyCache and m_response.cacheControlContainsMustRevalidate() && isExpired()\n", this); + return true; + } + return false; + } + + // CachePolicyVerify + if (isExpired()) { + LOG(ResourceLoading, "CachedResource %p mustRevalidate because of isExpired()\n", this); + return true; + } + + return false; +} + +bool CachedResource::isSafeToMakePurgeable() const +{ + return !hasClients() && !m_proxyResource && !m_resourceToRevalidate; +} + +bool CachedResource::makePurgeable(bool purgeable) +{ + if (purgeable) { + ASSERT(isSafeToMakePurgeable()); + + if (m_purgeableData) { + ASSERT(!m_data); + return true; + } + if (!m_data) + return false; + + // Should not make buffer purgeable if it has refs other than this since we don't want two copies. + if (!m_data->hasOneRef()) + return false; + + if (m_data->hasPurgeableBuffer()) { + m_purgeableData = m_data->releasePurgeableBuffer(); + } else { + m_purgeableData = PurgeableBuffer::create(m_data->data(), m_data->size()); + if (!m_purgeableData) + return false; + m_purgeableData->setPurgePriority(purgePriority()); + } + + m_purgeableData->makePurgeable(true); + m_data.clear(); + return true; + } + + if (!m_purgeableData) + return true; + ASSERT(!m_data); + ASSERT(!hasClients()); + + if (!m_purgeableData->makePurgeable(false)) + return false; + + m_data = SharedBuffer::adoptPurgeableBuffer(m_purgeableData.release()); + return true; +} + +bool CachedResource::isPurgeable() const +{ + return m_purgeableData && m_purgeableData->isPurgeable(); +} + +bool CachedResource::wasPurged() const +{ + return m_purgeableData && m_purgeableData->wasPurged(); +} + +unsigned CachedResource::overheadSize() const +{ + return sizeof(CachedResource) + m_response.memoryUsage() + 576; + /* + 576 = 192 + // average size of m_url + 384; // average size of m_clients hash map + */ +} + +void CachedResource::setLoadPriority(ResourceLoadPriority loadPriority) +{ + if (loadPriority == ResourceLoadPriorityUnresolved) + return; + m_loadPriority = loadPriority; +} + +} diff --git a/Source/WebCore/loader/cache/CachedResource.h b/Source/WebCore/loader/cache/CachedResource.h new file mode 100644 index 0000000..3600a02 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResource.h @@ -0,0 +1,296 @@ +/* + 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 "FrameLoaderTypes.h" +#include "PlatformString.h" +#include "PurgePriority.h" +#include "ResourceLoadPriority.h" +#include "ResourceResponse.h" +#include <wtf/HashCountedSet.h> +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> +#include <wtf/Vector.h> +#include <time.h> + +namespace WebCore { + +class MemoryCache; +class CachedMetadata; +class CachedResourceClient; +class CachedResourceHandleBase; +class CachedResourceLoader; +class CachedResourceRequest; +class Frame; +class InspectorResource; +class PurgeableBuffer; + +// 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 : public Noncopyable { + friend class MemoryCache; + friend class InspectorResource; + +public: + enum Type { + ImageResource, + CSSStyleSheet, + Script, + FontResource +#if ENABLE(XSLT) + , XSLStyleSheet +#endif +#if ENABLE(LINK_PREFETCH) + , LinkPrefetch +#endif + }; + + enum Status { + Unknown, // let cache decide what to do with it + Pending, // only partially loaded + Cached, // regular case + LoadError, + DecodeError + }; + + CachedResource(const String& url, Type); + virtual ~CachedResource(); + + virtual void load(CachedResourceLoader* cachedResourceLoader) { load(cachedResourceLoader, false, DoSecurityCheck, true); } + void load(CachedResourceLoader*, bool incremental, SecurityCheckPolicy, bool sendResourceLoadCallbacks); + + virtual void setEncoding(const String&) { } + virtual String encoding() const { return String(); } + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status) { } + + virtual bool shouldIgnoreHTTPStatusCodeErrors() const { return false; } + + const String &url() const { return m_url; } + Type type() const { return static_cast<Type>(m_type); } + + ResourceLoadPriority loadPriority() const { return m_loadPriority; } + void setLoadPriority(ResourceLoadPriority); + + 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 static_cast<PreloadResult>(m_preloadResult); } + void setRequestedFromNetworkingLayer() { m_requestedFromNetworkingLayer = true; } + + virtual void didAddClient(CachedResourceClient*); + virtual void allClientsRemoved() { } + + unsigned count() const { return m_clients.size(); } + + Status status() const { return static_cast<Status>(m_status); } + void setStatus(Status status) { m_status = status; } + + unsigned size() const { return encodedSize() + decodedSize() + overheadSize(); } + unsigned encodedSize() const { return m_encodedSize; } + unsigned decodedSize() const { return m_decodedSize; } + unsigned overheadSize() const; + + bool isLoaded() const { return !m_loading; } // FIXME. Method name is inaccurate. Loading might not have started yet. + + bool isLoading() const { return m_loading; } + void setLoading(bool b) { m_loading = b; } + + virtual bool isImage() const { return false; } + bool isPrefetch() const + { +#if ENABLE(LINK_PREFETCH) + return type() == LinkPrefetch; +#else + return false; +#endif + } + + 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. + // The resource can be brought back to cache after successful revalidation. + void setInCache(bool inCache) { m_inCache = inCache; } + bool inCache() const { return m_inCache; } + + void setInLiveDecodedResourcesList(bool b) { m_inLiveDecodedResourcesList = b; } + bool inLiveDecodedResourcesList() { return m_inLiveDecodedResourcesList; } + + void setRequest(CachedResourceRequest*); + + SharedBuffer* data() const { ASSERT(!m_purgeableData); return m_data.get(); } + + void setResponse(const ResourceResponse&); + const ResourceResponse& response() const { return m_response; } + + // Sets the serialized metadata retrieved from the platform's cache. + void setSerializedCachedMetadata(const char*, size_t); + + // Caches the given metadata in association with this resource and suggests + // that the platform persist it. The dataTypeID is a pseudo-randomly chosen + // identifier that is used to distinguish data generated by the caller. + void setCachedMetadata(unsigned dataTypeID, const char*, size_t); + + // Returns cached metadata of the given type associated with this resource. + CachedMetadata* cachedMetadata(unsigned dataTypeID) const; + + bool canDelete() const { return !hasClients() && !m_request && !m_preloadCount && !m_handleCount && !m_resourceToRevalidate && !m_proxyResource; } + + bool isExpired() const; + + // List of acceptable MIME types separated 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 (status() == LoadError || status() == DecodeError); } + + bool sendResourceLoadCallbacks() const { return m_sendResourceLoadCallbacks; } + + virtual void destroyDecodedData() { } + + void setOwningCachedResourceLoader(CachedResourceLoader* cachedResourceLoader) { m_owningCachedResourceLoader = cachedResourceLoader; } + + bool isPreloaded() const { return m_preloadCount; } + void increasePreloadCount() { ++m_preloadCount; } + void decreasePreloadCount() { ASSERT(m_preloadCount); --m_preloadCount; } + + void registerHandle(CachedResourceHandleBase* h); + void unregisterHandle(CachedResourceHandleBase* h); + + bool canUseCacheValidator() const; + bool mustRevalidateDueToCacheHeaders(CachePolicy) const; + bool isCacheValidator() const { return m_resourceToRevalidate; } + CachedResource* resourceToRevalidate() const { return m_resourceToRevalidate; } + + bool isPurgeable() const; + bool wasPurged() const; + + // This is used by the archive machinery to get at a purged resource without + // triggering a load. We should make it protected again if we can find a + // better way to handle the archive case. + bool makePurgeable(bool purgeable); + + // HTTP revalidation support methods for CachedResourceLoader. + void setResourceToRevalidate(CachedResource*); + void switchClientsToRevalidatedResource(); + void clearResourceToRevalidate(); + void updateResponseAfterRevalidation(const ResourceResponse& validatingResponse); + +protected: + void setEncodedSize(unsigned); + void setDecodedSize(unsigned); + void didAccessDecodedData(double timeStamp); + + bool isSafeToMakePurgeable() const; + + HashCountedSet<CachedResourceClient*> m_clients; + + String m_url; + String m_accept; + CachedResourceRequest* m_request; + ResourceLoadPriority m_loadPriority; + + ResourceResponse m_response; + double m_responseTimestamp; + + RefPtr<SharedBuffer> m_data; + OwnPtr<PurgeableBuffer> m_purgeableData; + +private: + void addClientToSet(CachedResourceClient*); + + virtual PurgePriority purgePriority() const { return PurgeDefault; } + + double currentAge() const; + double freshnessLifetime() const; + + RefPtr<CachedMetadata> m_cachedMetadata; + + double m_lastDecodedAccessTime; // Used as a "thrash guard" in the cache + + unsigned m_encodedSize; + unsigned m_decodedSize; + unsigned m_accessCount; + unsigned m_handleCount; + unsigned m_preloadCount; + + unsigned m_preloadResult : 2; // PreloadResult + + bool m_inLiveDecodedResourcesList : 1; + bool m_requestedFromNetworkingLayer : 1; + bool m_sendResourceLoadCallbacks : 1; + + bool m_inCache : 1; + bool m_loading : 1; + + unsigned m_type : 3; // Type + unsigned m_status : 3; // Status + +#ifndef NDEBUG + bool m_deleted; + unsigned m_lruIndex; +#endif + + CachedResource* m_nextInAllResourcesList; + CachedResource* m_prevInAllResourcesList; + + CachedResource* m_nextInLiveResourcesList; + CachedResource* m_prevInLiveResourcesList; + + CachedResourceLoader* m_owningCachedResourceLoader; // only non-0 for resources that are not in the cache + + // 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; + + // If this field is non-null, the resource has a proxy for checking whether it is still up to date (see m_resourceToRevalidate). + CachedResource* m_proxyResource; + + // These handles will need to be updated to point to the m_resourceToRevalidate in case we get 304 response. + HashSet<CachedResourceHandleBase*> m_handlesToRevalidate; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceClient.h b/Source/WebCore/loader/cache/CachedResourceClient.h new file mode 100644 index 0000000..275d331 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceClient.h @@ -0,0 +1,71 @@ +/* + 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 CachedResourceClient_h +#define CachedResourceClient_h + +#include <wtf/FastAllocBase.h> +#include <wtf/Forward.h> + +namespace WebCore { + + class CachedCSSStyleSheet; + class CachedFont; + class CachedResource; + class CachedImage; + class Image; + class IntRect; + class KURL; + + /** + * @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 FastAllocBase + { + 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. If not null, the IntRect is the changed rect of the image. + virtual void imageChanged(CachedImage*, const IntRect* = 0) { }; + + // 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& /* href */, const KURL& /* baseURL */, const String& /* charset */, const CachedCSSStyleSheet*) { } + virtual void setXSLStyleSheet(const String& /* href */, const KURL& /* baseURL */, const String& /* sheet */) { } + virtual void fontLoaded(CachedFont*) {}; + virtual void notifyFinished(CachedResource*) { } + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceClientWalker.cpp b/Source/WebCore/loader/cache/CachedResourceClientWalker.cpp new file mode 100644 index 0000000..142a2a1 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceClientWalker.cpp @@ -0,0 +1,53 @@ +/* + 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 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 "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/Source/WebCore/loader/cache/CachedResourceClientWalker.h b/Source/WebCore/loader/cache/CachedResourceClientWalker.h new file mode 100644 index 0000000..d079584 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceClientWalker.h @@ -0,0 +1,49 @@ +/* + Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) + Copyright (C) 2001 Dirk Mueller <mueller@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. + + 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/Source/WebCore/loader/cache/CachedResourceHandle.cpp b/Source/WebCore/loader/cache/CachedResourceHandle.cpp new file mode 100644 index 0000000..871292c --- /dev/null +++ b/Source/WebCore/loader/cache/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/Source/WebCore/loader/cache/CachedResourceHandle.h b/Source/WebCore/loader/cache/CachedResourceHandle.h new file mode 100644 index 0000000..7d485bf --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceHandle.h @@ -0,0 +1,104 @@ +/* + * 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. + // Parenthesis is needed for winscw compiler to resolve class qualifier in this case. + 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); + 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(); } + }; + + // Don't inline for winscw compiler to prevent the compiler aggressively resolving + // the base class of R* when CachedResourceHandler<T>(R*) is inlined. The bug is + // reported at: https://xdabug001.ext.nokia.com/bugzilla/show_bug.cgi?id=9812. + template <class R> +#if !COMPILER(WINSCW) + inline +#endif + CachedResourceHandle<R>::CachedResourceHandle(R* res) : CachedResourceHandleBase(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; + } + 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/Source/WebCore/loader/cache/CachedResourceLoader.cpp b/Source/WebCore/loader/cache/CachedResourceLoader.cpp new file mode 100644 index 0000000..3fcace6 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceLoader.cpp @@ -0,0 +1,733 @@ +/* + 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. + Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.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. + + 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 "CachedResourceLoader.h" + +#include "CachedCSSStyleSheet.h" +#include "CachedFont.h" +#include "CachedImage.h" +#include "CachedResourceRequest.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "Console.h" +#include "DOMWindow.h" +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "HTMLElement.h" +#include "Logging.h" +#include "MemoryCache.h" +#include "PingLoader.h" +#include "SecurityOrigin.h" +#include "Settings.h" +#include <wtf/text/StringConcatenate.h> + +#define PRELOAD_DEBUG 0 + +namespace WebCore { + +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(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return new CachedResource(url.string(), CachedResource::LinkPrefetch); +#endif + } + ASSERT_NOT_REACHED(); + return 0; +} + +CachedResourceLoader::CachedResourceLoader(Document* document) + : m_cache(cache()) + , m_document(document) + , m_requestCount(0) +#ifdef ANDROID_BLOCK_NETWORK_IMAGE + , m_blockNetworkImage(false) +#endif + , m_autoLoadImages(true) + , m_loadFinishing(false) + , m_allowStaleResources(false) +{ + m_cache->addCachedResourceLoader(this); +} + +CachedResourceLoader::~CachedResourceLoader() +{ + cancelRequests(); + clearPreloads(); + DocumentResourceMap::iterator end = m_documentResources.end(); + for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) + it->second->setOwningCachedResourceLoader(0); + m_cache->removeCachedResourceLoader(this); + + // Make sure no requests still point to this CachedResourceLoader + ASSERT(m_requestCount == 0); +} + +CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const +{ + KURL url = m_document->completeURL(resourceURL); + return cachedResource(url); +} + +CachedResource* CachedResourceLoader::cachedResource(const KURL& resourceURL) const +{ + KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL); + return m_documentResources.get(url).get(); +} + +Frame* CachedResourceLoader::frame() const +{ + return m_document->frame(); +} + +CachedImage* CachedResourceLoader::requestImage(const String& url) +{ + if (Frame* f = frame()) { + Settings* settings = f->settings(); + if (!f->loader()->client()->allowImages(!settings || settings->areImagesEnabled())) + return 0; + + if (f->loader()->pageDismissalEventBeingDispatched()) { + KURL completeURL = m_document->completeURL(url); + if (completeURL.isValid() && canRequest(CachedResource::ImageResource, completeURL)) + PingLoader::loadImage(f, completeURL); + return 0; + } + } + 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); + load(resource, true); + } + return resource; +} + +CachedFont* CachedResourceLoader::requestFont(const String& url) +{ + return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String())); +} + +CachedCSSStyleSheet* CachedResourceLoader::requestCSSStyleSheet(const String& url, const String& charset, ResourceLoadPriority priority) +{ + return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset, priority)); +} + +CachedCSSStyleSheet* CachedResourceLoader::requestUserCSSStyleSheet(const String& requestURL, const String& charset) +{ + KURL url = MemoryCache::removeFragmentIdentifierIfNeeded(KURL(KURL(), requestURL)); + + if (CachedResource* existing = cache()->resourceForURL(url)) { + if (existing->type() == CachedResource::CSSStyleSheet) + return static_cast<CachedCSSStyleSheet*>(existing); + cache()->remove(existing); + } + CachedCSSStyleSheet* userSheet = new CachedCSSStyleSheet(url, charset); + + bool inCache = cache()->add(userSheet); + if (!inCache) + userSheet->setInCache(true); + + userSheet->load(this, /*incremental*/ false, SkipSecurityCheck, /*sendResourceLoadCallbacks*/ false); + + if (!inCache) + userSheet->setInCache(false); + + return userSheet; +} + +CachedScript* CachedResourceLoader::requestScript(const String& url, const String& charset) +{ + return static_cast<CachedScript*>(requestResource(CachedResource::Script, url, charset)); +} + +#if ENABLE(XSLT) +CachedXSLStyleSheet* CachedResourceLoader::requestXSLStyleSheet(const String& url) +{ + return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, url, String())); +} +#endif + +#if ENABLE(LINK_PREFETCH) +CachedResource* CachedResourceLoader::requestLinkPrefetch(const String& url) +{ + ASSERT(frame()); + return requestResource(CachedResource::LinkPrefetch, url, String()); +} +#endif + +bool CachedResourceLoader::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: +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: +#endif + // These types of resources can be loaded from any origin. + // FIXME: Are we sure about CachedResource::FontResource? + break; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + if (!m_document->securityOrigin()->canRequest(url)) { + printAccessDeniedMessage(url); + return false; + } + break; +#endif + default: + ASSERT_NOT_REACHED(); + break; + } + + // Given that the load is allowed by the same-origin policy, we should + // check whether the load passes the mixed-content policy. + // + // Note: Currently, we always allow mixed content, but we generate a + // callback to the FrameLoaderClient in case the embedder wants to + // update any security indicators. + // + switch (type) { + case CachedResource::Script: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif + // These resource can inject script into the current document. + if (Frame* f = frame()) + f->loader()->checkIfRunInsecureContent(m_document->securityOrigin(), url); + break; + case CachedResource::ImageResource: + case CachedResource::CSSStyleSheet: + case CachedResource::FontResource: { + // These resources can corrupt only the frame's pixels. + if (Frame* f = frame()) { + Frame* top = f->tree()->top(); + top->loader()->checkIfDisplayInsecureContent(top->document()->securityOrigin(), url); + } + break; + } +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + // Prefetch cannot affect the current document. + break; +#endif + default: + ASSERT_NOT_REACHED(); + break; + } + // FIXME: Consider letting the embedder block mixed content loads. + return true; +} + +CachedResource* CachedResourceLoader::requestResource(CachedResource::Type type, const String& resourceURL, const String& charset, ResourceLoadPriority priority, bool forPreload) +{ + KURL url = m_document->completeURL(resourceURL); + + LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.string().latin1().data(), charset.latin1().data(), priority, forPreload); + + // If only the fragment identifiers differ, it is the same resource. + url = MemoryCache::removeFragmentIdentifierIfNeeded(url); + + if (!url.isValid()) + return 0; + + if (!canRequest(type, url)) + return 0; + + // FIXME: Figure out what is the correct way to merge this security check with the one above. + if (!document()->securityOrigin()->canDisplay(url)) { + if (!forPreload) + FrameLoader::reportLocalLoadFailed(document()->frame(), url.string()); + LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); + return 0; + } + + if (cache()->disabled()) { + DocumentResourceMap::iterator it = m_documentResources.find(url.string()); + if (it != m_documentResources.end()) { + it->second->setOwningCachedResourceLoader(0); + m_documentResources.remove(it); + } + } + + // See if we can use an existing resource from the cache. + CachedResource* resource = cache()->resourceForURL(url); + + switch (determineRevalidationPolicy(type, forPreload, resource)) { + case Load: + resource = loadResource(type, url, charset, priority); + break; + case Reload: + cache()->remove(resource); + resource = loadResource(type, url, charset, priority); + break; + case Revalidate: + resource = revalidateResource(resource, priority); + break; + case Use: + cache()->resourceAccessed(resource); + notifyLoadedFromMemoryCache(resource); + break; + } + + if (!resource) + return 0; + + ASSERT(resource->url() == url.string()); + m_documentResources.set(resource->url(), resource); + + return resource; +} + +CachedResource* CachedResourceLoader::revalidateResource(CachedResource* resource, ResourceLoadPriority priority) +{ + ASSERT(resource); + ASSERT(resource->inCache()); + ASSERT(!cache()->disabled()); + ASSERT(resource->canUseCacheValidator()); + ASSERT(!resource->resourceToRevalidate()); + + const String& url = resource->url(); + CachedResource* newResource = createResource(resource->type(), KURL(ParsedURLString, url), resource->encoding()); + + LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource, resource); + newResource->setResourceToRevalidate(resource); + + cache()->remove(resource); + cache()->add(newResource); + + newResource->setLoadPriority(priority); + newResource->load(this); + + m_validatedURLs.add(url); + return newResource; +} + +CachedResource* CachedResourceLoader::loadResource(CachedResource::Type type, const KURL& url, const String& charset, ResourceLoadPriority priority) +{ + ASSERT(!cache()->resourceForURL(url)); + + LOG(ResourceLoading, "Loading CachedResource for '%s'.", url.string().latin1().data()); + + CachedResource* resource = createResource(type, url, charset); + + bool inCache = cache()->add(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. + if (!inCache) + resource->setInCache(true); + + resource->setLoadPriority(priority); + resource->load(this); + + if (!inCache) { + resource->setOwningCachedResourceLoader(this); + resource->setInCache(false); + } + + // We don't support immediate loads, but we do support immediate failure. + if (resource->errorOccurred()) { + if (inCache) + cache()->remove(resource); + else + delete resource; + return 0; + } + + m_validatedURLs.add(url.string()); + return resource; +} + +CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, bool forPreload, CachedResource* existingResource) const +{ + if (!existingResource) + return Load; + + // We already have a preload going for this URL. + if (forPreload && existingResource->isPreloaded()) + return Use; + + // If the same URL has been loaded as a different type, we need to reload. + if (existingResource->type() != type) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch."); + return Reload; + } + + // Don't reload resources while pasting. + if (m_allowStaleResources) + return Use; + + // Alwaus use preloads. + if (existingResource->isPreloaded()) + return Use; + + // Avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to. + if (m_validatedURLs.contains(existingResource->url())) + return Use; + + // CachePolicyReload always reloads + if (cachePolicy() == CachePolicyReload) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload."); + return Reload; + } + + // CachePolicyHistoryBuffer uses the cache no matter what. + if (cachePolicy() == CachePolicyHistoryBuffer) + return Use; + + // We'll try to reload the resource if it failed last time. + if (existingResource->errorOccurred()) { + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state"); + return Reload; + } + + // For resources that are not yet loaded we ignore the cache policy. + if (existingResource->isLoading()) + return Use; + + // Check if the cache headers requires us to revalidate (cache expiration for example). + if (existingResource->mustRevalidateDueToCacheHeaders(cachePolicy())) { + // See if the resource has usable ETag or Last-modified headers. + if (existingResource->canUseCacheValidator()) + return Revalidate; + + // No, must reload. + LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators."); + return Reload; + } + + return Use; +} + +void CachedResourceLoader::printAccessDeniedMessage(const KURL& url) const +{ + if (url.isNull()) + return; + + if (!frame()) + return; + + Settings* settings = frame()->settings(); + if (!settings || settings->privateBrowsingEnabled()) + return; + + String message = m_document->url().isNull() ? + makeString("Unsafe attempt to load URL ", url.string(), '.') : + makeString("Unsafe attempt to load URL ", url.string(), " from frame with URL ", m_document->url().string(), ". Domains, protocols and ports must match.\n"); + + // FIXME: provide a real line number and source URL. + frame()->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String()); +} + +void CachedResourceLoader::setAutoLoadImages(bool enable) +{ + if (enable == m_autoLoadImages) + return; + + m_autoLoadImages = enable; + + if (!m_autoLoadImages) + return; + + DocumentResourceMap::iterator end = m_documentResources.end(); + for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { + CachedResource* resource = it->second.get(); + 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()) + load(image, true); + } + } +} + +#ifdef ANDROID_BLOCK_NETWORK_IMAGE +bool CachedResourceLoader::shouldBlockNetworkImage(const String& url) const +{ + if (!m_blockNetworkImage) + return false; + + KURL kurl = m_document->completeURL(url); + if (kurl.protocolIs("http") || kurl.protocolIs("https")) + return true; + + return false; +} + +void CachedResourceLoader::setBlockNetworkImage(bool block) +{ + if (block == m_blockNetworkImage) + return; + + m_blockNetworkImage = block; + + if (!m_autoLoadImages || m_blockNetworkImage) + return; + + DocumentResourceMap::iterator end = m_documentResources.end(); + for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { + CachedResource* resource = it->second.get(); + if (resource->type() == CachedResource::ImageResource) { + CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource)); + if (image->stillNeedsLoad()) + load(image, true); + } + } +} +#endif + +CachePolicy CachedResourceLoader::cachePolicy() const +{ + return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify; +} + +void CachedResourceLoader::removeCachedResource(CachedResource* resource) const +{ +#ifndef NDEBUG + DocumentResourceMap::iterator it = m_documentResources.find(resource->url()); + if (it != m_documentResources.end()) + ASSERT(it->second.get() == resource); +#endif + m_documentResources.remove(resource->url()); +} + +void CachedResourceLoader::load(CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) +{ + incrementRequestCount(resource); + + RefPtr<CachedResourceRequest> request = CachedResourceRequest::load(this, resource, incremental, securityCheck, sendResourceLoadCallbacks); + if (request) + m_requests.add(request); +} + +void CachedResourceLoader::loadDone(CachedResourceRequest* request) +{ + m_loadFinishing = false; + RefPtr<CachedResourceRequest> protect(request); + if (request) + m_requests.remove(request); + if (frame()) + frame()->loader()->loadDone(); + checkForPendingPreloads(); +} + +void CachedResourceLoader::cancelRequests() +{ + clearPendingPreloads(); + Vector<CachedResourceRequest*, 256> requestsToCancel; + RequestSet::iterator end = m_requests.end(); + for (RequestSet::iterator i = m_requests.begin(); i != end; ++i) + requestsToCancel.append((*i).get()); + + for (unsigned i = 0; i < requestsToCancel.size(); ++i) + requestsToCancel[i]->didFail(true); +} + +void CachedResourceLoader::notifyLoadedFromMemoryCache(CachedResource* resource) +{ + if (!resource || !frame() || resource->status() != CachedResource::Cached) + 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 CachedResourceLoader::incrementRequestCount(const CachedResource* res) +{ + if (res->isPrefetch()) + return; + + ++m_requestCount; +} + +void CachedResourceLoader::decrementRequestCount(const CachedResource* res) +{ + if (res->isPrefetch()) + return; + + --m_requestCount; + ASSERT(m_requestCount > -1); +} + +int CachedResourceLoader::requestCount() +{ + if (m_loadFinishing) + return m_requestCount + 1; + return m_requestCount; +} + +void CachedResourceLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody) +{ + bool hasRendering = m_document->body() && m_document->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 CachedResourceLoader::checkForPendingPreloads() +{ + unsigned count = m_pendingPreloads.size(); + if (!count || !m_document->body() || !m_document->body()->renderer()) + return; + for (unsigned i = 0; i < count; ++i) { + PendingPreload& preload = m_pendingPreloads[i]; + // Don't request preload if the resource already loaded normally (this will result in double load if the page is being reloaded with cached results ignored). + if (!cachedResource(m_document->completeURL(preload.m_url))) + requestPreload(preload.m_type, preload.m_url, preload.m_charset); + } + m_pendingPreloads.clear(); +} + +void CachedResourceLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset) +{ + String encoding; + if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) + encoding = charset.isEmpty() ? m_document->frame()->loader()->writer()->encoding() : charset; + + CachedResource* resource = requestResource(type, url, encoding, ResourceLoadPriorityUnresolved, true); + if (!resource || (m_preloads && m_preloads->contains(resource))) + return; + resource->increasePreloadCount(); + + if (!m_preloads) + m_preloads = adoptPtr(new ListHashSet<CachedResource*>); + m_preloads->add(resource); + +#if PRELOAD_DEBUG + printf("PRELOADING %s\n", resource->url().latin1().data()); +#endif +} + +void CachedResourceLoader::clearPreloads() +{ +#if PRELOAD_DEBUG + printPreloadStats(); +#endif + if (!m_preloads) + return; + + 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(); +} + +void CachedResourceLoader::clearPendingPreloads() +{ + m_pendingPreloads.clear(); +} + +#if PRELOAD_DEBUG +void CachedResourceLoader::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/Source/WebCore/loader/cache/CachedResourceLoader.h b/Source/WebCore/loader/cache/CachedResourceLoader.h new file mode 100644 index 0000000..1d53976 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceLoader.h @@ -0,0 +1,156 @@ +/* + 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. + Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.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. + + 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 CachedResourceLoader_h +#define CachedResourceLoader_h + +#include "CachedResource.h" +#include "CachedResourceHandle.h" +#include "CachePolicy.h" +#include "ResourceLoadPriority.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/ListHashSet.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class CachedCSSStyleSheet; +class CachedFont; +class CachedImage; +class CachedResourceRequest; +class CachedScript; +class CachedXSLStyleSheet; +class Document; +class Frame; +class ImageLoader; +class KURL; + +// The CachedResourceLoader manages the loading of scripts/images/stylesheets for a single document. +class CachedResourceLoader : public Noncopyable { +friend class MemoryCache; +friend class ImageLoader; + +public: + CachedResourceLoader(Document*); + ~CachedResourceLoader(); + + CachedImage* requestImage(const String& url); + CachedCSSStyleSheet* requestCSSStyleSheet(const String& url, const String& charset, ResourceLoadPriority priority = ResourceLoadPriorityUnresolved); + 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(LINK_PREFETCH) + CachedResource* requestLinkPrefetch(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; + CachedResource* cachedResource(const KURL& url) const; + + typedef HashMap<String, CachedResourceHandle<CachedResource> > DocumentResourceMap; + const DocumentResourceMap& allCachedResources() const { return m_documentResources; } + + 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; + + Frame* frame() const; // Can be NULL + Document* document() const { return m_document; } + + void removeCachedResource(CachedResource*) const; + + void load(CachedResource*, bool incremental = false, SecurityCheckPolicy = DoSecurityCheck, bool sendResourceLoadCallbacks = true); + void loadFinishing() { m_loadFinishing = true; } + void loadDone(CachedResourceRequest*); + void cancelRequests(); + + void setAllowStaleResources(bool allowStaleResources) { m_allowStaleResources = allowStaleResources; } + + void incrementRequestCount(const CachedResource*); + void decrementRequestCount(const CachedResource*); + int requestCount(); + + void clearPreloads(); + void clearPendingPreloads(); + 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, ResourceLoadPriority priority = ResourceLoadPriorityUnresolved, bool isPreload = false); + CachedResource* revalidateResource(CachedResource*, ResourceLoadPriority priority); + CachedResource* loadResource(CachedResource::Type, const KURL&, const String& charset, ResourceLoadPriority priority); + void requestPreload(CachedResource::Type, const String& url, const String& charset); + + enum RevalidationPolicy { Use, Revalidate, Reload, Load }; + RevalidationPolicy determineRevalidationPolicy(CachedResource::Type, bool forPreload, CachedResource* existingResource) const; + + void notifyLoadedFromMemoryCache(CachedResource*); + bool canRequest(CachedResource::Type, const KURL&); + + MemoryCache* m_cache; + HashSet<String> m_validatedURLs; + mutable DocumentResourceMap m_documentResources; + Document* m_document; + + typedef HashSet<RefPtr<CachedResourceRequest> > RequestSet; + RequestSet m_requests; + + int m_requestCount; + + OwnPtr<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_loadFinishing : 1; + bool m_allowStaleResources : 1; +}; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedResourceRequest.cpp b/Source/WebCore/loader/cache/CachedResourceRequest.cpp new file mode 100644 index 0000000..827bb8e --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceRequest.cpp @@ -0,0 +1,280 @@ +/* + 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 "CachedResourceRequest.h" + +#include "MemoryCache.h" +#include "CachedImage.h" +#include "CachedResource.h" +#include "CachedResourceLoader.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "Logging.h" +#include "ResourceHandle.h" +#include "ResourceLoadScheduler.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "SharedBuffer.h" +#include <wtf/Assertions.h> +#include <wtf/Vector.h> +#include <wtf/text/CString.h> + +namespace WebCore { + +static ResourceRequest::TargetType cachedResourceTypeToTargetType(CachedResource::Type type) +{ + switch (type) { + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif + return ResourceRequest::TargetIsStyleSheet; + case CachedResource::Script: + return ResourceRequest::TargetIsScript; + case CachedResource::FontResource: + return ResourceRequest::TargetIsFontResource; + case CachedResource::ImageResource: + return ResourceRequest::TargetIsImage; +#if ENABLE(LINK_PREFETCH) + case CachedResource::LinkPrefetch: + return ResourceRequest::TargetIsPrefetch; +#endif + } + ASSERT_NOT_REACHED(); + return ResourceRequest::TargetIsSubresource; +} + +CachedResourceRequest::CachedResourceRequest(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental) + : m_cachedResourceLoader(cachedResourceLoader) + , m_resource(resource) + , m_incremental(incremental) + , m_multipart(false) + , m_finishing(false) +{ + m_resource->setRequest(this); +} + +CachedResourceRequest::~CachedResourceRequest() +{ + m_resource->setRequest(0); +} + +PassRefPtr<CachedResourceRequest> CachedResourceRequest::load(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks) +{ + RefPtr<CachedResourceRequest> request = adoptRef(new CachedResourceRequest(cachedResourceLoader, resource, incremental)); + + ResourceRequest resourceRequest(resource->url()); + resourceRequest.setTargetType(cachedResourceTypeToTargetType(resource->type())); + + if (!resource->accept().isEmpty()) + resourceRequest.setHTTPAccept(resource->accept()); + + if (resource->isCacheValidator()) { + CachedResource* resourceToRevalidate = resource->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()) { + ASSERT(cachedResourceLoader->cachePolicy() != CachePolicyReload); + if (cachedResourceLoader->cachePolicy() == CachePolicyRevalidate) + 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); + } + } + +#if ENABLE(LINK_PREFETCH) + if (resource->type() == CachedResource::LinkPrefetch) + resourceRequest.setHTTPHeaderField("Purpose", "prefetch"); +#endif + + ResourceLoadPriority priority = resource->loadPriority(); + + RefPtr<SubresourceLoader> loader = resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->document()->frame(), + request.get(), resourceRequest, priority, securityCheck, sendResourceLoadCallbacks); + if (!loader || loader->reachedTerminalState()) { + // FIXME: What if resources in other frames were waiting for this revalidation? + LOG(ResourceLoading, "Cannot start loading '%s'", resource->url().latin1().data()); + cachedResourceLoader->decrementRequestCount(resource); + cachedResourceLoader->loadFinishing(); + if (resource->resourceToRevalidate()) + cache()->revalidationFailed(resource); + resource->error(CachedResource::LoadError); + cachedResourceLoader->loadDone(0); + return 0; + } + request->m_loader = loader; + return request.release(); +} + +void CachedResourceRequest::willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse&) +{ + m_resource->setRequestedFromNetworkingLayer(); +} + +void CachedResourceRequest::didFinishLoading(SubresourceLoader* loader) +{ + if (m_finishing) + return; + + ASSERT(loader == m_loader.get()); + ASSERT(!m_resource->resourceToRevalidate()); + LOG(ResourceLoading, "Received '%s'.", m_resource->url().latin1().data()); + + // Prevent the document from being destroyed before we are done with + // the cachedResourceLoader that it will delete when the document gets deleted. + RefPtr<Document> protector(m_cachedResourceLoader->document()); + if (!m_multipart) + m_cachedResourceLoader->decrementRequestCount(m_resource); + m_finishing = true; + + // 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 (!m_resource->errorOccurred()) { + m_cachedResourceLoader->loadFinishing(); + m_resource->data(loader->resourceData(), true); + if (!m_resource->errorOccurred()) + m_resource->finish(); + } + m_cachedResourceLoader->loadDone(this); +} + +void CachedResourceRequest::didFail(SubresourceLoader*, const ResourceError&) +{ + if (!m_loader) + return; + didFail(); +} + +void CachedResourceRequest::didFail(bool cancelled) +{ + if (m_finishing) + return; + + LOG(ResourceLoading, "Failed to load '%s' (cancelled=%d).\n", m_resource->url().latin1().data(), cancelled); + + // Prevent the document from being destroyed before we are done with + // the cachedResourceLoader that it will delete when the document gets deleted. + RefPtr<Document> protector(m_cachedResourceLoader->document()); + if (!m_multipart) + m_cachedResourceLoader->decrementRequestCount(m_resource); + m_finishing = true; + m_loader->clearClient(); + + if (m_resource->resourceToRevalidate()) + cache()->revalidationFailed(m_resource); + + if (!cancelled) { + m_cachedResourceLoader->loadFinishing(); + m_resource->error(CachedResource::LoadError); + } + + if (cancelled || !m_resource->isPreloaded()) + cache()->remove(m_resource); + + m_cachedResourceLoader->loadDone(this); +} + +void CachedResourceRequest::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response) +{ + ASSERT(loader == m_loader.get()); + if (m_resource->isCacheValidator()) { + if (response.httpStatusCode() == 304) { + // 304 Not modified / Use local copy + loader->clearClient(); + RefPtr<Document> protector(m_cachedResourceLoader->document()); + m_cachedResourceLoader->decrementRequestCount(m_resource); + m_finishing = true; + + // Existing resource is ok, just use it updating the expiration time. + cache()->revalidationSucceeded(m_resource, response); + + if (m_cachedResourceLoader->frame()) + m_cachedResourceLoader->frame()->loader()->checkCompleted(); + + m_cachedResourceLoader->loadDone(this); + return; + } + // Did not get 304 response, continue as a regular resource load. + cache()->revalidationFailed(m_resource); + } + + m_resource->setResponse(response); + + String encoding = response.textEncodingName(); + if (!encoding.isNull()) + m_resource->setEncoding(encoding); + + if (m_multipart) { + ASSERT(m_resource->isImage()); + static_cast<CachedImage*>(m_resource)->clear(); + if (m_cachedResourceLoader->frame()) + m_cachedResourceLoader->frame()->loader()->checkCompleted(); + } else if (response.isMultipart()) { + m_multipart = true; + + // We don't count multiParts in a CachedResourceLoader's request count + m_cachedResourceLoader->decrementRequestCount(m_resource); + + // If we get a multipart response, we must have a handle + ASSERT(loader->handle()); + if (!m_resource->isImage()) + loader->handle()->cancel(); + } +} + +void CachedResourceRequest::didReceiveData(SubresourceLoader* loader, const char* data, int size) +{ + ASSERT(loader == m_loader.get()); + ASSERT(!m_resource->isCacheValidator()); + + if (m_resource->errorOccurred()) + return; + + if (m_resource->response().httpStatusCode() >= 400) { + if (!m_resource->shouldIgnoreHTTPStatusCodeErrors()) + m_resource->error(CachedResource::LoadError); + return; + } + + // Set the data. + if (m_multipart) { + // 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); + m_resource->data(copiedData.release(), true); + } else if (m_incremental) + m_resource->data(loader->resourceData(), false); +} + +void CachedResourceRequest::didReceiveCachedMetadata(SubresourceLoader*, const char* data, int size) +{ + ASSERT(!m_resource->isCacheValidator()); + m_resource->setSerializedCachedMetadata(data, size); +} + +} //namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedResourceRequest.h b/Source/WebCore/loader/cache/CachedResourceRequest.h new file mode 100644 index 0000000..389b9ce --- /dev/null +++ b/Source/WebCore/loader/cache/CachedResourceRequest.h @@ -0,0 +1,65 @@ +/* + 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 CachedResourceRequest_h +#define CachedResourceRequest_h + +#include "FrameLoaderTypes.h" +#include "SubresourceLoader.h" +#include "SubresourceLoaderClient.h" +#include <wtf/HashMap.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + + class CachedResource; + class CachedResourceLoader; + class Request; + + class CachedResourceRequest : public RefCounted<CachedResourceRequest>, private SubresourceLoaderClient { + public: + static PassRefPtr<CachedResourceRequest> load(CachedResourceLoader*, CachedResource*, bool incremental, SecurityCheckPolicy, bool sendResourceLoadCallbacks); + ~CachedResourceRequest(); + void didFail(bool cancelled = false); + + CachedResourceLoader* cachedResourceLoader() const { return m_cachedResourceLoader; } + + private: + CachedResourceRequest(CachedResourceLoader*, CachedResource*, bool incremental); + virtual void willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse&); + virtual void didReceiveResponse(SubresourceLoader*, const ResourceResponse&); + virtual void didReceiveData(SubresourceLoader*, const char*, int); + virtual void didReceiveCachedMetadata(SubresourceLoader*, const char*, int); + virtual void didFinishLoading(SubresourceLoader*); + virtual void didFail(SubresourceLoader*, const ResourceError&); + + RefPtr<SubresourceLoader> m_loader; + CachedResourceLoader* m_cachedResourceLoader; + CachedResource* m_resource; + bool m_incremental; + bool m_multipart; + bool m_finishing; + }; + +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedScript.cpp b/Source/WebCore/loader/cache/CachedScript.cpp new file mode 100644 index 0000000..54b4503 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedScript.cpp @@ -0,0 +1,124 @@ +/* + 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. + + 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 "MemoryCache.h" +#include "CachedResourceClient.h" +#include "CachedResourceClientWalker.h" +#include "SharedBuffer.h" +#include "TextResourceDecoder.h" +#include <wtf/Vector.h> + +namespace WebCore { + +CachedScript::CachedScript(const String& url, const String& charset) + : CachedResource(url, Script) + , m_decoder(TextResourceDecoder::create("application/javascript", charset)) + , m_decodedDataDeletionTimer(this, &CachedScript::decodedDataDeletionTimerFired) +{ + // 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("*/*"); +} + +CachedScript::~CachedScript() +{ +} + +void CachedScript::allClientsRemoved() +{ + m_decodedDataDeletionTimer.startOneShot(0); +} + +void CachedScript::setEncoding(const String& chs) +{ + m_decoder->setEncoding(chs, TextResourceDecoder::EncodingFromHTTPHeader); +} + +String CachedScript::encoding() const +{ + return m_decoder->encoding().name(); +} + +const String& CachedScript::script() +{ + ASSERT(!isPurgeable()); + + if (!m_script && m_data) { + m_script = m_decoder->decode(m_data->data(), encodedSize()); + m_script += m_decoder->flush(); + setDecodedSize(m_script.length() * sizeof(UChar)); + } + m_decodedDataDeletionTimer.startOneShot(0); + return m_script; +} + +void CachedScript::data(PassRefPtr<SharedBuffer> data, bool allDataReceived) +{ + if (!allDataReceived) + return; + + m_data = data; + setEncodedSize(m_data.get() ? m_data->size() : 0); + setLoading(false); + checkNotify(); +} + +void CachedScript::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient* c = w.next()) + c->notifyFinished(this); +} + +void CachedScript::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +void CachedScript::destroyDecodedData() +{ + m_script = String(); + setDecodedSize(0); + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() && isSafeToMakePurgeable()) + makePurgeable(true); +} + +void CachedScript::decodedDataDeletionTimerFired(Timer<CachedScript>*) +{ + destroyDecodedData(); +} + +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/CachedScript.h b/Source/WebCore/loader/cache/CachedScript.h new file mode 100644 index 0000000..30fcb1e --- /dev/null +++ b/Source/WebCore/loader/cache/CachedScript.h @@ -0,0 +1,65 @@ +/* + 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, 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 CachedScript_h +#define CachedScript_h + +#include "CachedResource.h" +#include "Timer.h" + +namespace WebCore { + + class CachedResourceLoader; + class TextResourceDecoder; + + class CachedScript : public CachedResource { + public: + CachedScript(const String& url, const String& charset); + virtual ~CachedScript(); + + const String& script(); + + virtual void allClientsRemoved(); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + void checkNotify(); + + virtual void destroyDecodedData(); + + private: + void decodedDataDeletionTimerFired(Timer<CachedScript>*); + virtual PurgePriority purgePriority() const { return PurgeLast; } + + String m_script; + RefPtr<TextResourceDecoder> m_decoder; + Timer<CachedScript> m_decodedDataDeletionTimer; + }; +} + +#endif diff --git a/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp b/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp new file mode 100644 index 0000000..ca7bf13 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedXSLStyleSheet.cpp @@ -0,0 +1,100 @@ +/* + 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. + + 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 "SharedBuffer.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::didAddClient(CachedResourceClient* c) +{ + if (!isLoading()) + c->setXSLStyleSheet(m_url, m_response.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(); + } + setLoading(false); + checkNotify(); +} + +void CachedXSLStyleSheet::checkNotify() +{ + if (isLoading()) + return; + + CachedResourceClientWalker w(m_clients); + while (CachedResourceClient *c = w.next()) + c->setXSLStyleSheet(m_url, m_response.url(), m_sheet); +} + +void CachedXSLStyleSheet::error(CachedResource::Status status) +{ + setStatus(status); + ASSERT(errorOccurred()); + setLoading(false); + checkNotify(); +} + +#endif + +} diff --git a/Source/WebCore/loader/cache/CachedXSLStyleSheet.h b/Source/WebCore/loader/cache/CachedXSLStyleSheet.h new file mode 100644 index 0000000..8b29792 --- /dev/null +++ b/Source/WebCore/loader/cache/CachedXSLStyleSheet.h @@ -0,0 +1,62 @@ +/* + 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, 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 CachedXSLStyleSheet_h +#define CachedXSLStyleSheet_h + +#include "CachedResource.h" +#include <wtf/Vector.h> + +namespace WebCore { + + class CachedResourceLoader; + class TextResourceDecoder; + +#if ENABLE(XSLT) + class CachedXSLStyleSheet : public CachedResource { + public: + CachedXSLStyleSheet(const String& url); + + const String& sheet() const { return m_sheet; } + + virtual void didAddClient(CachedResourceClient*); + + virtual void setEncoding(const String&); + virtual String encoding() const; + virtual void data(PassRefPtr<SharedBuffer> data, bool allDataReceived); + virtual void error(CachedResource::Status); + + void checkNotify(); + + protected: + String m_sheet; + RefPtr<TextResourceDecoder> m_decoder; + }; + +#endif + +} + +#endif diff --git a/Source/WebCore/loader/cache/MemoryCache.cpp b/Source/WebCore/loader/cache/MemoryCache.cpp new file mode 100644 index 0000000..930033a --- /dev/null +++ b/Source/WebCore/loader/cache/MemoryCache.cpp @@ -0,0 +1,670 @@ +/* + 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 "MemoryCache.h" + +#include "CachedCSSStyleSheet.h" +#include "CachedFont.h" +#include "CachedImage.h" +#include "CachedScript.h" +#include "CachedXSLStyleSheet.h" +#include "CachedResourceLoader.h" +#include "Document.h" +#include "FrameLoader.h" +#include "FrameLoaderTypes.h" +#include "FrameView.h" +#include "Image.h" +#include "Logging.h" +#include "ResourceHandle.h" +#include "SecurityOrigin.h" +#include <stdio.h> +#include <wtf/CurrentTime.h> +#include <wtf/text/CString.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; + +MemoryCache* cache() +{ + static MemoryCache* staticCache = new MemoryCache; + return staticCache; +} + +MemoryCache::MemoryCache() + : 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) +{ +} + +KURL MemoryCache::removeFragmentIdentifierIfNeeded(const KURL& originalURL) +{ + if (!originalURL.hasFragmentIdentifier()) + return originalURL; + // Strip away fragment identifier from HTTP and file urls. + // Data urls must be unmodified and it is also safer to keep them for custom protocols. + if (!(originalURL.protocolInHTTPFamily() || originalURL.isLocalFile())) + return originalURL; + KURL url = originalURL; + url.removeFragmentIdentifier(); + return url; +} + +bool MemoryCache::add(CachedResource* resource) +{ + if (disabled()) + return false; + + m_resources.set(resource->url(), resource); + resource->setInCache(true); + + resourceAccessed(resource); + + LOG(ResourceLoading, "MemoryCache::add Added '%s', resource %p\n", resource->url().latin1().data(), resource); + return true; +} + +void MemoryCache::revalidationSucceeded(CachedResource* revalidatingResource, const ResourceResponse& response) +{ + CachedResource* resource = revalidatingResource->resourceToRevalidate(); + ASSERT(resource); + ASSERT(!resource->inCache()); + ASSERT(resource->isLoaded()); + ASSERT(revalidatingResource->inCache()); + + evict(revalidatingResource); + + ASSERT(!m_resources.get(resource->url())); + m_resources.set(resource->url(), resource); + resource->setInCache(true); + resource->updateResponseAfterRevalidation(response); + 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 MemoryCache::revalidationFailed(CachedResource* revalidatingResource) +{ + LOG(ResourceLoading, "Revalidation failed for %p", revalidatingResource); + ASSERT(revalidatingResource->resourceToRevalidate()); + revalidatingResource->clearResourceToRevalidate(); +} + +CachedResource* MemoryCache::resourceForURL(const KURL& resourceURL) +{ + KURL url = removeFragmentIdentifierIfNeeded(resourceURL); + CachedResource* resource = m_resources.get(url); + bool wasPurgeable = MemoryCache::shouldMakeResourcePurgeableOnEviction() && resource && resource->isPurgeable(); + if (resource && !resource->makePurgeable(false)) { + ASSERT(!resource->hasClients()); + evict(resource); + return 0; + } + // Add the size back since we had subtracted it when we marked the memory as purgeable. + if (wasPurgeable) + adjustSize(resource->hasClients(), resource->size()); + return resource; +} + +unsigned MemoryCache::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 MemoryCache::liveCapacity() const +{ + // Live resource capacity is whatever is left over after calculating dead resource capacity. + return m_capacity - deadCapacity(); +} + +void MemoryCache::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 = WTF::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. + + // The list might not be sorted by the m_lastDecodedAccessTime. The impact + // of this weaker invariant is minor as the below if statement to check the + // elapsedTime will evaluate to false as the currentTime will be a lot + // greater than the current->m_lastDecodedAccessTime. + // For more details see: https://bugs.webkit.org/show_bug.cgi?id=30209 + 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 different LRU + // list in m_allResources. + current->destroyDecodedData(); + + if (targetSize && m_liveSize <= targetSize) + return; + } + current = prev; + } +} + +void MemoryCache::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(); + + if (!m_inPruneDeadResources) { + // See if we have any purged resources we can evict. + for (int i = 0; i < size; i++) { + CachedResource* current = m_allResources[i].m_tail; + while (current) { + CachedResource* prev = current->m_prevInAllResourcesList; + if (current->wasPurged()) { + ASSERT(!current->hasClients()); + ASSERT(!current->isPreloaded()); + evict(current); + } + current = prev; + } + } + if (targetSize && m_deadSize <= targetSize) + return; + } + + 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()) { + // Destroy our decoded data. This will remove us from + // m_liveDecodedResources, and possibly move us to a different + // 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() && !current->isCacheValidator()) { + if (!makeResourcePurgeable(current)) + 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 MemoryCache::setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes) +{ + ASSERT(minDeadBytes <= maxDeadBytes); + ASSERT(maxDeadBytes <= totalBytes); + m_minDeadCapacity = minDeadBytes; + m_maxDeadCapacity = maxDeadBytes; + m_capacity = totalBytes; + prune(); +} + +bool MemoryCache::makeResourcePurgeable(CachedResource* resource) +{ + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction()) + return false; + + if (!resource->inCache()) + return false; + + if (resource->isPurgeable()) + return true; + + if (!resource->isSafeToMakePurgeable()) + return false; + + if (!resource->makePurgeable(true)) + return false; + + adjustSize(resource->hasClients(), -static_cast<int>(resource->size())); + + return true; +} + +void MemoryCache::evict(CachedResource* resource) +{ + LOG(ResourceLoading, "Evicting resource %p for '%s' from cache", resource, resource->url().latin1().data()); + // 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); + + // If the resource was purged, it means we had already decremented the size when we made the + // resource purgeable in makeResourcePurgeable(). So adjust the size if we are evicting a + // resource that was not marked as purgeable. + if (!MemoryCache::shouldMakeResourcePurgeableOnEviction() || !resource->isPurgeable()) + adjustSize(resource->hasClients(), -static_cast<int>(resource->size())); + } else + ASSERT(m_resources.get(resource->url()) != resource); + + if (resource->canDelete()) + delete resource; +} + +void MemoryCache::addCachedResourceLoader(CachedResourceLoader* cachedResourceLoader) +{ + m_cachedResourceLoaders.add(cachedResourceLoader); +} + +void MemoryCache::removeCachedResourceLoader(CachedResourceLoader* cachedResourceLoader) +{ + m_cachedResourceLoaders.remove(cachedResourceLoader); +} + +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; +} + +MemoryCache::LRUList* MemoryCache::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 MemoryCache::removeFromLRUList(CachedResource* resource) +{ + // If we've never been accessed, then we're brand new and not in any list. + if (resource->accessCount() == 0) + return; + +#if !ASSERT_DISABLED + unsigned oldListIndex = resource->m_lruIndex; +#endif + + LRUList* list = lruListFor(resource); + +#if !ASSERT_DISABLED + // 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 MemoryCache::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 MemoryCache::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); + + // If this is the first time the resource has been accessed, adjust the size of the cache to account for its initial size. + if (!resource->accessCount()) + adjustSize(resource->hasClients(), resource->size()); + + // Add to our access count. + resource->increaseAccessCount(); + + // Now insert into the new queue. + insertInLRUList(resource); +} + +void MemoryCache::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 MemoryCache::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 MemoryCache::addToLiveResourcesSize(CachedResource* resource) +{ + m_liveSize += resource->size(); + m_deadSize -= resource->size(); +} + +void MemoryCache::removeFromLiveResourcesSize(CachedResource* resource) +{ + m_liveSize -= resource->size(); + m_deadSize += resource->size(); +} + +void MemoryCache::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; + } +} + +void MemoryCache::TypeStatistic::addResource(CachedResource* o) +{ + bool purged = o->wasPurged(); + bool purgeable = o->isPurgeable() && !purged; + int pageSize = (o->encodedSize() + o->overheadSize() + 4095) & ~4095; + count++; + size += purged ? 0 : o->size(); + liveSize += o->hasClients() ? o->size() : 0; + decodedSize += o->decodedSize(); + purgeableSize += purgeable ? pageSize : 0; + purgedSize += purged ? pageSize : 0; +} + +MemoryCache::Statistics MemoryCache::getStatistics() +{ + Statistics stats; + CachedResourceMap::iterator e = m_resources.end(); + for (CachedResourceMap::iterator i = m_resources.begin(); i != e; ++i) { + CachedResource* resource = i->second; + switch (resource->type()) { + case CachedResource::ImageResource: + stats.images.addResource(resource); + break; + case CachedResource::CSSStyleSheet: + stats.cssStyleSheets.addResource(resource); + break; + case CachedResource::Script: + stats.scripts.addResource(resource); + break; +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: + stats.xslStyleSheets.addResource(resource); + break; +#endif + case CachedResource::FontResource: + stats.fonts.addResource(resource); + break; + default: + break; + } + } + return stats; +} + +void MemoryCache::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 MemoryCache::dumpStats() +{ + Statistics s = getStatistics(); + printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "", "Count", "Size", "LiveSize", "DecodedSize", "PurgeableSize", "PurgedSize"); + printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Images", s.images.count, s.images.size, s.images.liveSize, s.images.decodedSize, s.images.purgeableSize, s.images.purgedSize); + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "CSS", s.cssStyleSheets.count, s.cssStyleSheets.size, s.cssStyleSheets.liveSize, s.cssStyleSheets.decodedSize, s.cssStyleSheets.purgeableSize, s.cssStyleSheets.purgedSize); +#if ENABLE(XSLT) + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "XSL", s.xslStyleSheets.count, s.xslStyleSheets.size, s.xslStyleSheets.liveSize, s.xslStyleSheets.decodedSize, s.xslStyleSheets.purgeableSize, s.xslStyleSheets.purgedSize); +#endif + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "JavaScript", s.scripts.count, s.scripts.size, s.scripts.liveSize, s.scripts.decodedSize, s.scripts.purgeableSize, s.scripts.purgedSize); + printf("%-13s %13d %13d %13d %13d %13d %13d\n", "Fonts", s.fonts.count, s.fonts.size, s.fonts.liveSize, s.fonts.decodedSize, s.fonts.purgeableSize, s.fonts.purgedSize); + printf("%-13s %-13s %-13s %-13s %-13s %-13s %-13s\n\n", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------", "-------------"); +} + +void MemoryCache::dumpLRULists(bool includeLive) const +{ + printf("LRU-SP lists in eviction order (Kilobytes decoded, Kilobytes encoded, Access count, Referenced, isPurgeable, wasPurged):\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, %d, %d); ", current->decodedSize() / 1024.0f, (current->encodedSize() + current->overheadSize()) / 1024.0f, current->accessCount(), current->hasClients(), current->isPurgeable(), current->wasPurged()); + + current = prev; + } + } +} +#endif + +} // namespace WebCore diff --git a/Source/WebCore/loader/cache/MemoryCache.h b/Source/WebCore/loader/cache/MemoryCache.h new file mode 100644 index 0000000..dc47733 --- /dev/null +++ b/Source/WebCore/loader/cache/MemoryCache.h @@ -0,0 +1,231 @@ +/* + 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 <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/Vector.h> +#include <wtf/text/StringHash.h> + +namespace WebCore { + +class CachedCSSStyleSheet; +class CachedResource; +class CachedResourceLoader; +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) +// --|----------++++++++++| +// -------|-----+++++++++++++++| +// -------|-----+++++++++++++++|+++++ + +// The behavior of the cache changes in the following way if shouldMakeResourcePurgeableOnEviction +// returns true. +// +// 1. Dead resources in the cache are kept in non-purgeable memory. +// 2. When we prune dead resources, instead of freeing them, we mark their memory as purgeable and +// keep the resources until the kernel reclaims the purgeable memory. +// +// By leaving the in-cache dead resources in dirty resident memory, we decrease the likelihood of +// the kernel claiming that memory and forcing us to refetch the resource (for example when a user +// presses back). +// +// And by having an unbounded number of resource objects using purgeable memory, we can use as much +// memory as is available on the machine. The trade-off here is that the CachedResource object (and +// its member variables) are allocated in non-purgeable TC-malloc'd memory so we would see slightly +// more memory use due to this. + +class MemoryCache : public Noncopyable { +public: + friend MemoryCache* 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; + int purgeableSize; + int purgedSize; + TypeStatistic() : count(0), size(0), liveSize(0), decodedSize(0), purgeableSize(0), purgedSize(0) { } + void addResource(CachedResource*); + }; + + struct Statistics { + TypeStatistic images; + TypeStatistic cssStyleSheets; + TypeStatistic scripts; +#if ENABLE(XSLT) + TypeStatistic xslStyleSheets; +#endif + TypeStatistic fonts; + }; + + CachedResource* resourceForURL(const KURL&); + + bool add(CachedResource* resource); + void remove(CachedResource* resource) { evict(resource); } + + static KURL removeFragmentIdentifierIfNeeded(const KURL& originalURL); + + 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; } + + void addCachedResourceLoader(CachedResourceLoader*); + void removeCachedResourceLoader(CachedResourceLoader*); + + // 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*); + + static bool shouldMakeResourcePurgeableOnEviction(); + + // Function to collect cache statistics for the caches window in the Safari Debug menu. + Statistics getStatistics(); + + void resourceAccessed(CachedResource*); + +#ifdef ANDROID_INSTRUMENT + unsigned getLiveSize() { return m_liveSize; } + unsigned getDeadSize() { return m_deadSize; } +#endif + +private: + MemoryCache(); + ~MemoryCache(); // Not implemented to make sure nobody accidentally calls delete -- WebCore does not delete singletons. + + LRUList* lruListFor(CachedResource*); +#ifndef NDEBUG + void dumpStats(); + 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. + + bool makeResourcePurgeable(CachedResource*); + void evict(CachedResource*); + + // Member variables. + HashSet<CachedResourceLoader*> m_cachedResourceLoaders; + + 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" multiple 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; +}; + +inline bool MemoryCache::shouldMakeResourcePurgeableOnEviction() +{ +#if PLATFORM(IOS) + return true; +#else + return false; +#endif +} + +// Function to obtain the global cache. +MemoryCache* cache(); + +} + +#endif diff --git a/Source/WebCore/loader/cf/ResourceLoaderCFNet.cpp b/Source/WebCore/loader/cf/ResourceLoaderCFNet.cpp new file mode 100644 index 0000000..833fdeb --- /dev/null +++ b/Source/WebCore/loader/cf/ResourceLoaderCFNet.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009 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 "ResourceLoader.h" + +#if USE(CFNETWORK) + +#include "FrameLoader.h" +#include "FrameLoaderClient.h" + +namespace WebCore { + +bool ResourceLoader::shouldCacheResponse(ResourceHandle*, CFCachedURLResponseRef cachedResponse) +{ + if (!m_sendResourceLoadCallbacks) + return 0; + + CFURLResponseRef response = CFCachedURLResponseGetWrappedResponse(cachedResponse); + CFDataRef data = CFCachedURLResponseGetReceiverData(cachedResponse); + return frameLoader()->client()->shouldCacheResponse(documentLoader(), identifier(), response, CFDataGetBytePtr(data), CFDataGetLength(data)); +} + +} // namespace WebCore + +#endif // USE(CFNETWORK) diff --git a/Source/WebCore/loader/icon/IconDatabase.cpp b/Source/WebCore/loader/icon/IconDatabase.cpp new file mode 100644 index 0000000..6040037 --- /dev/null +++ b/Source/WebCore/loader/icon/IconDatabase.cpp @@ -0,0 +1,2096 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 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" + +#if ENABLE(ICONDATABASE) + +#include "AutodrainedPool.h" +#include "DocumentLoader.h" +#include "FileSystem.h" +#include "IconDatabaseClient.h" +#include "IconRecord.h" +#include "IntSize.h" +#include "Logging.h" +#include "ScriptController.h" +#include "SQLiteStatement.h" +#include "SQLiteTransaction.h" +#include "SuddenTermination.h" +#include <wtf/CurrentTime.h> +#include <wtf/MainThread.h> +#include <wtf/StdLibExtras.h> +#include <wtf/text/CString.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) || PLATFORM(GTK) +#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" +static const int currentDatabaseVersion = 6; + +// Icons expire once every 4 days +static const int iconExpirationTime = 60*60*24*4; + +static 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) { + ScriptController::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.crossThreadString(); + + // 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_syncThreadRunning = m_syncThread; + 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; + + m_syncDB.close(); + ASSERT(!isOpen()); +} + +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.crossThreadString(); + 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.crossThreadString(); + + 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.crossThreadString()); + + // 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().threadsafeCopy() : 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 }; + + DEFINE_STATIC_LOCAL(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.crossThreadString(); + + record = new PageURLRecord(pageURL); + m_pageURLToRecordMap.set(pageURL, record); + } + + if (!record->retain()) { + if (pageURL.isNull()) + pageURL = pageURLOriginal.crossThreadString(); + + // 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.crossThreadString(), 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.crossThreadString(); + + 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.crossThreadString(); + iconURL = iconURLOriginal.crossThreadString(); + + 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_syncTimer(this, &IconDatabase::syncTimerFired) + , m_syncThreadRunning(false) + , m_isEnabled(false) + , m_privateBrowsingEnabled(false) + , m_threadTerminationRequested(false) + , m_removeIconsRequested(false) + , m_iconURLImportComplete(false) + , m_disabledSuddenTerminationForSyncThread(false) + , m_initialPruningComplete(false) + , m_client(defaultClient()) + , m_imported(false) + , m_isImportedSet(false) +{ + ASSERT(isMainThread()); +} + +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); + + if (!m_disabledSuddenTerminationForSyncThread) { + m_disabledSuddenTerminationForSyncThread = true; + // The following is balanced by the call to enableSuddenTermination in the + // syncThreadMainLoop function. + // FIXME: It would be better to only disable sudden termination if we have + // something to write, not just if we have something to read. + disableSuddenTermination(); + } + + m_syncCondition.signal(); +} + +void IconDatabase::scheduleOrDeferSyncTimer() +{ + ASSERT_NOT_SYNC_THREAD(); + + if (!m_syncTimer.isActive()) { + // The following is balanced by the call to enableSuddenTermination in the + // syncTimerFired function. + disableSuddenTermination(); + } + + m_syncTimer.startOneShot(updateTimerDelay); +} + +void IconDatabase::syncTimerFired(Timer<IconDatabase>*) +{ + ASSERT_NOT_SYNC_THREAD(); + wakeSyncThread(); + + // The following is balanced by the call to disableSuddenTermination in the + // scheduleOrDeferSyncTimer function. + enableSuddenTermination(); +} + +// ****************** +// *** 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.threadsafeCopy(); +} + +String IconDatabase::defaultDatabaseFilename() +{ + DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db")); + return defaultDatabaseFilename.threadsafeCopy(); +} + +// 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 %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(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(); + + bool shouldReenableSuddenTermination = 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 + static bool prunedUnretainedIcons = false; + 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; + + if (shouldReenableSuddenTermination) { + // The following is balanced by the call to disableSuddenTermination in the + // wakeSyncThread function. Any time we wait on the condition, we also have + // to enableSuddenTermation, after doing the next batch of work. + ASSERT(m_disabledSuddenTerminationForSyncThread); + enableSuddenTermination(); + m_disabledSuddenTerminationForSyncThread = false; + } + + m_syncCondition.wait(m_syncLock); + + shouldReenableSuddenTermination = true; + } + + m_syncLock.unlock(); + + // Thread is terminating at this point + cleanupSyncThread(); + + if (shouldReenableSuddenTermination) { + // The following is balanced by the call to disableSuddenTermination in the + // wakeSyncThread function. Any time we wait on the condition, we also have + // to enableSuddenTermation, after doing the next batch of work. + ASSERT(m_disabledSuddenTerminationForSyncThread); + enableSuddenTermination(); + m_disabledSuddenTerminationForSyncThread = false; + } + + return 0; +} + +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) { +#if OS(WINDOWS) + LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); +#else + LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i])); +#endif + pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]); + int result = pageDeleteSQL.step(); + if (result != SQLResultDone) +#if OS(WINDOWS) + LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); +#else + LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i])); +#endif + 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.clear(); + m_removePageURLStatement.clear(); + m_getIconIDForIconURLStatement.clear(); + m_getImageDataForIconURLStatement.clear(); + m_addIconToIconInfoStatement.clear(); + m_addIconToIconDataStatement.clear(); + m_getImageDataStatement.clear(); + m_deletePageURLsForIconURLStatement.clear(); + m_deleteIconFromIconInfoStatement.clear(); + m_deleteIconFromIconDataStatement.clear(); + m_updateIconInfoStatement.clear(); + m_updateIconDataStatement.clear(); + m_setIconInfoStatement.clear(); + m_setIconDataStatement.clear(); +} + +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 = adoptPtr(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 + +#endif // ENABLE(ICONDATABASE) diff --git a/Source/WebCore/loader/icon/IconDatabase.h b/Source/WebCore/loader/icon/IconDatabase.h new file mode 100644 index 0000000..6146aa6 --- /dev/null +++ b/Source/WebCore/loader/icon/IconDatabase.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009 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 + +#include "Timer.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> +#include <wtf/text/StringHash.h> + +#if ENABLE(ICONDATABASE) +#include "SQLiteDatabase.h" +#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 : public 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(); + void syncTimerFired(Timer<IconDatabase>*); + + Timer<IconDatabase> m_syncTimer; + 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; + bool m_disabledSuddenTerminationForSyncThread; + + 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; +#endif // ENABLE(ICONDATABASE) + +// *** 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; + +#if ENABLE(ICONDATABASE) +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 // IconDatabase_h diff --git a/Source/WebCore/loader/icon/IconDatabaseClient.h b/Source/WebCore/loader/icon/IconDatabaseClient.h new file mode 100644 index 0000000..c210d7d --- /dev/null +++ b/Source/WebCore/loader/icon/IconDatabaseClient.h @@ -0,0 +1,50 @@ +/* + * 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 + +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> + +// All of these client methods will be called from a non-main thread +// Take appropriate measures + +namespace WebCore { + +class IconDatabaseClient : public Noncopyable { +public: + virtual ~IconDatabaseClient() { } + virtual bool performImport() { return true; } + virtual void dispatchDidRemoveAllIcons() { } + virtual void dispatchDidAddIconForPageURL(const String& /*pageURL*/) { } +}; + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/loader/icon/IconDatabaseNone.cpp b/Source/WebCore/loader/icon/IconDatabaseNone.cpp new file mode 100644 index 0000000..7b7cc9f --- /dev/null +++ b/Source/WebCore/loader/icon/IconDatabaseNone.cpp @@ -0,0 +1,218 @@ +/* + * 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" + +#if !ENABLE(ICONDATABASE) + +#include "PlatformString.h" +#include "SharedBuffer.h" +#include <wtf/StdLibExtras.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() +{ + DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("Icons.db")); + return defaultDatabaseFilename.threadsafeCopy(); +} + +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() +{ +} + +size_t IconDatabase::pageURLMappingCount() +{ + return 0; +} + +size_t IconDatabase::retainedPageURLCount() +{ + return 0; +} + +size_t IconDatabase::iconRecordCount() +{ + return 0; +} + +size_t IconDatabase::iconRecordCountWithData() +{ + return 0; +} + +void IconDatabase::setClient(IconDatabaseClient*) +{ +} + +// ************************ +// *** Sync Thread Only *** +// ************************ + +void IconDatabase::importIconURLForPageURL(const String&, const String&) +{ +} + +void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer>, const String&) +{ +} + +bool IconDatabase::shouldStopThreadActivity() const +{ + return true; +} + +} // namespace WebCore + +#endif // !ENABLE(ICONDATABASE) diff --git a/Source/WebCore/loader/icon/IconLoader.cpp b/Source/WebCore/loader/icon/IconLoader.cpp new file mode 100644 index 0000000..24562d0 --- /dev/null +++ b/Source/WebCore/loader/icon/IconLoader.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 "IconLoader.h" + +#include "Document.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "IconDatabase.h" +#include "Logging.h" +#include "ResourceHandle.h" +#include "ResourceLoadScheduler.h" +#include "ResourceResponse.h" +#include "ResourceRequest.h" +#include "SharedBuffer.h" +#include "SubresourceLoader.h" +#include <wtf/UnusedParam.h> +#include <wtf/text/CString.h> + +using namespace std; + +namespace WebCore { + +IconLoader::IconLoader(Frame* frame) + : m_frame(frame) + , m_loadIsInProgress(false) +{ +} + +PassOwnPtr<IconLoader> IconLoader::create(Frame* frame) +{ + return adoptPtr(new IconLoader(frame)); +} + +IconLoader::~IconLoader() +{ +} + +void IconLoader::startLoading() +{ + if (m_resourceLoader) + return; + + // Set flag so we can detect the case where the load completes before + // SubresourceLoader::create returns. + m_loadIsInProgress = true; + + RefPtr<SubresourceLoader> loader = resourceLoadScheduler()->scheduleSubresourceLoad(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->firstRequest().url() : KURL(), 0); + } +} + +void IconLoader::didReceiveData(SubresourceLoader* unusedLoader, const char*, int unusedSize) +{ +#if LOG_DISABLED + UNUSED_PARAM(unusedLoader); + UNUSED_PARAM(unusedSize); +#endif + LOG(IconDatabase, "IconLoader::didReceiveData() - Loader %p, number of bytes %i", unusedLoader, unusedSize); +} + +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->firstRequest().url() : KURL(), 0); + } +} + +void IconLoader::didReceiveAuthenticationChallenge(SubresourceLoader*, const AuthenticationChallenge&) +{ + // 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->firstRequest().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) { + LOG(IconDatabase, "IconLoader::finishLoading() - Committing iconURL %s to database", iconURL.string().ascii().data()); + m_frame->loader()->commitIconURLToIconDatabase(iconURL); + // Setting the icon data only after committing to the database ensures that the data is + // kept in memory (so it does not have to be read from the database asynchronously), since + // there is a page URL referencing it. + iconDatabase()->setIconDataForIconURL(data, iconURL.string()); + m_frame->loader()->client()->dispatchDidReceiveIcon(); + } + + clearLoadingState(); +} + +void IconLoader::clearLoadingState() +{ + m_resourceLoader = 0; + m_loadIsInProgress = false; +} + +} diff --git a/Source/WebCore/loader/icon/IconLoader.h b/Source/WebCore/loader/icon/IconLoader.h new file mode 100644 index 0000000..1ebac48 --- /dev/null +++ b/Source/WebCore/loader/icon/IconLoader.h @@ -0,0 +1,69 @@ +/* + * 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 <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class Frame; +class KURL; +class SharedBuffer; + +class IconLoader : private SubresourceLoaderClient, public Noncopyable { +public: + static PassOwnPtr<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/Source/WebCore/loader/icon/IconRecord.cpp b/Source/WebCore/loader/icon/IconRecord.cpp new file mode 100644 index 0000000..7e90d8e --- /dev/null +++ b/Source/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 <wtf/text/CString.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&) +{ + // 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/Source/WebCore/loader/icon/IconRecord.h b/Source/WebCore/loader/icon/IconRecord.h new file mode 100644 index 0000000..f1fe12f --- /dev/null +++ b/Source/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 "PlatformString.h" +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> +#include <wtf/text/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/Source/WebCore/loader/icon/PageURLRecord.cpp b/Source/WebCore/loader/icon/PageURLRecord.cpp new file mode 100644 index 0000000..09d649f --- /dev/null +++ b/Source/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/Source/WebCore/loader/icon/PageURLRecord.h b/Source/WebCore/loader/icon/PageURLRecord.h new file mode 100644 index 0000000..f7ccb8f --- /dev/null +++ b/Source/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 : public 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/Source/WebCore/loader/icon/wince/IconDatabaseWinCE.cpp b/Source/WebCore/loader/icon/wince/IconDatabaseWinCE.cpp new file mode 100644 index 0000000..54a36e5 --- /dev/null +++ b/Source/WebCore/loader/icon/wince/IconDatabaseWinCE.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007-2009 Torch Mobile 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. + * + */ + +#include "config.h" +#include "IconDatabase.h" + +#include "AutodrainedPool.h" +#include "DocumentLoader.h" +#include "FileSystem.h" +#include "IconDatabaseClient.h" +#include "IconRecord.h" +#include "Image.h" +#include <wtf/text/CString.h> + +namespace WebCore { + +// Function to obtain the global icon database. +IconDatabase* iconDatabase() { return 0; } + +IconDatabase::IconDatabase() {} +IconDatabase::~IconDatabase() {} + +void IconDatabase::setClient(IconDatabaseClient*) {} + +bool IconDatabase::open(const String& path) { return false; } +void IconDatabase::close() {} + +void IconDatabase::removeAllIcons() {} + +Image* IconDatabase::iconForPageURL(const String&, const IntSize&) { return 0; } +void IconDatabase::readIconForPageURLFromDisk(const String&) {} +String IconDatabase::iconURLForPageURL(const String&) { return String(); } +Image* IconDatabase::defaultIcon(const IntSize&) { return 0;} + +void IconDatabase::retainIconForPageURL(const String&) {} +void IconDatabase::releaseIconForPageURL(const String&) {} + +void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String&) {} +void IconDatabase::setIconURLForPageURL(const String& iconURL, const String& pageURL) {} + +IconLoadDecision IconDatabase::loadDecisionForIconURL(const String&, DocumentLoader*) { return IconLoadNo; } +bool IconDatabase::iconDataKnownForIconURL(const String&) { return false; } + +void IconDatabase::setEnabled(bool enabled) {} +bool IconDatabase::isEnabled() const { return false; } + +void IconDatabase::setPrivateBrowsingEnabled(bool flag) {} +bool IconDatabase::isPrivateBrowsingEnabled() const { return false; } + +void IconDatabase::delayDatabaseCleanup() {} +void IconDatabase::allowDatabaseCleanup() {} +void IconDatabase::checkIntegrityBeforeOpening() {} + +// Support for WebCoreStatistics in WebKit +size_t IconDatabase::pageURLMappingCount() { return 0; } +size_t IconDatabase::retainedPageURLCount() {return 0; } +size_t IconDatabase::iconRecordCount() { return 0; } +size_t IconDatabase::iconRecordCountWithData() { return 0; } + +bool IconDatabase::isOpen() const { return false; } +String IconDatabase::databasePath() const { return String(); } +String IconDatabase::defaultDatabaseFilename() { return String(); } + +} // namespace WebCore diff --git a/Source/WebCore/loader/mac/DocumentLoaderMac.cpp b/Source/WebCore/loader/mac/DocumentLoaderMac.cpp new file mode 100644 index 0000000..8cc40d2 --- /dev/null +++ b/Source/WebCore/loader/mac/DocumentLoaderMac.cpp @@ -0,0 +1,84 @@ +/* + * 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" +#include <wtf/UnusedParam.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); +#else + UNUSED_PARAM(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); +#else + UNUSED_PARAM(pair); +#endif +} + +} // namespace diff --git a/Source/WebCore/loader/mac/LoaderNSURLExtras.h b/Source/WebCore/loader/mac/LoaderNSURLExtras.h new file mode 100644 index 0000000..ce5a490 --- /dev/null +++ b/Source/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/Source/WebCore/loader/mac/LoaderNSURLExtras.mm b/Source/WebCore/loader/mac/LoaderNSURLExtras.mm new file mode 100644 index 0000000..9a507f5 --- /dev/null +++ b/Source/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/Source/WebCore/loader/mac/ResourceLoaderMac.mm b/Source/WebCore/loader/mac/ResourceLoaderMac.mm new file mode 100644 index 0000000..3835517 --- /dev/null +++ b/Source/WebCore/loader/mac/ResourceLoaderMac.mm @@ -0,0 +1,49 @@ +/* + * 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" + +#if !USE(CFNETWORK) + +#include "FrameLoader.h" +#include "FrameLoaderClient.h" +#include "ResourceHandle.h" + +namespace WebCore { + +NSCachedURLResponse* ResourceLoader::willCacheResponse(ResourceHandle*, NSCachedURLResponse* response) +{ + if (!m_sendResourceLoadCallbacks) + return 0; + return frameLoader()->client()->willCacheResponse(documentLoader(), identifier(), response); +} + +} + +#endif // !USE(CFNETWORK) diff --git a/Source/WebCore/loader/win/DocumentLoaderWin.cpp b/Source/WebCore/loader/win/DocumentLoaderWin.cpp new file mode 100644 index 0000000..bab7de6 --- /dev/null +++ b/Source/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/Source/WebCore/loader/win/FrameLoaderWin.cpp b/Source/WebCore/loader/win/FrameLoaderWin.cpp new file mode 100644 index 0000000..66aa6ff --- /dev/null +++ b/Source/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(); +} + +} |