REGRESSION (r243682): Quick Look previews loaded from the memory cache render with the wrong content type
https://bugs.webkit.org/show_bug.cgi?id=202935
<rdar://problem/54318133>

Reviewed by Tim Horton.

Source/WebCore:

When loading a Quick Look preview after deciding content policy, PreviewLoader would update
DocumentLoader with the preview response (the response that contains the preview's
Content-Type). It would not update the CachedResource representing the preview main
resource, however, which caches the underlying ResourceResponse in m_response.

When loading from the memory cache, it's the CachedResource's response that's used to
synthesize DocumentLoader::responseReceived. When loading Quick Look previews *before*
deciding content policy, this response would be the preview response, but as described
above, when loading after deciding content policy it's the underlying response.

This patch updates a Quick Look preview's CachedResource with the preview response along
with updating DocumentLoader so that there is not a mismatch between the resource's content
type and its underlying data.

Added a new API test.

* loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::previewResponseReceived):
* loader/DocumentLoader.h:
* loader/ResourceLoader.h:
(WebCore::ResourceLoader::didReceivePreviewResponse):
* loader/SubresourceLoader.cpp:
(WebCore::SubresourceLoader::didReceivePreviewResponse):
* loader/SubresourceLoader.h:
* loader/cache/CachedRawResource.cpp:
(WebCore::CachedRawResource::previewResponseReceived):
* loader/cache/CachedRawResource.h:
* loader/cache/CachedRawResourceClient.h:
(WebCore::CachedRawResourceClient::previewResponseReceived):
* loader/cache/CachedResource.cpp:
(WebCore::CachedResource::previewResponseReceived):
* loader/cache/CachedResource.h:
* loader/ios/PreviewLoader.mm:
(-[WebPreviewLoader _loadPreviewIfNeeded]):

Tools:

* TestWebKitAPI/Tests/WebKitCocoa/QuickLook.mm:
(TEST):
* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[WKWebView synchronouslyGoBack]):
(-[WKWebView synchronouslyGoForward]):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251105 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 0e97f72..553cc0b 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,46 @@
+2019-10-14  Andy Estes  <aestes@apple.com>
+
+        REGRESSION (r243682): Quick Look previews loaded from the memory cache render with the wrong content type
+        https://bugs.webkit.org/show_bug.cgi?id=202935
+        <rdar://problem/54318133>
+
+        Reviewed by Tim Horton.
+
+        When loading a Quick Look preview after deciding content policy, PreviewLoader would update
+        DocumentLoader with the preview response (the response that contains the preview's
+        Content-Type). It would not update the CachedResource representing the preview main
+        resource, however, which caches the underlying ResourceResponse in m_response.
+
+        When loading from the memory cache, it's the CachedResource's response that's used to
+        synthesize DocumentLoader::responseReceived. When loading Quick Look previews *before*
+        deciding content policy, this response would be the preview response, but as described
+        above, when loading after deciding content policy it's the underlying response.
+
+        This patch updates a Quick Look preview's CachedResource with the preview response along
+        with updating DocumentLoader so that there is not a mismatch between the resource's content
+        type and its underlying data.
+
+        Added a new API test.
+
+        * loader/DocumentLoader.cpp:
+        (WebCore::DocumentLoader::previewResponseReceived):
+        * loader/DocumentLoader.h:
+        * loader/ResourceLoader.h:
+        (WebCore::ResourceLoader::didReceivePreviewResponse):
+        * loader/SubresourceLoader.cpp:
+        (WebCore::SubresourceLoader::didReceivePreviewResponse):
+        * loader/SubresourceLoader.h:
+        * loader/cache/CachedRawResource.cpp:
+        (WebCore::CachedRawResource::previewResponseReceived):
+        * loader/cache/CachedRawResource.h:
+        * loader/cache/CachedRawResourceClient.h:
+        (WebCore::CachedRawResourceClient::previewResponseReceived):
+        * loader/cache/CachedResource.cpp:
+        (WebCore::CachedResource::previewResponseReceived):
+        * loader/cache/CachedResource.h:
+        * loader/ios/PreviewLoader.mm:
+        (-[WebPreviewLoader _loadPreviewIfNeeded]):
+
 2019-10-14  Youenn Fablet  <youenn@apple.com>
 
         A response body promise should be rejected in case of a failure happening after the HTTP response
