1 /*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2009 Google Inc. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "config.h"
32 #include "InspectorResource.h"
33
34 #if ENABLE(INSPECTOR)
35
36 #include "Cache.h"
37 #include "CachedResource.h"
38 #include "DocLoader.h"
39 #include "DocumentLoader.h"
40 #include "Frame.h"
41 #include "InspectorFrontend.h"
42 #include "ResourceRequest.h"
43 #include "ResourceResponse.h"
44 #include "TextEncoding.h"
45 #include "ScriptObject.h"
46
47 namespace WebCore {
48
InspectorResource(unsigned long identifier,DocumentLoader * loader,const KURL & requestURL)49 InspectorResource::InspectorResource(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL)
50 : m_identifier(identifier)
51 , m_loader(loader)
52 , m_frame(loader->frame())
53 , m_requestURL(requestURL)
54 , m_expectedContentLength(0)
55 , m_cached(false)
56 , m_finished(false)
57 , m_failed(false)
58 , m_length(0)
59 , m_responseStatusCode(0)
60 , m_startTime(-1.0)
61 , m_responseReceivedTime(-1.0)
62 , m_endTime(-1.0)
63 , m_loadEventTime(-1.0)
64 , m_domContentEventTime(-1.0)
65 , m_isMainResource(false)
66 {
67 }
68
~InspectorResource()69 InspectorResource::~InspectorResource()
70 {
71 }
72
appendRedirect(unsigned long identifier,const KURL & redirectURL)73 PassRefPtr<InspectorResource> InspectorResource::appendRedirect(unsigned long identifier, const KURL& redirectURL)
74 {
75 // Last redirect is always a container of all previous ones. Pass this container here.
76 RefPtr<InspectorResource> redirect = InspectorResource::create(m_identifier, m_loader.get(), redirectURL);
77 redirect->m_redirects = m_redirects;
78 redirect->m_redirects.append(this);
79 redirect->m_changes.set(RedirectsChange);
80
81 m_identifier = identifier;
82 // Re-send request info with new id.
83 m_changes.set(RequestChange);
84 m_redirects.clear();
85 return redirect;
86 }
87
createCached(unsigned long identifier,DocumentLoader * loader,const CachedResource * cachedResource)88 PassRefPtr<InspectorResource> InspectorResource::createCached(unsigned long identifier, DocumentLoader* loader, const CachedResource* cachedResource)
89 {
90 PassRefPtr<InspectorResource> resource = create(identifier, loader, KURL(ParsedURLString, cachedResource->url()));
91
92 resource->m_finished = true;
93
94 resource->updateResponse(cachedResource->response());
95
96 resource->m_length = cachedResource->encodedSize();
97 resource->m_cached = true;
98 resource->m_startTime = currentTime();
99 resource->m_responseReceivedTime = resource->m_startTime;
100 resource->m_endTime = resource->m_startTime;
101
102 resource->m_changes.setAll();
103
104 return resource;
105 }
106
updateRequest(const ResourceRequest & request)107 void InspectorResource::updateRequest(const ResourceRequest& request)
108 {
109 m_requestHeaderFields = request.httpHeaderFields();
110 m_requestMethod = request.httpMethod();
111 if (request.httpBody() && !request.httpBody()->isEmpty())
112 m_requestFormData = request.httpBody()->flattenToString();
113
114 m_changes.set(RequestChange);
115 }
116
updateResponse(const ResourceResponse & response)117 void InspectorResource::updateResponse(const ResourceResponse& response)
118 {
119 m_expectedContentLength = response.expectedContentLength();
120 m_mimeType = response.mimeType();
121 if (m_mimeType.isEmpty() && response.httpStatusCode() == 304) {
122 CachedResource* cachedResource = cache()->resourceForURL(response.url().string());
123 if (cachedResource)
124 m_mimeType = cachedResource->response().mimeType();
125 }
126 m_responseHeaderFields = response.httpHeaderFields();
127 m_responseStatusCode = response.httpStatusCode();
128 m_suggestedFilename = response.suggestedFilename();
129
130 m_changes.set(ResponseChange);
131 m_changes.set(TypeChange);
132 }
133
populateHeadersObject(ScriptObject * object,const HTTPHeaderMap & headers)134 static void populateHeadersObject(ScriptObject* object, const HTTPHeaderMap& headers)
135 {
136 HTTPHeaderMap::const_iterator end = headers.end();
137 for (HTTPHeaderMap::const_iterator it = headers.begin(); it != end; ++it) {
138 object->set(it->first.string(), it->second);
139 }
140 }
141
142
updateScriptObject(InspectorFrontend * frontend)143 void InspectorResource::updateScriptObject(InspectorFrontend* frontend)
144 {
145 if (m_changes.hasChange(NoChange))
146 return;
147
148 ScriptObject jsonObject = frontend->newScriptObject();
149 if (m_changes.hasChange(RequestChange)) {
150 jsonObject.set("url", m_requestURL.string());
151 jsonObject.set("documentURL", m_frame->document()->url().string());
152 jsonObject.set("host", m_requestURL.host());
153 jsonObject.set("path", m_requestURL.path());
154 jsonObject.set("lastPathComponent", m_requestURL.lastPathComponent());
155 ScriptObject requestHeaders = frontend->newScriptObject();
156 populateHeadersObject(&requestHeaders, m_requestHeaderFields);
157 jsonObject.set("requestHeaders", requestHeaders);
158 jsonObject.set("mainResource", m_isMainResource);
159 jsonObject.set("requestMethod", m_requestMethod);
160 jsonObject.set("requestFormData", m_requestFormData);
161 jsonObject.set("didRequestChange", true);
162 jsonObject.set("cached", m_cached);
163 }
164
165 if (m_changes.hasChange(ResponseChange)) {
166 jsonObject.set("mimeType", m_mimeType);
167 jsonObject.set("suggestedFilename", m_suggestedFilename);
168 jsonObject.set("expectedContentLength", m_expectedContentLength);
169 jsonObject.set("statusCode", m_responseStatusCode);
170 jsonObject.set("suggestedFilename", m_suggestedFilename);
171 ScriptObject responseHeaders = frontend->newScriptObject();
172 populateHeadersObject(&responseHeaders, m_responseHeaderFields);
173 jsonObject.set("responseHeaders", responseHeaders);
174 jsonObject.set("didResponseChange", true);
175 }
176
177 if (m_changes.hasChange(TypeChange)) {
178 jsonObject.set("type", static_cast<int>(type()));
179 jsonObject.set("didTypeChange", true);
180 }
181
182 if (m_changes.hasChange(LengthChange)) {
183 jsonObject.set("contentLength", m_length);
184 jsonObject.set("didLengthChange", true);
185 }
186
187 if (m_changes.hasChange(CompletionChange)) {
188 jsonObject.set("failed", m_failed);
189 jsonObject.set("finished", m_finished);
190 jsonObject.set("didCompletionChange", true);
191 }
192
193 if (m_changes.hasChange(TimingChange)) {
194 if (m_startTime > 0)
195 jsonObject.set("startTime", m_startTime);
196 if (m_responseReceivedTime > 0)
197 jsonObject.set("responseReceivedTime", m_responseReceivedTime);
198 if (m_endTime > 0)
199 jsonObject.set("endTime", m_endTime);
200 if (m_loadEventTime > 0)
201 jsonObject.set("loadEventTime", m_loadEventTime);
202 if (m_domContentEventTime > 0)
203 jsonObject.set("domContentEventTime", m_domContentEventTime);
204 jsonObject.set("didTimingChange", true);
205 }
206
207 if (m_changes.hasChange(RedirectsChange)) {
208 for (size_t i = 0; i < m_redirects.size(); ++i)
209 m_redirects[i]->updateScriptObject(frontend);
210 }
211
212 if (frontend->updateResource(m_identifier, jsonObject))
213 m_changes.clearAll();
214 }
215
releaseScriptObject(InspectorFrontend * frontend,bool callRemoveResource)216 void InspectorResource::releaseScriptObject(InspectorFrontend* frontend, bool callRemoveResource)
217 {
218 m_changes.setAll();
219
220 for (size_t i = 0; i < m_redirects.size(); ++i)
221 m_redirects[i]->releaseScriptObject(frontend, callRemoveResource);
222
223 if (!callRemoveResource)
224 return;
225
226 frontend->removeResource(m_identifier);
227 }
228
cachedResource() const229 CachedResource* InspectorResource::cachedResource() const
230 {
231 // Try hard to find a corresponding CachedResource. During preloading, DocLoader may not have the resource in document resources set yet,
232 // but Inspector will already try to fetch data that is only available via CachedResource (and it won't update once the resource is added,
233 // because m_changes will not have the appropriate bits set).
234 const String& url = m_requestURL.string();
235 CachedResource* cachedResource = m_frame->document()->docLoader()->cachedResource(url);
236 if (!cachedResource)
237 cachedResource = cache()->resourceForURL(url);
238 return cachedResource;
239 }
240
cachedResourceType() const241 InspectorResource::Type InspectorResource::cachedResourceType() const
242 {
243 CachedResource* cachedResource = this->cachedResource();
244
245 if (!cachedResource)
246 return Other;
247
248 switch (cachedResource->type()) {
249 case CachedResource::ImageResource:
250 return Image;
251 case CachedResource::FontResource:
252 return Font;
253 case CachedResource::CSSStyleSheet:
254 #if ENABLE(XSLT)
255 case CachedResource::XSLStyleSheet:
256 #endif
257 return Stylesheet;
258 case CachedResource::Script:
259 return Script;
260 default:
261 return Other;
262 }
263 }
264
type() const265 InspectorResource::Type InspectorResource::type() const
266 {
267 if (!m_xmlHttpResponseText.isNull())
268 return XHR;
269
270 if (m_requestURL == m_loader->requestURL()) {
271 InspectorResource::Type resourceType = cachedResourceType();
272 if (resourceType == Other)
273 return Doc;
274
275 return resourceType;
276 }
277
278 if (m_loader->frameLoader() && m_requestURL == m_loader->frameLoader()->iconURL())
279 return Image;
280
281 return cachedResourceType();
282 }
283
setXMLHttpResponseText(const ScriptString & data)284 void InspectorResource::setXMLHttpResponseText(const ScriptString& data)
285 {
286 m_xmlHttpResponseText = data;
287 m_changes.set(TypeChange);
288 }
289
sourceString() const290 String InspectorResource::sourceString() const
291 {
292 if (!m_xmlHttpResponseText.isNull())
293 return String(m_xmlHttpResponseText);
294
295 String textEncodingName;
296 RefPtr<SharedBuffer> buffer = resourceData(&textEncodingName);
297 if (!buffer)
298 return String();
299
300 TextEncoding encoding(textEncodingName);
301 if (!encoding.isValid())
302 encoding = WindowsLatin1Encoding();
303 return encoding.decode(buffer->data(), buffer->size());
304 }
305
resourceData(String * textEncodingName) const306 PassRefPtr<SharedBuffer> InspectorResource::resourceData(String* textEncodingName) const
307 {
308 if (m_requestURL == m_loader->requestURL()) {
309 *textEncodingName = m_frame->document()->inputEncoding();
310 return m_loader->mainResourceData();
311 }
312
313 CachedResource* cachedResource = this->cachedResource();
314 if (!cachedResource)
315 return 0;
316
317 if (cachedResource->isPurgeable()) {
318 // If the resource is purgeable then make it unpurgeable to get
319 // get its data. This might fail, in which case we return an
320 // empty String.
321 // FIXME: should we do something else in the case of a purged
322 // resource that informs the user why there is no data in the
323 // inspector?
324 if (!cachedResource->makePurgeable(false))
325 return 0;
326 }
327
328 *textEncodingName = cachedResource->encoding();
329 return cachedResource->data();
330 }
331
startTiming()332 void InspectorResource::startTiming()
333 {
334 m_startTime = currentTime();
335 m_changes.set(TimingChange);
336 }
337
markResponseReceivedTime()338 void InspectorResource::markResponseReceivedTime()
339 {
340 m_responseReceivedTime = currentTime();
341 m_changes.set(TimingChange);
342 }
343
endTiming()344 void InspectorResource::endTiming()
345 {
346 m_endTime = currentTime();
347 m_finished = true;
348 m_changes.set(TimingChange);
349 m_changes.set(CompletionChange);
350 }
351
markDOMContentEventTime()352 void InspectorResource::markDOMContentEventTime()
353 {
354 m_domContentEventTime = currentTime();
355 m_changes.set(TimingChange);
356 }
357
markLoadEventTime()358 void InspectorResource::markLoadEventTime()
359 {
360 m_loadEventTime = currentTime();
361 m_changes.set(TimingChange);
362 }
363
markFailed()364 void InspectorResource::markFailed()
365 {
366 m_failed = true;
367 m_changes.set(CompletionChange);
368 }
369
addLength(int lengthReceived)370 void InspectorResource::addLength(int lengthReceived)
371 {
372 m_length += lengthReceived;
373 m_changes.set(LengthChange);
374
375 // Update load time, otherwise the resource will
376 // have start time == end time and 0 load duration
377 // until its loading is completed.
378 m_endTime = currentTime();
379 m_changes.set(TimingChange);
380 }
381
382 } // namespace WebCore
383
384 #endif // ENABLE(INSPECTOR)
385