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