1 /* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Matt Lilek <webkit (at) 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 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 69 InspectorResource::~InspectorResource() 70 { 71 } 72 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 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 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 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 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 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 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 229 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 241 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 265 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 284 void InspectorResource::setXMLHttpResponseText(const ScriptString& data) 285 { 286 m_xmlHttpResponseText = data; 287 m_changes.set(TypeChange); 288 } 289 290 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 306 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 332 void InspectorResource::startTiming() 333 { 334 m_startTime = currentTime(); 335 m_changes.set(TimingChange); 336 } 337 338 void InspectorResource::markResponseReceivedTime() 339 { 340 m_responseReceivedTime = currentTime(); 341 m_changes.set(TimingChange); 342 } 343 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 352 void InspectorResource::markDOMContentEventTime() 353 { 354 m_domContentEventTime = currentTime(); 355 m_changes.set(TimingChange); 356 } 357 358 void InspectorResource::markLoadEventTime() 359 { 360 m_loadEventTime = currentTime(); 361 m_changes.set(TimingChange); 362 } 363 364 void InspectorResource::markFailed() 365 { 366 m_failed = true; 367 m_changes.set(CompletionChange); 368 } 369 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