cf51d9a421a2bf34330e06d60093d175bf8bf070
[WebKit-https.git] / WebCore / loader / loader.cpp
1 /*
2     Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3     Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4     Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5     Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
6     Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
7
8     This library is free software; you can redistribute it and/or
9     modify it under the terms of the GNU Library General Public
10     License as published by the Free Software Foundation; either
11     version 2 of the License, or (at your option) any later version.
12
13     This library is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16     Library General Public License for more details.
17
18     You should have received a copy of the GNU Library General Public License
19     along with this library; see the file COPYING.LIB.  If not, write to
20     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21     Boston, MA 02110-1301, USA.
22 */
23
24 #include "config.h"
25 #include "loader.h"
26
27 #include "MemoryCache.h"
28 #include "CachedImage.h"
29 #include "CachedResource.h"
30 #include "CachedResourceLoader.h"
31 #include "Frame.h"
32 #include "FrameLoader.h"
33 #include "Logging.h"
34 #include "ResourceHandle.h"
35 #include "ResourceLoadScheduler.h"
36 #include "ResourceRequest.h"
37 #include "ResourceResponse.h"
38 #include "SharedBuffer.h"
39 #include <wtf/Assertions.h>
40 #include <wtf/Vector.h>
41 #include <wtf/text/CString.h>
42
43 namespace WebCore {
44     
45 static ResourceRequest::TargetType cachedResourceTypeToTargetType(CachedResource::Type type)
46 {
47     switch (type) {
48     case CachedResource::CSSStyleSheet:
49 #if ENABLE(XSLT)
50     case CachedResource::XSLStyleSheet:
51 #endif
52         return ResourceRequest::TargetIsStyleSheet;
53     case CachedResource::Script: 
54         return ResourceRequest::TargetIsScript;
55     case CachedResource::FontResource:
56         return ResourceRequest::TargetIsFontResource;
57     case CachedResource::ImageResource:
58         return ResourceRequest::TargetIsImage;
59 #if ENABLE(LINK_PREFETCH)
60     case CachedResource::LinkPrefetch:
61         return ResourceRequest::TargetIsPrefetch;
62 #endif
63     }
64     ASSERT_NOT_REACHED();
65     return ResourceRequest::TargetIsSubresource;
66 }
67
68 static ResourceLoadScheduler::Priority determinePriority(const CachedResource* resource)
69 {
70     switch (resource->type()) {
71         case CachedResource::CSSStyleSheet:
72 #if ENABLE(XSLT)
73         case CachedResource::XSLStyleSheet:
74 #endif
75             return ResourceLoadScheduler::High;
76         case CachedResource::Script:
77         case CachedResource::FontResource:
78             return ResourceLoadScheduler::Medium;
79         case CachedResource::ImageResource:
80             return ResourceLoadScheduler::Low;
81 #if ENABLE(LINK_PREFETCH)
82         case CachedResource::LinkPrefetch:
83             return ResourceLoadScheduler::VeryLow;
84 #endif
85     }
86     ASSERT_NOT_REACHED();
87     return ResourceLoadScheduler::Low;
88 }
89
90 Loader::Loader(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental)
91     : m_cachedResourceLoader(cachedResourceLoader)
92     , m_resource(resource)
93     , m_incremental(incremental)
94     , m_multipart(false)
95     , m_finishing(false)
96 {
97     m_resource->setRequest(this);
98 }
99
100 Loader::~Loader()
101 {
102     m_resource->setRequest(0);
103 }
104
105 PassRefPtr<Loader> Loader::load(CachedResourceLoader* cachedResourceLoader, CachedResource* resource, bool incremental, SecurityCheckPolicy securityCheck, bool sendResourceLoadCallbacks)
106 {
107     RefPtr<Loader> request = adoptRef(new Loader(cachedResourceLoader, resource, incremental));
108
109     ResourceRequest resourceRequest(resource->url());
110     resourceRequest.setTargetType(cachedResourceTypeToTargetType(resource->type()));
111
112     if (!resource->accept().isEmpty())
113         resourceRequest.setHTTPAccept(resource->accept());
114
115     if (resource->isCacheValidator()) {
116         CachedResource* resourceToRevalidate = resource->resourceToRevalidate();
117         ASSERT(resourceToRevalidate->canUseCacheValidator());
118         ASSERT(resourceToRevalidate->isLoaded());
119         const String& lastModified = resourceToRevalidate->response().httpHeaderField("Last-Modified");
120         const String& eTag = resourceToRevalidate->response().httpHeaderField("ETag");
121         if (!lastModified.isEmpty() || !eTag.isEmpty()) {
122             ASSERT(cachedResourceLoader->cachePolicy() != CachePolicyReload);
123             if (cachedResourceLoader->cachePolicy() == CachePolicyRevalidate)
124                 resourceRequest.setHTTPHeaderField("Cache-Control", "max-age=0");
125             if (!lastModified.isEmpty())
126                 resourceRequest.setHTTPHeaderField("If-Modified-Since", lastModified);
127             if (!eTag.isEmpty())
128                 resourceRequest.setHTTPHeaderField("If-None-Match", eTag);
129         }
130     }
131     
132 #if ENABLE(LINK_PREFETCH)
133     if (resource->type() == CachedResource::LinkPrefetch)
134         resourceRequest.setHTTPHeaderField("X-Purpose", "prefetch");
135 #endif
136
137     RefPtr<SubresourceLoader> loader = resourceLoadScheduler()->scheduleSubresourceLoad(cachedResourceLoader->document()->frame(),
138         request.get(), resourceRequest, determinePriority(resource), securityCheck, sendResourceLoadCallbacks);
139     if (!loader || loader->reachedTerminalState()) {
140         // FIXME: What if resources in other frames were waiting for this revalidation?
141         LOG(ResourceLoading, "Cannot start loading '%s'", resource->url().latin1().data());
142         cachedResourceLoader->decrementRequestCount(resource);
143         cachedResourceLoader->loadFinishing();
144         if (resource->resourceToRevalidate()) 
145             cache()->revalidationFailed(resource); 
146         resource->error(CachedResource::LoadError);
147         cachedResourceLoader->loadDone(0);
148         return 0;
149     }
150     request->m_loader = loader;
151     return request.release();
152 }
153
154 void Loader::willSendRequest(SubresourceLoader*, ResourceRequest&, const ResourceResponse&)
155 {
156     m_resource->setRequestedFromNetworkingLayer();
157 }
158
159 void Loader::didFinishLoading(SubresourceLoader* loader)
160 {
161     if (m_finishing)
162         return;
163
164     ASSERT(loader == m_loader.get());
165     ASSERT(!m_resource->resourceToRevalidate());
166     LOG(ResourceLoading, "Received '%s'.", m_resource->url().latin1().data());
167
168     // Prevent the document from being destroyed before we are done with
169     // the cachedResourceLoader that it will delete when the document gets deleted.
170     RefPtr<Document> protector(m_cachedResourceLoader->document());
171     if (!m_multipart)
172         m_cachedResourceLoader->decrementRequestCount(m_resource);
173     m_finishing = true;
174
175     // If we got a 4xx response, we're pretending to have received a network
176     // error, so we can't send the successful data() and finish() callbacks.
177     if (!m_resource->errorOccurred()) {
178         m_cachedResourceLoader->loadFinishing();
179         m_resource->data(loader->resourceData(), true);
180         if (!m_resource->errorOccurred())
181             m_resource->finish();
182     }
183     m_cachedResourceLoader->loadDone(this);
184 }
185
186 void Loader::didFail(SubresourceLoader*, const ResourceError&)
187 {
188     if (!m_loader)
189         return;
190     didFail();
191 }
192
193 void Loader::didFail(bool cancelled)
194 {
195     if (m_finishing)
196         return;
197
198     LOG(ResourceLoading, "Failed to load '%s' (cancelled=%d).\n", m_resource->url().latin1().data(), cancelled);
199
200     // Prevent the document from being destroyed before we are done with
201     // the cachedResourceLoader that it will delete when the document gets deleted.
202     RefPtr<Document> protector(m_cachedResourceLoader->document());
203     if (!m_multipart)
204         m_cachedResourceLoader->decrementRequestCount(m_resource);
205     m_finishing = true;
206     m_loader->clearClient();
207
208     if (m_resource->resourceToRevalidate())
209         cache()->revalidationFailed(m_resource);
210
211     if (!cancelled) {
212         m_cachedResourceLoader->loadFinishing();
213         m_resource->error(CachedResource::LoadError);
214     }
215
216     if (cancelled || !m_resource->isPreloaded())
217         cache()->remove(m_resource);
218     
219     m_cachedResourceLoader->loadDone(this);
220 }
221
222 void Loader::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response)
223 {
224     ASSERT(loader == m_loader.get());
225     if (m_resource->isCacheValidator()) {
226         if (response.httpStatusCode() == 304) {
227             // 304 Not modified / Use local copy
228             loader->clearClient();
229             RefPtr<Document> protector(m_cachedResourceLoader->document());
230             m_cachedResourceLoader->decrementRequestCount(m_resource);
231             m_finishing = true;
232
233             // Existing resource is ok, just use it updating the expiration time.
234             cache()->revalidationSucceeded(m_resource, response);
235             
236             if (m_cachedResourceLoader->frame())
237                 m_cachedResourceLoader->frame()->loader()->checkCompleted();
238
239             m_cachedResourceLoader->loadDone(this);
240             return;
241         } 
242         // Did not get 304 response, continue as a regular resource load.
243         cache()->revalidationFailed(m_resource);
244     }
245
246     m_resource->setResponse(response);
247
248     String encoding = response.textEncodingName();
249     if (!encoding.isNull())
250         m_resource->setEncoding(encoding);
251     
252     if (m_multipart) {
253         ASSERT(m_resource->isImage());
254         static_cast<CachedImage*>(m_resource)->clear();
255         if (m_cachedResourceLoader->frame())
256             m_cachedResourceLoader->frame()->loader()->checkCompleted();
257     } else if (response.isMultipart()) {
258         m_multipart = true;
259         
260         // We don't count multiParts in a CachedResourceLoader's request count
261         m_cachedResourceLoader->decrementRequestCount(m_resource);
262
263         // If we get a multipart response, we must have a handle
264         ASSERT(loader->handle());
265         if (!m_resource->isImage())
266             loader->handle()->cancel();
267     }
268 }
269
270 void Loader::didReceiveData(SubresourceLoader* loader, const char* data, int size)
271 {
272     ASSERT(loader == m_loader.get());
273     ASSERT(!m_resource->isCacheValidator());
274     
275     if (m_resource->errorOccurred())
276         return;
277
278     if (m_resource->response().httpStatusCode() >= 400) {
279         if (!m_resource->shouldIgnoreHTTPStatusCodeErrors())
280             m_resource->error(CachedResource::LoadError);
281         return;
282     }
283
284     // Set the data.
285     if (m_multipart) {
286         // The loader delivers the data in a multipart section all at once, send eof.
287         // The resource data will change as the next part is loaded, so we need to make a copy.
288         RefPtr<SharedBuffer> copiedData = SharedBuffer::create(data, size);
289         m_resource->data(copiedData.release(), true);
290     } else if (m_incremental)
291         m_resource->data(loader->resourceData(), false);
292 }
293
294 void Loader::didReceiveCachedMetadata(SubresourceLoader*, const char* data, int size)
295 {
296     ASSERT(!m_resource->isCacheValidator());
297     m_resource->setSerializedCachedMetadata(data, size);
298 }
299
300 } //namespace WebCore