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