diff --git a/Source/WebCore/loader/DocumentLoader.cpp b/Source/WebCore/loader/DocumentLoader.cpp
index 6a63023..fba266a 100644
--- a/Source/WebCore/loader/DocumentLoader.cpp
+++ b/Source/WebCore/loader/DocumentLoader.cpp
@@ -2135,6 +2135,12 @@
 
 #if USE(QUICK_LOOK)
 
+void DocumentLoader::previewResponseReceived(CachedResource& resource, const ResourceResponse& response)
+{
+    ASSERT_UNUSED(resource, m_mainResource == &resource);
+    m_response = response;
+}
+
 void DocumentLoader::setPreviewConverter(std::unique_ptr<PreviewConverter>&& previewConverter)
 {
     m_previewConverter = WTFMove(previewConverter);
diff --git a/Source/WebCore/loader/DocumentLoader.h b/Source/WebCore/loader/DocumentLoader.h
index a85a076..dffed40 100644
--- a/Source/WebCore/loader/DocumentLoader.h
+++ b/Source/WebCore/loader/DocumentLoader.h
@@ -432,6 +432,9 @@
     WEBCORE_EXPORT void responseReceived(CachedResource&, const ResourceResponse&, CompletionHandler<void()>&&) override;
     WEBCORE_EXPORT void dataReceived(CachedResource&, const char* data, int length) override;
     WEBCORE_EXPORT void notifyFinished(CachedResource&) override;
+#if USE(QUICK_LOOK)
+    WEBCORE_EXPORT void previewResponseReceived(CachedResource&, const ResourceResponse&) override;
+#endif
 
     void responseReceived(const ResourceResponse&, CompletionHandler<void()>&&);
     void dataReceived(const char* data, int length);
diff --git a/Source/WebCore/loader/ResourceLoader.h b/Source/WebCore/loader/ResourceLoader.h
index 5c5c8df..72f4c7a 100644
--- a/Source/WebCore/loader/ResourceLoader.h
+++ b/Source/WebCore/loader/ResourceLoader.h
@@ -117,6 +117,7 @@
 
 #if USE(QUICK_LOOK)
     bool isQuickLookResource() const;
+    virtual void didReceivePreviewResponse(const ResourceResponse&) { };
 #endif
 
     const URL& url() const { return m_request.url(); }
diff --git a/Source/WebCore/loader/SubresourceLoader.cpp b/Source/WebCore/loader/SubresourceLoader.cpp
index a5be875..8e30b75 100644
--- a/Source/WebCore/loader/SubresourceLoader.cpp
+++ b/Source/WebCore/loader/SubresourceLoader.cpp
@@ -314,6 +314,15 @@
     return PreviewConverter::supportsMIMEType(response.mimeType());
 }
 
+void SubresourceLoader::didReceivePreviewResponse(const ResourceResponse& response)
+{
+    ASSERT(m_state == Initialized);
+    ASSERT(!response.isNull());
+    ASSERT(m_resource);
+    m_resource->previewResponseReceived(response);
+    ResourceLoader::didReceivePreviewResponse(response);
+}
+
 #endif
 
 void SubresourceLoader::didReceiveResponse(const ResourceResponse& response, CompletionHandler<void()>&& policyCompletionHandler)
diff --git a/Source/WebCore/loader/SubresourceLoader.h b/Source/WebCore/loader/SubresourceLoader.h
index dec8d0b..9ecb782 100644
--- a/Source/WebCore/loader/SubresourceLoader.h
+++ b/Source/WebCore/loader/SubresourceLoader.h
@@ -100,6 +100,7 @@
 
 #if USE(QUICK_LOOK)
     bool shouldCreatePreviewLoaderForResponse(const ResourceResponse&) const;
+    void didReceivePreviewResponse(const ResourceResponse&) override;
 #endif
 
     enum SubresourceLoaderState {
diff --git a/Source/WebCore/loader/cache/CachedRawResource.cpp b/Source/WebCore/loader/cache/CachedRawResource.cpp
index f9a75f3..e7dc633 100644
--- a/Source/WebCore/loader/cache/CachedRawResource.cpp
+++ b/Source/WebCore/loader/cache/CachedRawResource.cpp
@@ -341,4 +341,16 @@
         m_loader->clearResourceData();
 }
 
