Move SystemPreview code from WebKitAdditions to WebKit
[WebKit-https.git] / Source / WebKit / UIProcess / Cocoa / SystemPreviewControllerCocoa.mm
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "SystemPreviewController.h"
28
29 #if USE(SYSTEM_PREVIEW)
30
31 #import "APIUIClient.h"
32 #import "WebPageProxy.h"
33 #import <MobileCoreServices/MobileCoreServices.h>
34 #import <QuickLook/QuickLook.h>
35 #import <UIKit/UIViewController.h>
36 #import <WebCore/MIMETypeRegistry.h>
37 #import <pal/spi/ios/QuickLookSPI.h>
38 #import <wtf/SoftLinking.h>
39 #import <wtf/WeakObjCPtr.h>
40
41 SOFT_LINK_FRAMEWORK(QuickLook)
42 SOFT_LINK_CLASS(QuickLook, QLPreviewController);
43 SOFT_LINK_CLASS(QuickLook, QLItem);
44
45 // FIXME: At the moment we only have one supported UTI, but
46 // if we start supporting more types, then we'll need a table.
47 static String getUTIForMIMEType(const String& mimeType)
48 {
49     static const NeverDestroyed<String> uti = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, CFSTR("usdz"), nil)).get();
50
51     if (!WebCore::MIMETypeRegistry::isSystemPreviewMIMEType(mimeType))
52         return emptyString();
53
54     return uti;
55 }
56
57 @interface _WKPreviewControllerDataSource : NSObject <QLPreviewControllerDataSource> {
58     RetainPtr<NSItemProvider> _itemProvider;
59     RetainPtr<QLItem> _item;
60 };
61
62 @property (strong) NSItemProviderCompletionHandler completionHandler;
63 @property (copy) NSString *mimeType;
64
65 @end
66
67 @implementation _WKPreviewControllerDataSource
68
69 - (instancetype)initWithMIMEType:(NSString*)mimeType
70 {
71     if (!(self = [super init]))
72         return nil;
73
74     _mimeType = [mimeType copy];
75
76     return self;
77 }
78
79 - (void)dealloc
80 {
81     [_completionHandler release];
82     [_mimeType release];
83     [super dealloc];
84 }
85
86 - (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller
87 {
88     return 1;
89 }
90
91 - (id<QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index
92 {
93     if (_item)
94         return _item.get();
95
96     _itemProvider = adoptNS([[NSItemProvider alloc] init]);
97     // FIXME: We are launching the preview controller before getting a response from the resource, which
98     // means we don't actually know the real MIME type yet. Assume it is one of those that we registered.
99     NSString *contentType = getUTIForMIMEType(*WebCore::MIMETypeRegistry::getSystemPreviewMIMETypes().begin());
100
101     _item = adoptNS([allocQLItemInstance() initWithPreviewItemProvider:_itemProvider.get() contentType:contentType previewTitle:@"Preview" fileSize:@(0)]);
102     [_item setUseLoadingTimeout:NO];
103
104     WeakObjCPtr<_WKPreviewControllerDataSource> weakSelf { self };
105     [_itemProvider registerItemForTypeIdentifier:contentType loadHandler:[weakSelf = WTFMove(weakSelf)] (NSItemProviderCompletionHandler completionHandler, Class expectedValueClass, NSDictionary * options) {
106         if (auto strongSelf = weakSelf.get())
107             [strongSelf setCompletionHandler:completionHandler];
108     }];
109     return _item.get();
110 }
111
112 - (void)setProgress:(float)progress
113 {
114     if (_item)
115         [_item setPreviewItemProviderProgress:@(progress)];
116 }
117
118 - (void)finish:(WebCore::URL)url
119 {
120     if (self.completionHandler)
121         self.completionHandler((NSURL*)url, nil);
122 }
123
124 - (void)failWithError:(NSError *)error
125 {
126     if (self.completionHandler)
127         self.completionHandler(nil, error);
128 }
129
130 @end
131
132 @interface _WKPreviewControllerDelegate : NSObject <QLPreviewControllerDelegate> {
133     WebKit::SystemPreviewController* _previewController;
134     WebCore::IntRect _linkRect;
135 };
136 @end
137
138 @implementation _WKPreviewControllerDelegate
139
140 - (id)initWithSystemPreviewController:(WebKit::SystemPreviewController*)previewController fromRect:(WebCore::IntRect)rect
141 {
142     if (!(self = [super init]))
143         return nil;
144
145     _previewController = previewController;
146     _linkRect = rect;
147     return self;
148 }
149
150 - (void)previewControllerDidDismiss:(QLPreviewController *)controller
151 {
152     if (_previewController)
153         _previewController->cancel();
154 }
155
156 - (UIViewController *)presentingViewController
157 {
158     if (!_previewController)
159         return nil;
160
161     return _previewController->page().uiClient().presentingViewController();
162 }
163
164 - (CGRect)previewController:(QLPreviewController *)controller frameForPreviewItem:(id <QLPreviewItem>)item inSourceView:(UIView * *)view
165 {
166     UIViewController *presentingViewController = [self presentingViewController];
167
168     if (!presentingViewController)
169         return CGRectZero;
170
171     *view = presentingViewController.view;
172
173     if (_linkRect.isEmpty()) {
174         CGRect frame;
175         frame.size.width = presentingViewController.view.frame.size.width / 2.0;
176         frame.size.height = presentingViewController.view.frame.size.height / 2.0;
177         frame.origin.x = (presentingViewController.view.frame.size.width - frame.size.width) / 2.0;
178         frame.origin.y = (presentingViewController.view.frame.size.height - frame.size.height) / 2.0;
179         return frame;
180     }
181
182     return _previewController->page().syncRootViewToScreen(_linkRect);
183 }
184
185 - (UIImage *)previewController:(QLPreviewController *)controller transitionImageForPreviewItem:(id <QLPreviewItem>)item contentRect:(CGRect *)contentRect
186 {
187     *contentRect = CGRectZero;
188
189     UIViewController *presentingViewController = [self presentingViewController];
190     if (presentingViewController) {
191         if (_linkRect.isEmpty())
192             *contentRect = {CGPointZero, {presentingViewController.view.frame.size.width / 2.0, presentingViewController.view.frame.size.height / 2.0}};
193         else {
194             WebCore::IntRect screenRect = _previewController->page().syncRootViewToScreen(_linkRect);
195             *contentRect = { CGPointZero, { static_cast<CGFloat>(screenRect.width()), static_cast<CGFloat>(screenRect.height()) } };
196         }
197     }
198
199     return [[UIImage new] autorelease];
200 }
201
202 @end
203
204 namespace WebKit {
205
206 void SystemPreviewController::start(const String& mimeType, const WebCore::IntRect& fromRect)
207 {
208     ASSERT(!m_qlPreviewController);
209     if (m_qlPreviewController)
210         return;
211
212     UIViewController *presentingViewController = m_webPageProxy.uiClient().presentingViewController();
213
214     if (!presentingViewController)
215         return;
216
217     m_qlPreviewController = adoptNS([allocQLPreviewControllerInstance() init]);
218
219     m_qlPreviewControllerDelegate = adoptNS([[_WKPreviewControllerDelegate alloc] initWithSystemPreviewController:this fromRect:fromRect]);
220     [m_qlPreviewController setDelegate:m_qlPreviewControllerDelegate.get()];
221
222     m_qlPreviewControllerDataSource = adoptNS([[_WKPreviewControllerDataSource alloc] initWithMIMEType:mimeType]);
223     [m_qlPreviewController setDataSource:m_qlPreviewControllerDataSource.get()];
224
225     [presentingViewController presentViewController:m_qlPreviewController.get() animated:YES completion:nullptr];
226 }
227
228 void SystemPreviewController::updateProgress(float progress)
229 {
230     if (m_qlPreviewControllerDataSource)
231         [m_qlPreviewControllerDataSource setProgress:progress];
232 }
233
234 void SystemPreviewController::finish(WebCore::URL url)
235 {
236     if (m_qlPreviewControllerDataSource)
237         [m_qlPreviewControllerDataSource finish:url];
238 }
239
240 void SystemPreviewController::cancel()
241 {
242     if (m_qlPreviewController)
243         [m_qlPreviewController.get() dismissViewControllerAnimated:YES completion:nullptr];
244
245     m_qlPreviewControllerDelegate = nullptr;
246     m_qlPreviewControllerDataSource = nullptr;
247     m_qlPreviewController = nullptr;
248 }
249
250 void SystemPreviewController::fail(const WebCore::ResourceError& error)
251 {
252     if (m_qlPreviewControllerDataSource)
253         [m_qlPreviewControllerDataSource failWithError:error.nsError()];
254 }
255
256 }
257
258 #endif