Have insertDictatedTextAsync() take an InsertTextOptions
[WebKit-https.git] / Source / WebKit / WebProcess / WebPage / Cocoa / WebPageCocoa.mm
1 /*
2  * Copyright (C) 2016-2019 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 "WebPage.h"
28
29 #import "AttributedString.h"
30 #import "InsertTextOptions.h"
31 #import "LoadParameters.h"
32 #import "PluginView.h"
33 #import "WKAccessibilityWebPageObjectBase.h"
34 #import "WebPageProxyMessages.h"
35 #import "WebPaymentCoordinator.h"
36 #import "WebRemoteObjectRegistry.h"
37 #import <pal/spi/cocoa/LaunchServicesSPI.h>
38 #import <WebCore/DictionaryLookup.h>
39 #import <WebCore/Editor.h>
40 #import <WebCore/EventHandler.h>
41 #import <WebCore/FocusController.h>
42 #import <WebCore/FrameView.h>
43 #import <WebCore/HTMLConverter.h>
44 #import <WebCore/HitTestResult.h>
45 #import <WebCore/NodeRenderStyle.h>
46 #import <WebCore/PaymentCoordinator.h>
47 #import <WebCore/PlatformMediaSessionManager.h>
48 #import <WebCore/Range.h>
49 #import <WebCore/RenderElement.h>
50 #import <WebCore/SimpleRange.h>
51
52 #if PLATFORM(COCOA)
53
54 namespace WebKit {
55
56 void WebPage::platformDidReceiveLoadParameters(const LoadParameters& loadParameters)
57 {
58     m_dataDetectionContext = loadParameters.dataDetectionContext;
59 }
60
61 void WebPage::requestActiveNowPlayingSessionInfo(CallbackID callbackID)
62 {
63     bool hasActiveSession = false;
64     String title = emptyString();
65     double duration = NAN;
66     double elapsedTime = NAN;
67     uint64_t uniqueIdentifier = 0;
68     bool registeredAsNowPlayingApplication = false;
69     if (auto* sharedManager = WebCore::PlatformMediaSessionManager::sharedManagerIfExists()) {
70         hasActiveSession = sharedManager->hasActiveNowPlayingSession();
71         title = sharedManager->lastUpdatedNowPlayingTitle();
72         duration = sharedManager->lastUpdatedNowPlayingDuration();
73         elapsedTime = sharedManager->lastUpdatedNowPlayingElapsedTime();
74         uniqueIdentifier = sharedManager->lastUpdatedNowPlayingInfoUniqueIdentifier().toUInt64();
75         registeredAsNowPlayingApplication = sharedManager->registeredAsNowPlayingApplication();
76     }
77
78     send(Messages::WebPageProxy::NowPlayingInfoCallback(hasActiveSession, registeredAsNowPlayingApplication, title, duration, elapsedTime, uniqueIdentifier, callbackID));
79 }
80     
81 void WebPage::performDictionaryLookupAtLocation(const FloatPoint& floatPoint)
82 {
83     if (auto* pluginView = pluginViewForFrame(&m_page->mainFrame())) {
84         if (pluginView->performDictionaryLookupAtLocation(floatPoint))
85             return;
86     }
87     
88     // Find the frame the point is over.
89     constexpr OptionSet<HitTestRequest::RequestType> hitType { HitTestRequest::ReadOnly, HitTestRequest::Active, HitTestRequest::DisallowUserAgentShadowContent, HitTestRequest::AllowChildFrameContent };
90     HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint(m_page->mainFrame().view()->windowToContents(roundedIntPoint(floatPoint)), hitType);
91     auto [range, options] = DictionaryLookup::rangeAtHitTestResult(result);
92     if (!range)
93         return;
94     
95     auto* frame = result.innerNonSharedNode() ? result.innerNonSharedNode()->document().frame() : &m_page->focusController().focusedOrMainFrame();
96     if (!frame)
97         return;
98     
99     performDictionaryLookupForRange(*frame, *range, options, TextIndicatorPresentationTransition::Bounce);
100 }
101
102 void WebPage::performDictionaryLookupForSelection(Frame& frame, const VisibleSelection& selection, TextIndicatorPresentationTransition presentationTransition)
103 {
104     auto [selectedRange, options] = DictionaryLookup::rangeForSelection(selection);
105     if (selectedRange)
106         performDictionaryLookupForRange(frame, *selectedRange, options, presentationTransition);
107 }
108
109 void WebPage::performDictionaryLookupOfCurrentSelection()
110 {
111     auto& frame = m_page->focusController().focusedOrMainFrame();
112     performDictionaryLookupForSelection(frame, frame.selection().selection(), TextIndicatorPresentationTransition::BounceAndCrossfade);
113 }
114     
115 void WebPage::performDictionaryLookupForRange(Frame& frame, Range& range, NSDictionary *options, TextIndicatorPresentationTransition presentationTransition)
116 {
117     send(Messages::WebPageProxy::DidPerformDictionaryLookup(dictionaryPopupInfoForRange(frame, range, options, presentationTransition)));
118 }
119
120 DictionaryPopupInfo WebPage::dictionaryPopupInfoForRange(Frame& frame, Range& range, NSDictionary *options, TextIndicatorPresentationTransition presentationTransition)
121 {
122     Editor& editor = frame.editor();
123     editor.setIsGettingDictionaryPopupInfo(true);
124
125     DictionaryPopupInfo dictionaryPopupInfo;
126     if (range.text().stripWhiteSpace().isEmpty()) {
127         editor.setIsGettingDictionaryPopupInfo(false);
128         return dictionaryPopupInfo;
129     }
130
131     Vector<FloatQuad> quads;
132     range.absoluteTextQuads(quads);
133     if (quads.isEmpty()) {
134         editor.setIsGettingDictionaryPopupInfo(false);
135         return dictionaryPopupInfo;
136     }
137
138     IntRect rangeRect = frame.view()->contentsToWindow(quads[0].enclosingBoundingBox());
139
140     const RenderStyle* style = range.startContainer().renderStyle();
141     float scaledAscent = style ? style->fontMetrics().ascent() * pageScaleFactor() : 0;
142     dictionaryPopupInfo.origin = FloatPoint(rangeRect.x(), rangeRect.y() + scaledAscent);
143     dictionaryPopupInfo.options = options;
144
145 #if PLATFORM(MAC)
146     NSAttributedString *nsAttributedString = editingAttributedStringFromRange(range, IncludeImagesInAttributedString::No);
147     
148     RetainPtr<NSMutableAttributedString> scaledNSAttributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:[nsAttributedString string]]);
149     
150     NSFontManager *fontManager = [NSFontManager sharedFontManager];
151     
152     [nsAttributedString enumerateAttributesInRange:NSMakeRange(0, [nsAttributedString length]) options:0 usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
153         RetainPtr<NSMutableDictionary> scaledAttributes = adoptNS([attributes mutableCopy]);
154         
155         NSFont *font = [scaledAttributes objectForKey:NSFontAttributeName];
156         if (font)
157             font = [fontManager convertFont:font toSize:font.pointSize * pageScaleFactor()];
158         if (font)
159             [scaledAttributes setObject:font forKey:NSFontAttributeName];
160         
161         [scaledNSAttributedString addAttributes:scaledAttributes.get() range:range];
162     }];
163 #endif // PLATFORM(MAC)
164
165     TextIndicatorOptions indicatorOptions = TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges;
166     if (presentationTransition == TextIndicatorPresentationTransition::BounceAndCrossfade)
167         indicatorOptions |= TextIndicatorOptionIncludeSnapshotWithSelectionHighlight;
168     
169     auto textIndicator = TextIndicator::createWithRange(range, indicatorOptions, presentationTransition);
170     if (!textIndicator) {
171         editor.setIsGettingDictionaryPopupInfo(false);
172         return dictionaryPopupInfo;
173     }
174
175     dictionaryPopupInfo.textIndicator = textIndicator->data();
176 #if PLATFORM(MAC)
177     dictionaryPopupInfo.attributedString = scaledNSAttributedString;
178 #elif PLATFORM(MACCATALYST)
179     dictionaryPopupInfo.attributedString = adoptNS([[NSMutableAttributedString alloc] initWithString:range.text()]);
180 #endif
181
182     editor.setIsGettingDictionaryPopupInfo(false);
183     return dictionaryPopupInfo;
184 }
185
186 void WebPage::insertDictatedTextAsync(const String& text, const EditingRange& replacementEditingRange, const Vector<WebCore::DictationAlternative>& dictationAlternativeLocations, InsertTextOptions&& options)
187 {
188     auto& frame = m_page->focusController().focusedOrMainFrame();
189     Ref<Frame> protector { frame };
190
191     if (replacementEditingRange.location != notFound) {
192         auto replacementRange = EditingRange::toRange(frame, replacementEditingRange);
193         if (replacementRange)
194             frame.selection().setSelection(VisibleSelection { *replacementRange, SEL_DEFAULT_AFFINITY });
195     }
196
197     if (options.registerUndoGroup)
198         send(Messages::WebPageProxy::RegisterInsertionUndoGrouping { });
199
200     ASSERT(!frame.editor().hasComposition());
201     frame.editor().insertDictatedText(text, dictationAlternativeLocations, nullptr /* triggeringEvent */);
202 }
203
204 void WebPage::accessibilityTransferRemoteToken(RetainPtr<NSData> remoteToken)
205 {
206     IPC::DataReference dataToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteToken bytes]), [remoteToken length]);
207     send(Messages::WebPageProxy::RegisterWebProcessAccessibilityToken(dataToken));
208 }
209
210 #if ENABLE(APPLE_PAY)
211 WebPaymentCoordinator* WebPage::paymentCoordinator()
212 {
213     if (!m_page)
214         return nullptr;
215     auto& client = m_page->paymentCoordinator().client();
216     return is<WebPaymentCoordinator>(client) ? downcast<WebPaymentCoordinator>(&client) : nullptr;
217 }
218 #endif
219
220 void WebPage::getContentsAsAttributedString(CompletionHandler<void(const AttributedString&)>&& completionHandler)
221 {
222     auto* documentElement = m_page->mainFrame().document()->documentElement();
223     if (!documentElement) {
224         completionHandler({ });
225         return;
226     }
227
228     NSDictionary* documentAttributes = nil;
229
230     AttributedString result;
231     result.string = attributedStringFromRange(rangeOfContents(*documentElement), &documentAttributes);
232     result.documentAttributes = documentAttributes;
233
234     completionHandler({ result });
235 }
236
237 void WebPage::setRemoteObjectRegistry(WebRemoteObjectRegistry* registry)
238 {
239     m_remoteObjectRegistry = makeWeakPtr(registry);
240 }
241
242 WebRemoteObjectRegistry* WebPage::remoteObjectRegistry()
243 {
244     return m_remoteObjectRegistry.get();
245 }
246
247 void WebPage::updateMockAccessibilityElementAfterCommittingLoad()
248 {
249     auto* document = mainFrame()->document();
250     [m_mockAccessibilityElement setHasMainFramePlugin:document ? document->isPluginDocument() : false];
251 }
252
253 RetainPtr<CFDataRef> WebPage::pdfSnapshotAtSize(IntRect rect, IntSize bitmapSize, SnapshotOptions options)
254 {
255     Frame* coreFrame = m_mainFrame->coreFrame();
256     if (!coreFrame)
257         return nullptr;
258
259     FrameView* frameView = coreFrame->view();
260     if (!frameView)
261         return nullptr;
262
263     auto data = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
264
265     auto dataConsumer = adoptCF(CGDataConsumerCreateWithCFData(data.get()));
266     auto mediaBox = CGRectMake(0, 0, bitmapSize.width(), bitmapSize.height());
267     auto pdfContext = adoptCF(CGPDFContextCreate(dataConsumer.get(), &mediaBox, nullptr));
268
269     int64_t remainingHeight = bitmapSize.height();
270     int64_t nextRectY = rect.y();
271     while (remainingHeight > 0) {
272         // PDFs have a per-page height limit of 200 inches at 72dpi.
273         // We'll export one PDF page at a time, up to that maximum height.
274         static const int64_t maxPageHeight = 72 * 200;
275         bitmapSize.setHeight(std::min(remainingHeight, maxPageHeight));
276         rect.setHeight(bitmapSize.height());
277         rect.setY(nextRectY);
278
279         CGRect mediaBox = CGRectMake(0, 0, bitmapSize.width(), bitmapSize.height());
280         auto mediaBoxData = adoptCF(CFDataCreate(NULL, (const UInt8 *)&mediaBox, sizeof(CGRect)));
281         auto dictionary = (CFDictionaryRef)@{
282             (NSString *)kCGPDFContextMediaBox : (NSData *)mediaBoxData.get()
283         };
284
285         CGPDFContextBeginPage(pdfContext.get(), dictionary);
286
287         GraphicsContext graphicsContext { pdfContext.get() };
288         graphicsContext.scale({ 1, -1 });
289         graphicsContext.translate(0, -bitmapSize.height());
290
291         paintSnapshotAtSize(rect, bitmapSize, options, *coreFrame, *frameView, graphicsContext);
292
293         CGPDFContextEndPage(pdfContext.get());
294
295         nextRectY += bitmapSize.height();
296         remainingHeight -= maxPageHeight;
297     }
298
299     CGPDFContextClose(pdfContext.get());
300
301     return data;
302 }
303
304 void WebPage::getProcessDisplayName(CompletionHandler<void(String&&)>&& completionHandler)
305 {
306 #if PLATFORM(MAC)
307     completionHandler(adoptCF((CFStringRef)_LSCopyApplicationInformationItem(kLSDefaultSessionID, _LSGetCurrentApplicationASN(), _kLSDisplayNameKey)).get());
308 #else
309     completionHandler({ });
310 #endif
311 }
312
313 } // namespace WebKit
314
315 #endif // PLATFORM(COCOA)