+#if USE(QUICK_LOOK)
+void CachedRawResource::previewResponseReceived(const ResourceResponse& response)
+{
+    CachedResourceHandle<CachedRawResource> protectedThis(this);
+    CachedResource::previewResponseReceived(response);
+    CachedResourceClientWalker<CachedRawResourceClient> w(m_clients);
+    while (CachedRawResourceClient* c = w.next())
+        c->previewResponseReceived(*this, m_response);
+}
+
+#endif
+
 } // namespace WebCore
diff --git a/Source/WebCore/loader/cache/CachedRawResource.h b/Source/WebCore/loader/cache/CachedRawResource.h
index 6376136..27cddd3 100644
--- a/Source/WebCore/loader/cache/CachedRawResource.h
+++ b/Source/WebCore/loader/cache/CachedRawResource.h
@@ -68,6 +68,10 @@
 
     Optional<SharedBufferDataView> calculateIncrementalDataChunk(const SharedBuffer*) const;
     void notifyClientsDataWasReceived(const char* data, unsigned length);
+    
+#if USE(QUICK_LOOK)
+    void previewResponseReceived(const ResourceResponse&) final;
+#endif
 
     unsigned long m_identifier;
     bool m_allowEncodedDataReplacement;
diff --git a/Source/WebCore/loader/cache/CachedRawResourceClient.h b/Source/WebCore/loader/cache/CachedRawResourceClient.h
index 547c689..c80315e 100644
--- a/Source/WebCore/loader/cache/CachedRawResourceClient.h
+++ b/Source/WebCore/loader/cache/CachedRawResourceClient.h
@@ -49,6 +49,10 @@
     virtual void dataReceived(CachedResource&, const char* /* data */, int /* length */) { }
     virtual void redirectReceived(CachedResource&, ResourceRequest&& request, const ResourceResponse&, CompletionHandler<void(ResourceRequest&&)>&& completionHandler) { completionHandler(WTFMove(request)); }
     virtual void finishedTimingForWorkerLoad(CachedResource&, const ResourceTiming&) { }
+
+#if USE(QUICK_LOOK)
+    virtual void previewResponseReceived(CachedResource&, const ResourceResponse&) { };
+#endif
 };
 
 }
diff --git a/Source/WebCore/loader/cache/CachedResource.cpp b/Source/WebCore/loader/cache/CachedResource.cpp
index 8722455..4b16f59 100644
--- a/Source/WebCore/loader/cache/CachedResource.cpp
+++ b/Source/WebCore/loader/cache/CachedResource.cpp
@@ -919,4 +919,14 @@
 
 #endif
 
+#if USE(QUICK_LOOK)
+
+void CachedResource::previewResponseReceived(const ResourceResponse& response)
+{
+    ASSERT(response.url().protocolIs(QLPreviewProtocol));
+    CachedResource::responseReceived(response);
+}
+
+#endif
+
 }
diff --git a/Source/WebCore/loader/cache/CachedResource.h b/Source/WebCore/loader/cache/CachedResource.h
index fc30b0f..7f8cb1f 100644
--- a/Source/WebCore/loader/cache/CachedResource.h
+++ b/Source/WebCore/loader/cache/CachedResource.h
@@ -278,6 +278,10 @@
     void setOriginalRequest(std::unique_ptr<ResourceRequest>&& originalRequest) { m_originalRequest = WTFMove(originalRequest); }
     const std::unique_ptr<ResourceRequest>& originalRequest() const { return m_originalRequest; }
 
+#if USE(QUICK_LOOK)
+    virtual void previewResponseReceived(const ResourceResponse&);
+#endif
+
 protected:
     // CachedResource constructor that may be used when the CachedResource can already be filled with response data.
     CachedResource(const URL&, Type, const PAL::SessionID&, const CookieJar*);
diff --git a/Source/WebCore/loader/ios/PreviewLoader.mm b/Source/WebCore/loader/ios/PreviewLoader.mm
index c5ca5c2..2cd3559 100644
--- a/Source/WebCore/loader/ios/PreviewLoader.mm
+++ b/Source/WebCore/loader/ios/PreviewLoader.mm
@@ -143,7 +143,7 @@
 
     if (_shouldDecidePolicyBeforeLoading) {
         _hasProcessedResponse = YES;
-        _resourceLoader->documentLoader()->setResponse(response);
+        _resourceLoader->didReceivePreviewResponse(response);
         return;
     }
 
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 77e437f..39fc344 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,18 @@
+2019-10-14  Andy Estes  <aestes@apple.com>
+
+        REGRESSION (r243682): Quick Look previews loaded from the memory cache render with the wrong content type
+        https://bugs.webkit.org/show_bug.cgi?id=202935
+        <rdar://problem/54318133>
+
+        Reviewed by Tim Horton.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/QuickLook.mm:
+        (TEST):
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[WKWebView synchronouslyGoBack]):
+        (-[WKWebView synchronouslyGoForward]):
+
 2019-10-14  Alex Christensen  <achristensen@webkit.org>
 
         REGRESSION: [iOS 13?] TestWebKitAPI.SharedBufferTest.tryCreateArrayBufferLargeSegments is failing
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/QuickLook.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/QuickLook.mm
index 3db52f3..a54ea78 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/QuickLook.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/QuickLook.mm
@@ -29,6 +29,8 @@
 
 #import "PlatformUtilities.h"
 #import "Test.h"
+#import "TestProtocol.h"
+#import "TestWKWebView.h"
 #import <CoreServices/CoreServices.h>
 #import <WebCore/WebCoreThread.h>
 #import <WebKit/WKNavigationDelegatePrivate.h>
@@ -478,4 +480,30 @@
     EXPECT_NULL(mainFrame.dataSource._quickLookContent);
 }
 
+TEST(QuickLook, LoadFromMemoryCache)
+{
+    [TestProtocol registerWithScheme:@"http"];
+    TestProtocol.additionalResponseHeaders = @{ @"Cache-Control" : @"max-age=3600" };
+
+    auto webView = adoptNS([[TestWKWebView alloc] init]);
+    [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://bundle-file/simple.html"]]];
+
+    NSString * const contentTypeJavaScript = @"document.contentType";
+    NSString * const simpleMIMEType = @"text/html";
+    EXPECT_WK_STREQ(simpleMIMEType, [webView stringByEvaluatingJavaScript:contentTypeJavaScript]);
+
+    NSURL * const pagesDocumentHTTPURL = [NSURL URLWithString:@"http://bundle-file/pages.pages"];
+    [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:pagesDocumentHTTPURL]];
+    EXPECT_WK_STREQ(pagesDocumentPreviewMIMEType, [webView stringByEvaluatingJavaScript:contentTypeJavaScript]);
+
+    [webView synchronouslyGoBack];
+    EXPECT_WK_STREQ(simpleMIMEType, [webView stringByEvaluatingJavaScript:contentTypeJavaScript]);
+
+    [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:pagesDocumentHTTPURL]];
+    EXPECT_WK_STREQ(pagesDocumentPreviewMIMEType, [webView stringByEvaluatingJavaScript:contentTypeJavaScript]);
+
+    TestProtocol.additionalResponseHeaders = nil;
+    [TestProtocol unregister];
+}
+
 #endif // PLATFORM(IOS_FAMILY)
diff --git a/Tools/TestWebKitAPI/cocoa/TestWKWebView.h b/Tools/TestWebKitAPI/cocoa/TestWKWebView.h
index 24a5acd..ab25039 100644
--- a/Tools/TestWebKitAPI/cocoa/TestWKWebView.h
+++ b/Tools/TestWebKitAPI/cocoa/TestWKWebView.h
@@ -50,6 +50,8 @@
 @interface WKWebView (TestWebKitAPI)
 @property (nonatomic, readonly) NSArray<NSString *> *tagsInBody;
 - (void)loadTestPageNamed:(NSString *)pageName;
+- (void)synchronouslyGoBack;
+- (void)synchronouslyGoForward;
 - (void)synchronouslyLoadHTMLString:(NSString *)html;
 - (void)synchronouslyLoadHTMLString:(NSString *)html baseURL:(NSURL *)url;
 - (void)synchronouslyLoadRequest:(NSURLRequest *)request;
diff --git a/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm b/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
index 651b5f2..fa3e9c0 100644
--- a/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
+++ b/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
@@ -70,6 +70,18 @@
     [self loadRequest:request];
 }
 
+- (void)synchronouslyGoBack
+{
+    [self goBack];
+    [self _test_waitForDidFinishNavigation];
+}
+
+- (void)synchronouslyGoForward
+{
+    [self goForward];
+    [self _test_waitForDidFinishNavigation];
+}
+
 - (void)synchronouslyLoadRequest:(NSURLRequest *)request
 {
     [self loadRequest:request];