1/*
2 * Copyright (C) 2012-2014 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#if PLATFORM(IOS)
30
31#import "AssistedNodeInformation.h"
32#import "DataReference.h"
33#import "DrawingArea.h"
34#import "EditingRange.h"
35#import "EditorState.h"
36#import "GestureTypes.h"
37#import "InjectedBundleUserMessageCoders.h"
38#import "InteractionInformationAtPosition.h"
39#import "PluginView.h"
40#import "RemoteLayerTreeDrawingArea.h"
41#import "VisibleContentRectUpdateInfo.h"
42#import "WKAccessibilityWebPageObjectIOS.h"
43#import "WebChromeClient.h"
44#import "WebCoreArgumentCoders.h"
45#import "WebFrame.h"
46#import "WebKitSystemInterface.h"
47#import "WebKitSystemInterfaceIOS.h"
48#import "WebPageProxyMessages.h"
49#import "WebProcess.h"
50#import <CoreText/CTFont.h>
51#import <WebCore/Chrome.h>
52#import <WebCore/DNS.h>
53#import <WebCore/Element.h>
54#import <WebCore/EventHandler.h>
55#import <WebCore/FloatQuad.h>
56#import <WebCore/FocusController.h>
57#import <WebCore/Frame.h>
58#import <WebCore/FrameView.h>
59#import <WebCore/HTMLElementTypeHelpers.h>
60#import <WebCore/HTMLFormElement.h>
61#import <WebCore/HTMLInputElement.h>
62#import <WebCore/HTMLOptGroupElement.h>
63#import <WebCore/HTMLOptionElement.h>
64#import <WebCore/HTMLOptionElement.h>
65#import <WebCore/HTMLParserIdioms.h>
66#import <WebCore/HTMLSelectElement.h>
67#import <WebCore/HTMLTextAreaElement.h>
68#import <WebCore/HistoryItem.h>
69#import <WebCore/HitTestResult.h>
70#import <WebCore/KeyboardEvent.h>
71#import <WebCore/MainFrame.h>
72#import <WebCore/MediaSessionManagerIOS.h>
73#import <WebCore/MemoryPressureHandler.h>
74#import <WebCore/Node.h>
75#import <WebCore/NotImplemented.h>
76#import <WebCore/Page.h>
77#import <WebCore/Pasteboard.h>
78#import <WebCore/PlatformKeyboardEvent.h>
79#import <WebCore/PlatformMouseEvent.h>
80#import <WebCore/RenderBlock.h>
81#import <WebCore/RenderImage.h>
82#import <WebCore/RenderThemeIOS.h>
83#import <WebCore/RenderView.h>
84#import <WebCore/ResourceBuffer.h>
85#import <WebCore/SharedBuffer.h>
86#import <WebCore/TextIterator.h>
87#import <WebCore/VisibleUnits.h>
88#import <WebCore/WKContentObservation.h>
89#import <WebCore/WebEvent.h>
90#import <wtf/TemporaryChange.h>
91
92using namespace WebCore;
93
94namespace WebKit {
95
96const int blockSelectionStartWidth = 100;
97const int blockSelectionStartHeight = 100;
98
99void WebPage::platformInitialize()
100{
101    platformInitializeAccessibility();
102}
103
104void WebPage::platformInitializeAccessibility()
105{
106    m_mockAccessibilityElement = adoptNS([[WKAccessibilityWebPageObject alloc] init]);
107    [m_mockAccessibilityElement setWebPage:this];
108
109    RetainPtr<CFUUIDRef> uuid = adoptCF(CFUUIDCreate(kCFAllocatorDefault));
110    NSData *remoteToken = WKAXRemoteToken(uuid.get());
111
112    IPC::DataReference dataToken = IPC::DataReference(reinterpret_cast<const uint8_t*>([remoteToken bytes]), [remoteToken length]);
113    send(Messages::WebPageProxy::RegisterWebProcessAccessibilityToken(dataToken));
114}
115
116void WebPage::platformPreferencesDidChange(const WebPreferencesStore&)
117{
118    notImplemented();
119}
120
121FloatSize WebPage::screenSize() const
122{
123    return m_screenSize;
124}
125
126FloatSize WebPage::availableScreenSize() const
127{
128    return m_availableScreenSize;
129}
130
131void WebPage::viewportPropertiesDidChange(const ViewportArguments& viewportArguments)
132{
133    if (m_viewportConfiguration.viewportArguments() == viewportArguments)
134        return;
135
136    float oldWidth = m_viewportConfiguration.viewportArguments().width;
137    bool wasUsingMinimalUI = m_viewportConfiguration.usesMinimalUI();
138
139    m_viewportConfiguration.setViewportArguments(viewportArguments);
140    viewportConfigurationChanged();
141
142    if (wasUsingMinimalUI != m_viewportConfiguration.usesMinimalUI())
143        send(Messages::WebPageProxy::SetUsesMinimalUI(m_viewportConfiguration.usesMinimalUI()));
144
145    if (oldWidth != viewportArguments.width)
146        send(Messages::WebPageProxy::ViewportMetaTagWidthDidChange(viewportArguments.width));
147}
148
149void WebPage::didReceiveMobileDocType(bool isMobileDoctype)
150{
151    if (isMobileDoctype)
152        m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::xhtmlMobileParameters());
153    else
154        resetViewportDefaultConfiguration(m_mainFrame.get());
155}
156
157void WebPage::savePageState(HistoryItem& historyItem)
158{
159    historyItem.setScaleIsInitial(!m_userHasChangedPageScaleFactor);
160    historyItem.setMinimumLayoutSizeInScrollViewCoordinates(m_viewportConfiguration.activeMinimumLayoutSizeInScrollViewCoordinates());
161    historyItem.setContentSize(m_viewportConfiguration.contentsSize());
162}
163
164static double scaleAfterViewportWidthChange(double currentScale, bool userHasChangedPageScaleFactor, const ViewportConfiguration& viewportConfiguration, float unobscuredWidthInScrollViewCoordinates, const IntSize& newContentSize, const IntSize& oldContentSize, float visibleHorizontalFraction)
165{
166    double scale;
167    if (!userHasChangedPageScaleFactor)
168        scale = viewportConfiguration.initialScale();
169    else
170        scale = std::max(std::min(currentScale, viewportConfiguration.maximumScale()), viewportConfiguration.minimumScale());
171
172    if (userHasChangedPageScaleFactor) {
173        // When the content size changes, we keep the same relative horizontal content width in view, otherwise we would
174        // end up zoomed too far in landscape->portrait, and too close in portrait->landscape.
175        double widthToKeepInView = visibleHorizontalFraction * newContentSize.width();
176        double newScale = unobscuredWidthInScrollViewCoordinates / widthToKeepInView;
177        scale = std::max(std::min(newScale, viewportConfiguration.maximumScale()), viewportConfiguration.minimumScale());
178    }
179    return scale;
180}
181
182static FloatPoint relativeCenterAfterContentSizeChange(const FloatRect& originalContentRect, IntSize oldContentSize, IntSize newContentSize)
183{
184    // If the content size has changed, keep the same relative position.
185    FloatPoint oldContentCenter = originalContentRect.center();
186    float relativeHorizontalPosition = oldContentCenter.x() / oldContentSize.width();
187    float relativeVerticalPosition =  oldContentCenter.y() / oldContentSize.height();
188    return FloatPoint(relativeHorizontalPosition * newContentSize.width(), relativeVerticalPosition * newContentSize.height());
189}
190
191static inline FloatRect adjustExposedRectForNewScale(const FloatRect& exposedRect, double exposedRectScale, double newScale)
192{
193    double overscaledWidth = exposedRect.width();
194    double missingHorizonalMargin = exposedRect.width() * exposedRectScale / newScale - overscaledWidth;
195
196    double overscaledHeight = exposedRect.height();
197    double missingVerticalMargin = exposedRect.height() * exposedRectScale / newScale - overscaledHeight;
198
199    return FloatRect(exposedRect.x() - missingHorizonalMargin / 2, exposedRect.y() - missingVerticalMargin / 2, exposedRect.width() + missingHorizonalMargin, exposedRect.height() + missingVerticalMargin);
200}
201
202void WebPage::restorePageState(const HistoryItem& historyItem)
203{
204    // When a HistoryItem is cleared, its scale factor and scroll point are set to zero. We should not try to restore the other
205    // parameters in those conditions.
206    if (!historyItem.pageScaleFactor())
207        return;
208
209    // We can restore the exposed rect and scale, but we cannot touch the scroll position since the obscured insets
210    // may be changing in the UIProcess. The UIProcess can update the position from the information we send and will then
211    // scroll to the correct position through a regular VisibleContentRectUpdate.
212
213    m_userHasChangedPageScaleFactor = !historyItem.scaleIsInitial();
214
215    FloatSize currentMinimumLayoutSizeInScrollViewCoordinates = m_viewportConfiguration.activeMinimumLayoutSizeInScrollViewCoordinates();
216    if (historyItem.minimumLayoutSizeInScrollViewCoordinates() == currentMinimumLayoutSizeInScrollViewCoordinates) {
217        float boundedScale = std::min<float>(m_viewportConfiguration.maximumScale(), std::max<float>(m_viewportConfiguration.minimumScale(), historyItem.pageScaleFactor()));
218        scalePage(boundedScale, IntPoint());
219
220        m_drawingArea->setExposedContentRect(historyItem.exposedContentRect());
221
222        send(Messages::WebPageProxy::RestorePageState(historyItem.exposedContentRect(), boundedScale));
223    } else {
224        IntSize oldContentSize = historyItem.contentSize();
225        FrameView& frameView = *m_page->mainFrame().view();
226        IntSize newContentSize = frameView.contentsSize();
227        double visibleHorizontalFraction = static_cast<float>(historyItem.unobscuredContentRect().width()) / oldContentSize.width();
228
229        double newScale = scaleAfterViewportWidthChange(historyItem.pageScaleFactor(), !historyItem.scaleIsInitial(), m_viewportConfiguration, currentMinimumLayoutSizeInScrollViewCoordinates.width(), newContentSize, oldContentSize, visibleHorizontalFraction);
230
231        FloatPoint newCenter;
232        if (!oldContentSize.isEmpty() && !newContentSize.isEmpty() && newContentSize != oldContentSize)
233            newCenter = relativeCenterAfterContentSizeChange(historyItem.unobscuredContentRect(), oldContentSize, newContentSize);
234        else
235            newCenter = FloatRect(historyItem.unobscuredContentRect()).center();
236
237        FloatSize unobscuredRectAtNewScale = frameView.customSizeForResizeEvent();
238        unobscuredRectAtNewScale.scale(1 / newScale, 1 / newScale);
239
240        FloatRect oldExposedRect = frameView.exposedContentRect();
241        FloatRect adjustedExposedRect = adjustExposedRectForNewScale(oldExposedRect, m_page->pageScaleFactor(), newScale);
242
243        FloatPoint oldCenter = adjustedExposedRect.center();
244        adjustedExposedRect.move(newCenter - oldCenter);
245
246        scalePage(newScale, IntPoint());
247
248        send(Messages::WebPageProxy::RestorePageCenterAndScale(newCenter, newScale));
249    }
250}
251
252double WebPage::minimumPageScaleFactor() const
253{
254    if (!m_viewportConfiguration.allowsUserScaling())
255        return m_page->pageScaleFactor();
256    return m_viewportConfiguration.minimumScale();
257}
258
259double WebPage::maximumPageScaleFactor() const
260{
261    if (!m_viewportConfiguration.allowsUserScaling())
262        return m_page->pageScaleFactor();
263    return m_viewportConfiguration.maximumScale();
264}
265
266bool WebPage::allowsUserScaling() const
267{
268    return m_viewportConfiguration.allowsUserScaling();
269}
270
271bool WebPage::handleEditingKeyboardEvent(KeyboardEvent* event)
272{
273    const PlatformKeyboardEvent* platformEvent = event->keyEvent();
274    if (!platformEvent)
275        return false;
276
277    // FIXME: Interpret the event immediately upon receiving it in UI process, without sending to WebProcess first.
278    bool eventWasHandled = false;
279    bool sendResult = WebProcess::shared().parentProcessConnection()->sendSync(Messages::WebPageProxy::InterpretKeyEvent(editorState(), platformEvent->type() == PlatformKeyboardEvent::Char),
280                                                                               Messages::WebPageProxy::InterpretKeyEvent::Reply(eventWasHandled), m_pageID);
281    if (!sendResult)
282        return false;
283
284    return eventWasHandled;
285}
286
287void WebPage::sendComplexTextInputToPlugin(uint64_t, const String&)
288{
289    notImplemented();
290}
291
292void WebPage::performDictionaryLookupAtLocation(const FloatPoint&)
293{
294    notImplemented();
295}
296
297void WebPage::performDictionaryLookupForSelection(Frame*, const VisibleSelection&)
298{
299    notImplemented();
300}
301
302void WebPage::performDictionaryLookupForRange(Frame*, Range&, NSDictionary *)
303{
304    notImplemented();
305}
306
307bool WebPage::performNonEditingBehaviorForSelector(const String&, WebCore::KeyboardEvent*)
308{
309    notImplemented();
310    return false;
311}
312
313bool WebPage::performDefaultBehaviorForKeyEvent(const WebKeyboardEvent&)
314{
315    notImplemented();
316    return false;
317}
318
319NSObject *WebPage::accessibilityObjectForMainFramePlugin()
320{
321    if (!m_page)
322        return 0;
323
324    if (PluginView* pluginView = pluginViewForFrame(&m_page->mainFrame()))
325        return pluginView->accessibilityObject();
326
327    return 0;
328}
329
330void WebPage::registerUIProcessAccessibilityTokens(const IPC::DataReference& elementToken, const IPC::DataReference&)
331{
332    NSData *elementTokenData = [NSData dataWithBytes:elementToken.data() length:elementToken.size()];
333    [m_mockAccessibilityElement setRemoteTokenData:elementTokenData];
334}
335
336void WebPage::readSelectionFromPasteboard(const String&, bool&)
337{
338    notImplemented();
339}
340
341void WebPage::getStringSelectionForPasteboard(String&)
342{
343    notImplemented();
344}
345
346void WebPage::getDataSelectionForPasteboard(const String, SharedMemory::Handle&, uint64_t&)
347{
348    notImplemented();
349}
350
351WKAccessibilityWebPageObject* WebPage::accessibilityRemoteObject()
352{
353    notImplemented();
354    return 0;
355}
356
357bool WebPage::platformHasLocalDataForURL(const WebCore::URL&)
358{
359    notImplemented();
360    return false;
361}
362
363String WebPage::cachedSuggestedFilenameForURL(const URL&)
364{
365    notImplemented();
366    return String();
367}
368
369String WebPage::cachedResponseMIMETypeForURL(const URL&)
370{
371    notImplemented();
372    return String();
373}
374
375PassRefPtr<SharedBuffer> WebPage::cachedResponseDataForURL(const URL&)
376{
377    notImplemented();
378    return 0;
379}
380
381bool WebPage::platformCanHandleRequest(const WebCore::ResourceRequest&)
382{
383    notImplemented();
384    return false;
385}
386
387void WebPage::shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent&, bool&)
388{
389    notImplemented();
390}
391
392void WebPage::acceptsFirstMouse(int, const WebKit::WebMouseEvent&, bool&)
393{
394    notImplemented();
395}
396
397void WebPage::computePagesForPrintingPDFDocument(uint64_t, const PrintInfo&, Vector<IntRect>&)
398{
399    notImplemented();
400}
401
402void WebPage::drawPagesToPDFFromPDFDocument(CGContextRef, PDFDocument *, const PrintInfo&, uint32_t, uint32_t)
403{
404    notImplemented();
405}
406
407void WebPage::advanceToNextMisspelling(bool)
408{
409    notImplemented();
410}
411
412IntRect WebPage::rectForElementAtInteractionLocation()
413{
414    HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint(m_lastInteractionLocation, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AllowChildFrameContent);
415    Node* hitNode = result.innerNode();
416    if (!hitNode || !hitNode->renderer())
417        return IntRect();
418    return result.innerNodeFrame()->view()->contentsToRootView(hitNode->renderer()->absoluteBoundingBoxRect(true));
419}
420
421void WebPage::updateSelectionAppearance()
422{
423    Frame& frame = m_page->focusController().focusedOrMainFrame();
424    if (!frame.editor().ignoreCompositionSelectionChange() && (frame.editor().hasComposition() || !frame.selection().selection().isNone()))
425        didChangeSelection();
426}
427
428void WebPage::handleSyntheticClick(Node* nodeRespondingToClick, const WebCore::FloatPoint& location)
429{
430    IntPoint roundedAdjustedPoint = roundedIntPoint(location);
431    Frame& mainframe = m_page->mainFrame();
432
433    WKBeginObservingContentChanges(true);
434
435    mainframe.eventHandler().mouseMoved(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, NoButton, PlatformEvent::MouseMoved, 0, false, false, false, false, 0));
436    mainframe.document()->updateStyleIfNeeded();
437
438    WKStopObservingContentChanges();
439
440    m_pendingSyntheticClickNode = nullptr;
441    m_pendingSyntheticClickLocation = FloatPoint();
442
443    switch (WKObservedContentChange()) {
444    case WKContentVisibilityChange:
445        // The move event caused new contents to appear. Don't send the click event.
446        return;
447    case WKContentIndeterminateChange:
448        // Wait for callback to completePendingSyntheticClickForContentChangeObserver() to decide whether to send the click event.
449        m_pendingSyntheticClickNode = nodeRespondingToClick;
450        m_pendingSyntheticClickLocation = location;
451        return;
452    case WKContentNoChange:
453        completeSyntheticClick(nodeRespondingToClick, location);
454        return;
455    }
456    ASSERT_NOT_REACHED();
457}
458
459void WebPage::completePendingSyntheticClickForContentChangeObserver()
460{
461    if (!m_pendingSyntheticClickNode)
462        return;
463    // Only dispatch the click if the document didn't get changed by any timers started by the move event.
464    if (WKObservedContentChange() == WKContentNoChange)
465        completeSyntheticClick(m_pendingSyntheticClickNode.get(), m_pendingSyntheticClickLocation);
466
467    m_pendingSyntheticClickNode = nullptr;
468    m_pendingSyntheticClickLocation = FloatPoint();
469}
470
471void WebPage::completeSyntheticClick(Node* nodeRespondingToClick, const WebCore::FloatPoint& location)
472{
473    IntPoint roundedAdjustedPoint = roundedIntPoint(location);
474    Frame& mainframe = m_page->mainFrame();
475
476    RefPtr<Frame> oldFocusedFrame = m_page->focusController().focusedFrame();
477    RefPtr<Element> oldFocusedElement = oldFocusedFrame ? oldFocusedFrame->document()->focusedElement() : nullptr;
478    m_userIsInteracting = true;
479
480    bool tapWasHandled = false;
481    m_lastInteractionLocation = roundedAdjustedPoint;
482    tapWasHandled |= mainframe.eventHandler().handleMousePressEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MousePressed, 1, false, false, false, false, 0));
483    tapWasHandled |= mainframe.eventHandler().handleMouseReleaseEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MouseReleased, 1, false, false, false, false, 0));
484
485    RefPtr<Frame> newFocusedFrame = m_page->focusController().focusedFrame();
486    RefPtr<Element> newFocusedElement = newFocusedFrame ? newFocusedFrame->document()->focusedElement() : nullptr;
487
488    // If the focus has not changed, we need to notify the client anyway, since it might be
489    // necessary to start assisting the node.
490    // If the node has been focused by JavaScript without user interaction, the
491    // keyboard is not on screen.
492    if (newFocusedElement && newFocusedElement == oldFocusedElement)
493        elementDidFocus(newFocusedElement.get());
494
495    m_userIsInteracting = false;
496
497    if (!tapWasHandled || !nodeRespondingToClick || !nodeRespondingToClick->isElementNode())
498        send(Messages::WebPageProxy::DidNotHandleTapAsClick(roundedIntPoint(location)));
499}
500
501void WebPage::handleTap(const IntPoint& point)
502{
503    FloatPoint adjustedPoint;
504    Node* nodeRespondingToClick = m_page->mainFrame().nodeRespondingToClickEvents(point, adjustedPoint);
505    handleSyntheticClick(nodeRespondingToClick, adjustedPoint);
506}
507
508void WebPage::sendTapHighlightForNodeIfNecessary(uint64_t requestID, Node* node)
509{
510    if (!node)
511        return;
512
513    if (isElement(*node))
514        prefetchDNS(toElement(*node).absoluteLinkURL().host());
515
516    Vector<FloatQuad> quads;
517    if (RenderObject *renderer = node->renderer()) {
518        renderer->absoluteQuads(quads);
519        Color highlightColor = renderer->style().tapHighlightColor();
520        if (!node->document().frame()->isMainFrame()) {
521            FrameView* view = node->document().frame()->view();
522            for (size_t i = 0; i < quads.size(); ++i) {
523                FloatQuad& currentQuad = quads[i];
524                currentQuad.setP1(view->contentsToRootView(roundedIntPoint(currentQuad.p1())));
525                currentQuad.setP2(view->contentsToRootView(roundedIntPoint(currentQuad.p2())));
526                currentQuad.setP3(view->contentsToRootView(roundedIntPoint(currentQuad.p3())));
527                currentQuad.setP4(view->contentsToRootView(roundedIntPoint(currentQuad.p4())));
528            }
529        }
530
531        RoundedRect::Radii borderRadii;
532        if (renderer->isBox()) {
533            RenderBox* box = toRenderBox(renderer);
534            borderRadii = box->borderRadii();
535        }
536
537        send(Messages::WebPageProxy::DidGetTapHighlightGeometries(requestID, highlightColor, quads, roundedIntSize(borderRadii.topLeft()), roundedIntSize(borderRadii.topRight()), roundedIntSize(borderRadii.bottomLeft()), roundedIntSize(borderRadii.bottomRight())));
538    }
539}
540
541void WebPage::potentialTapAtPosition(uint64_t requestID, const WebCore::FloatPoint& position)
542{
543    m_potentialTapNode = m_page->mainFrame().nodeRespondingToClickEvents(position, m_potentialTapLocation);
544    sendTapHighlightForNodeIfNecessary(requestID, m_potentialTapNode.get());
545}
546
547void WebPage::commitPotentialTap()
548{
549    if (!m_potentialTapNode || !m_potentialTapNode->renderer()) {
550        commitPotentialTapFailed();
551        return;
552    }
553
554    FloatPoint adjustedPoint;
555    Node* nodeRespondingToClick = m_page->mainFrame().nodeRespondingToClickEvents(m_potentialTapLocation, adjustedPoint);
556
557    if (m_potentialTapNode == nodeRespondingToClick)
558        handleSyntheticClick(nodeRespondingToClick, adjustedPoint);
559    else
560        commitPotentialTapFailed();
561
562    m_potentialTapNode = nullptr;
563    m_potentialTapLocation = FloatPoint();
564}
565
566void WebPage::commitPotentialTapFailed()
567{
568    send(Messages::WebPageProxy::CommitPotentialTapFailed());
569    send(Messages::WebPageProxy::DidNotHandleTapAsClick(roundedIntPoint(m_potentialTapLocation)));
570}
571
572void WebPage::cancelPotentialTap()
573{
574    m_potentialTapNode = nullptr;
575    m_potentialTapLocation = FloatPoint();
576}
577
578void WebPage::tapHighlightAtPosition(uint64_t requestID, const FloatPoint& position)
579{
580    Frame& mainframe = m_page->mainFrame();
581    FloatPoint adjustedPoint;
582    sendTapHighlightForNodeIfNecessary(requestID, mainframe.nodeRespondingToClickEvents(position, adjustedPoint));
583}
584
585void WebPage::inspectorNodeSearchMovedToPosition(const FloatPoint& position)
586{
587    IntPoint adjustedPoint = roundedIntPoint(position);
588    Frame& mainframe = m_page->mainFrame();
589
590    mainframe.eventHandler().mouseMoved(PlatformMouseEvent(adjustedPoint, adjustedPoint, NoButton, PlatformEvent::MouseMoved, 0, false, false, false, false, 0));
591    mainframe.document()->updateStyleIfNeeded();
592}
593
594void WebPage::inspectorNodeSearchEndedAtPosition(const FloatPoint& position)
595{
596    if (Node* node = m_page->mainFrame().deepestNodeAtLocation(position))
597        node->inspect();
598}
599
600void WebPage::blurAssistedNode()
601{
602    if (m_assistedNode && m_assistedNode->isElementNode())
603        toElement(m_assistedNode.get())->blur();
604}
605
606void WebPage::setAssistedNodeValue(const String& value)
607{
608    if (!m_assistedNode)
609        return;
610    if (isHTMLInputElement(m_assistedNode.get())) {
611        HTMLInputElement *element = toHTMLInputElement(m_assistedNode.get());
612        element->setValue(value, DispatchInputAndChangeEvent);
613    }
614    // FIXME: should also handle the case of HTMLSelectElement.
615}
616
617void WebPage::setAssistedNodeValueAsNumber(double value)
618{
619    if (!m_assistedNode)
620        return;
621    if (isHTMLInputElement(m_assistedNode.get())) {
622        HTMLInputElement *element = toHTMLInputElement(m_assistedNode.get());
623        element->setValueAsNumber(value, ASSERT_NO_EXCEPTION, DispatchInputAndChangeEvent);
624    }
625}
626
627void WebPage::setAssistedNodeSelectedIndex(uint32_t index, bool allowMultipleSelection)
628{
629    if (!m_assistedNode || !isHTMLSelectElement(m_assistedNode.get()))
630        return;
631    HTMLSelectElement* select = toHTMLSelectElement(m_assistedNode.get());
632    select->optionSelectedByUser(index, true, allowMultipleSelection);
633}
634
635#if ENABLE(INSPECTOR)
636void WebPage::showInspectorHighlight(const WebCore::Highlight& highlight)
637{
638    send(Messages::WebPageProxy::ShowInspectorHighlight(highlight));
639}
640
641void WebPage::hideInspectorHighlight()
642{
643    send(Messages::WebPageProxy::HideInspectorHighlight());
644}
645
646void WebPage::showInspectorIndication()
647{
648    send(Messages::WebPageProxy::ShowInspectorIndication());
649}
650
651void WebPage::hideInspectorIndication()
652{
653    send(Messages::WebPageProxy::HideInspectorIndication());
654}
655
656void WebPage::enableInspectorNodeSearch()
657{
658    send(Messages::WebPageProxy::EnableInspectorNodeSearch());
659}
660
661void WebPage::disableInspectorNodeSearch()
662{
663    send(Messages::WebPageProxy::DisableInspectorNodeSearch());
664}
665#endif
666
667static FloatQuad innerFrameQuad(Frame* frame, Node* assistedNode)
668{
669    frame->document()->updateLayoutIgnorePendingStylesheets();
670    RenderObject* renderer;
671    if (assistedNode->hasTagName(HTMLNames::textareaTag) || assistedNode->hasTagName(HTMLNames::inputTag) || assistedNode->hasTagName(HTMLNames::selectTag))
672        renderer = assistedNode->renderer();
673    else
674        renderer = assistedNode->rootEditableElement()->renderer();
675
676    if (!renderer)
677        return FloatQuad();
678
679    RenderStyle& style = renderer->style();
680    IntRect boundingBox = renderer->absoluteBoundingBoxRect(true /* use transforms*/);
681
682    boundingBox.move(style.borderLeftWidth(), style.borderTopWidth());
683    boundingBox.setWidth(boundingBox.width() - style.borderLeftWidth() - style.borderRightWidth());
684    boundingBox.setHeight(boundingBox.height() - style.borderBottomWidth() - style.borderTopWidth());
685
686    return FloatQuad(boundingBox);
687}
688
689static IntPoint constrainPoint(const IntPoint& point, Frame* frame, Node* assistedNode)
690{
691    const int DEFAULT_CONSTRAIN_INSET = 2;
692    IntRect innerFrame = innerFrameQuad(frame, assistedNode).enclosingBoundingBox();
693    IntPoint constrainedPoint = point;
694
695    int minX = innerFrame.x() + DEFAULT_CONSTRAIN_INSET;
696    int maxX = innerFrame.maxX() - DEFAULT_CONSTRAIN_INSET;
697    int minY = innerFrame.y() + DEFAULT_CONSTRAIN_INSET;
698    int maxY = innerFrame.maxY() - DEFAULT_CONSTRAIN_INSET;
699
700    if (point.x() < minX)
701        constrainedPoint.setX(minX);
702    else if (point.x() > maxX)
703        constrainedPoint.setX(maxX);
704
705    if (point.y() < minY)
706        constrainedPoint.setY(minY);
707    else if (point.y() >= maxY)
708        constrainedPoint.setY(maxY);
709
710    return constrainedPoint;
711}
712
713static IntRect selectionBoxForRange(WebCore::Range* range)
714{
715    if (!range)
716        return IntRect();
717
718    IntRect boundingRect;
719    Vector<SelectionRect> selectionRects;
720    range->collectSelectionRects(selectionRects);
721    unsigned size = selectionRects.size();
722
723    for (unsigned i = 0; i < size; ++i) {
724        const IntRect &coreRect = selectionRects[i].rect();
725        if (!i)
726            boundingRect = coreRect;
727        else
728            boundingRect.unite(coreRect);
729    }
730    return boundingRect;
731}
732
733PassRefPtr<Range> WebPage::rangeForWebSelectionAtPosition(const IntPoint& point, const VisiblePosition& position, SelectionFlags& flags)
734{
735    HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint((point), HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent | HitTestRequest::AllowChildFrameContent);
736
737    Node* currentNode = result.innerNode();
738    RefPtr<Range> range;
739    FloatRect boundingRectInScrollViewCoordinates;
740
741    if (currentNode->isTextNode()) {
742        range = enclosingTextUnitOfGranularity(position, ParagraphGranularity, DirectionForward);
743        if (!range || range->collapsed(ASSERT_NO_EXCEPTION))
744            range = Range::create(currentNode->document(), position, position);
745        if (!range)
746            return nullptr;
747
748        boundingRectInScrollViewCoordinates = selectionBoxForRange(range.get());
749        boundingRectInScrollViewCoordinates.scale(m_page->pageScaleFactor());
750        if (boundingRectInScrollViewCoordinates.width() > m_blockSelectionDesiredSize.width() && boundingRectInScrollViewCoordinates.height() > m_blockSelectionDesiredSize.height())
751            return wordRangeFromPosition(position);
752
753        currentNode = range->commonAncestorContainer(ASSERT_NO_EXCEPTION);
754    }
755
756    if (!currentNode->isElementNode())
757        currentNode = currentNode->parentElement();
758
759    Node* bestChoice = currentNode;
760    while (currentNode) {
761        boundingRectInScrollViewCoordinates = currentNode->renderer()->absoluteBoundingBoxRect(true);
762        boundingRectInScrollViewCoordinates.scale(m_page->pageScaleFactor());
763        if (boundingRectInScrollViewCoordinates.width() > m_blockSelectionDesiredSize.width() && boundingRectInScrollViewCoordinates.height() > m_blockSelectionDesiredSize.height())
764            break;
765        bestChoice = currentNode;
766        currentNode = currentNode->parentElement();
767    }
768
769    if (!bestChoice)
770        return nullptr;
771
772    RenderObject* renderer = bestChoice->renderer();
773    if (!renderer || renderer->style().userSelect() == SELECT_NONE)
774        return nullptr;
775
776    if (renderer->childrenInline() && (renderer->isRenderBlock() && !toRenderBlock(renderer)->inlineElementContinuation()) && !renderer->isTable()) {
777        range = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionBackward);
778        if (range && !range->collapsed(ASSERT_NO_EXCEPTION))
779            return range;
780    }
781
782    // If all we could find is a block whose height is very close to the height
783    // of the visible area, don't use it.
784    const float adjustmentFactor = .97;
785    boundingRectInScrollViewCoordinates = renderer->absoluteBoundingBoxRect(true);
786
787    if (boundingRectInScrollViewCoordinates.height() > m_page->mainFrame().view()->exposedContentRect().height() * adjustmentFactor)
788        return nullptr;
789
790    flags = IsBlockSelection;
791    range = Range::create(bestChoice->document());
792    range->selectNodeContents(bestChoice, ASSERT_NO_EXCEPTION);
793    return range->collapsed(ASSERT_NO_EXCEPTION) ? nullptr : range;
794}
795
796PassRefPtr<Range> WebPage::rangeForBlockAtPoint(const IntPoint& point)
797{
798    HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint((point), HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent | HitTestRequest::IgnoreClipping);
799
800    Node* currentNode = result.innerNode();
801    RefPtr<Range> range;
802
803    if (currentNode->isTextNode()) {
804        range = enclosingTextUnitOfGranularity(m_page->focusController().focusedOrMainFrame().visiblePositionForPoint(point), ParagraphGranularity, DirectionForward);
805        if (range && !range->collapsed(ASSERT_NO_EXCEPTION))
806            return range;
807    }
808
809    if (!currentNode->isElementNode())
810        currentNode = currentNode->parentElement();
811
812    if (!currentNode)
813        return nullptr;
814
815    range = Range::create(currentNode->document());
816    range->selectNodeContents(currentNode, ASSERT_NO_EXCEPTION);
817    return range;
818}
819
820void WebPage::selectWithGesture(const IntPoint& point, uint32_t granularity, uint32_t gestureType, uint32_t gestureState, uint64_t callbackID)
821{
822    Frame& frame = m_page->focusController().focusedOrMainFrame();
823    IntPoint adjustedPoint(frame.view()->rootViewToContents(point));
824
825    IntPoint constrainedPoint = m_assistedNode ? constrainPoint(adjustedPoint, &frame, m_assistedNode.get()) : adjustedPoint;
826    VisiblePosition position = frame.visiblePositionForPoint(constrainedPoint);
827    if (position.isNull()) {
828        send(Messages::WebPageProxy::GestureCallback(point, gestureType, gestureState, 0, callbackID));
829        return;
830    }
831    RefPtr<Range> range;
832    SelectionFlags flags = None;
833    GestureRecognizerState wkGestureState = static_cast<GestureRecognizerState>(gestureState);
834    switch (static_cast<GestureType>(gestureType)) {
835    case GestureType::PhraseBoundary:
836    {
837        if (!frame.editor().hasComposition())
838            break;
839        RefPtr<Range> markedRange = frame.editor().compositionRange();
840        if (position < markedRange->startPosition())
841            position = markedRange->startPosition();
842        if (position > markedRange->endPosition())
843            position = markedRange->endPosition();
844        if (wkGestureState != GestureRecognizerState::Began)
845            flags = distanceBetweenPositions(markedRange->startPosition(), frame.selection().selection().start()) != distanceBetweenPositions(markedRange->startPosition(), position) ? PhraseBoundaryChanged : None;
846        else
847            flags = PhraseBoundaryChanged;
848        range = Range::create(*frame.document(), position, position);
849    }
850        break;
851
852    case GestureType::OneFingerTap:
853    {
854        VisiblePosition result;
855        // move the the position at the end of the word
856        if (atBoundaryOfGranularity(position, LineGranularity, DirectionForward)) {
857            // Don't cross line boundaries.
858            result = position;
859        } else if (withinTextUnitOfGranularity(position, WordGranularity, DirectionForward)) {
860            // The position lies within a word.
861            RefPtr<Range> wordRange = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionForward);
862
863            result = wordRange->startPosition();
864            if (distanceBetweenPositions(position, result) > 1)
865                result = wordRange->endPosition();
866        } else if (atBoundaryOfGranularity(position, WordGranularity, DirectionBackward)) {
867            // The position is at the end of a word.
868            result = position;
869        } else {
870            // The position is not within a word.
871            // Go to the next boundary.
872            result = positionOfNextBoundaryOfGranularity(position, WordGranularity, DirectionForward);
873
874            // If there is no such boundary we go to the end of the element.
875            if (result.isNull())
876                result = endOfEditableContent(position);
877        }
878        if (result.isNotNull())
879            range = Range::create(*frame.document(), result, result);
880    }
881        break;
882
883    case GestureType::Loupe:
884        if (position.rootEditableElement())
885            range = Range::create(*frame.document(), position, position);
886        else
887            range = wordRangeFromPosition(position);
888        break;
889
890    case GestureType::TapAndAHalf:
891        switch (wkGestureState) {
892        case GestureRecognizerState::Began:
893            range = wordRangeFromPosition(position);
894            m_currentWordRange = Range::create(*frame.document(), range->startPosition(), range->endPosition());
895            break;
896        case GestureRecognizerState::Changed:
897            range = Range::create(*frame.document(), m_currentWordRange->startPosition(), m_currentWordRange->endPosition());
898            if (position < range->startPosition())
899                range->setStart(position.deepEquivalent(), ASSERT_NO_EXCEPTION);
900            if (position > range->endPosition())
901                range->setEnd(position.deepEquivalent(), ASSERT_NO_EXCEPTION);
902            break;
903        case GestureRecognizerState::Ended:
904        case GestureRecognizerState::Cancelled:
905            m_currentWordRange = nullptr;
906            break;
907        case GestureRecognizerState::Failed:
908        case GestureRecognizerState::Possible:
909            ASSERT_NOT_REACHED();
910        }
911        break;
912
913    case GestureType::OneFingerDoubleTap:
914        if (atBoundaryOfGranularity(position, LineGranularity, DirectionForward)) {
915            // Double-tap at end of line only places insertion point there.
916            // This helps to get the callout for pasting at ends of lines,
917            // paragraphs, and documents.
918            range = Range::create(*frame.document(), position, position);
919         } else
920            range = wordRangeFromPosition(position);
921        break;
922
923    case GestureType::TwoFingerSingleTap:
924        // Single tap with two fingers selects the entire paragraph.
925        range = enclosingTextUnitOfGranularity(position, ParagraphGranularity, DirectionForward);
926        break;
927
928    case GestureType::OneFingerTripleTap:
929        if (atBoundaryOfGranularity(position, LineGranularity, DirectionForward)) {
930            // Triple-tap at end of line only places insertion point there.
931            // This helps to get the callout for pasting at ends of lines,
932            // paragraphs, and documents.
933            range = Range::create(*frame.document(), position, position);
934        } else
935            range = enclosingTextUnitOfGranularity(position, ParagraphGranularity, DirectionForward);
936        break;
937
938    case GestureType::MakeWebSelection:
939        if (wkGestureState == GestureRecognizerState::Began) {
940            m_blockSelectionDesiredSize.setWidth(blockSelectionStartWidth);
941            m_blockSelectionDesiredSize.setHeight(blockSelectionStartHeight);
942            m_currentBlockSelection = nullptr;
943        }
944        range = rangeForWebSelectionAtPosition(point, position, flags);
945        if (wkGestureState == GestureRecognizerState::Ended && flags & IsBlockSelection)
946            m_currentBlockSelection = range;
947        break;
948
949    default:
950        break;
951    }
952    if (range)
953        frame.selection().setSelectedRange(range.get(), position.affinity(), true);
954
955    send(Messages::WebPageProxy::GestureCallback(point, gestureType, gestureState, static_cast<uint32_t>(flags), callbackID));
956}
957
958static PassRefPtr<Range> rangeForPosition(Frame* frame, const VisiblePosition& position, bool baseIsStart)
959{
960    RefPtr<Range> range;
961    VisiblePosition result = position;
962
963    if (baseIsStart) {
964        VisiblePosition selectionStart = frame->selection().selection().visibleStart();
965        bool wouldFlip = position <= selectionStart;
966
967        if (wouldFlip)
968            result = selectionStart.next();
969
970        if (result.isNotNull())
971            range = Range::create(*frame->document(), selectionStart, result);
972    } else {
973        VisiblePosition selectionEnd = frame->selection().selection().visibleEnd();
974        bool wouldFlip = position >= selectionEnd;
975
976        if (wouldFlip)
977            result = selectionEnd.previous();
978
979        if (result.isNotNull())
980            range = Range::create(*frame->document(), result, selectionEnd);
981    }
982
983    return range.release();
984}
985
986static PassRefPtr<Range> rangeAtWordBoundaryForPosition(Frame* frame, const VisiblePosition& position, bool baseIsStart, SelectionDirection direction)
987{
988    SelectionDirection sameDirection = baseIsStart ? DirectionForward : DirectionBackward;
989    SelectionDirection oppositeDirection = baseIsStart ? DirectionBackward : DirectionForward;
990    VisiblePosition base = baseIsStart ? frame->selection().selection().visibleStart() : frame->selection().selection().visibleEnd();
991    VisiblePosition extent = baseIsStart ? frame->selection().selection().visibleEnd() : frame->selection().selection().visibleStart();
992    VisiblePosition initialExtent = position;
993
994    if (atBoundaryOfGranularity(extent, WordGranularity, sameDirection)) {
995        // This is a word boundary. Leave selection where it is.
996        return 0;
997    }
998
999    if (atBoundaryOfGranularity(extent, WordGranularity, oppositeDirection)) {
1000        // This is a word boundary in the wrong direction. Nudge the selection to a character before proceeding.
1001        extent = baseIsStart ? extent.previous() : extent.next();
1002    }
1003
1004    // Extend to the boundary of the word.
1005
1006    VisiblePosition wordBoundary = positionOfNextBoundaryOfGranularity(extent, WordGranularity, sameDirection);
1007    if (wordBoundary.isNotNull()
1008        && atBoundaryOfGranularity(wordBoundary, WordGranularity, sameDirection)
1009        && initialExtent != wordBoundary) {
1010        extent = wordBoundary;
1011        return (base < extent) ? Range::create(*frame->document(), base, extent) : Range::create(*frame->document(), extent, base);
1012    }
1013    // Conversely, if the initial extent equals the current word boundary, then
1014    // run the rest of this function to see if the selection should extend
1015    // the other direction to the other word.
1016
1017    // If this is where the extent was initially, then iterate in the other direction in the document until we hit the next word.
1018    while (extent.isNotNull()
1019           && !atBoundaryOfGranularity(extent, WordGranularity, sameDirection)
1020           && extent != base
1021           && !atBoundaryOfGranularity(extent, LineBoundary, sameDirection)
1022           && !atBoundaryOfGranularity(extent, LineBoundary, oppositeDirection)) {
1023        extent = baseIsStart ? extent.next() : extent.previous();
1024    }
1025
1026    // Don't let the smart extension make the extent equal the base.
1027    // Expand out to word boundary.
1028    if (extent.isNull() || extent == base)
1029        extent = wordBoundary;
1030    if (extent.isNull())
1031        return 0;
1032
1033    return (base < extent) ? Range::create(*frame->document(), base, extent) : Range::create(*frame->document(), extent, base);
1034}
1035
1036static const int maxHitTests = 10;
1037
1038static inline float distanceBetweenRectsForPosition(IntRect& first, IntRect& second, SelectionHandlePosition handlePosition)
1039{
1040    switch (handlePosition) {
1041    case SelectionHandlePosition::Top:
1042        return abs(first.y() - second.y());
1043    case SelectionHandlePosition::Right:
1044        return abs(first.maxX() - second.maxX());
1045    case SelectionHandlePosition::Bottom:
1046        return abs(first.maxY() - second.maxY());
1047    case SelectionHandlePosition::Left:
1048        return abs(first.x() - second.x());
1049    }
1050}
1051
1052static inline bool rectsEssentiallyTheSame(IntRect& first, IntRect& second, float allowablePercentDifference)
1053{
1054    const float minMagnitudeRatio = 1.0 - allowablePercentDifference;
1055    const float maxDisplacementRatio = allowablePercentDifference;
1056
1057    float xOriginShiftRatio = abs(first.x() - second.x())/std::min(first.width(), second.width());
1058    float yOriginShiftRatio = abs(first.y() - second.y())/std::min(first.height(), second.height());
1059
1060    float widthRatio = std::min(first.width() / second.width(), second.width() / first.width());
1061    float heightRatio = std::min(first.height() / second.height(), second.height() / first.height());
1062    return ((widthRatio > minMagnitudeRatio && xOriginShiftRatio < maxDisplacementRatio)
1063        && (heightRatio > minMagnitudeRatio && yOriginShiftRatio < maxDisplacementRatio));
1064}
1065
1066static inline bool containsRange(Range* first, Range* second)
1067{
1068    if (!first || !second)
1069        return false;
1070    return (first->commonAncestorContainer(ASSERT_NO_EXCEPTION)->ownerDocument() == second->commonAncestorContainer(ASSERT_NO_EXCEPTION)->ownerDocument()
1071        && first->compareBoundaryPoints(Range::START_TO_START, second, ASSERT_NO_EXCEPTION) <= 0
1072        && first->compareBoundaryPoints(Range::END_TO_END, second, ASSERT_NO_EXCEPTION) >= 0);
1073}
1074
1075static inline RefPtr<Range> unionDOMRanges(Range* rangeA, Range* rangeB)
1076{
1077    if (!rangeB)
1078        return rangeA;
1079    if (!rangeA)
1080        return rangeB;
1081
1082    Range* start = rangeA->compareBoundaryPoints(Range::START_TO_START, rangeB, ASSERT_NO_EXCEPTION) <= 0 ? rangeA : rangeB;
1083    Range* end = rangeA->compareBoundaryPoints(Range::END_TO_END, rangeB, ASSERT_NO_EXCEPTION) <= 0 ? rangeB : rangeA;
1084
1085    return Range::create(rangeA->ownerDocument(), start->startContainer(), start->startOffset(), end->endContainer(), end->endOffset());
1086}
1087
1088static inline IntPoint computeEdgeCenter(const IntRect& box, SelectionHandlePosition handlePosition)
1089{
1090    switch (handlePosition) {
1091    case SelectionHandlePosition::Top:
1092        return IntPoint(box.x() + box.width() / 2, box.y());
1093    case SelectionHandlePosition::Right:
1094        return IntPoint(box.maxX(), box.y() + box.height() / 2);
1095    case SelectionHandlePosition::Bottom:
1096        return IntPoint(box.x() + box.width() / 2, box.maxY());
1097    case SelectionHandlePosition::Left:
1098        return IntPoint(box.x(), box.y() + box.height() / 2);
1099    }
1100}
1101
1102PassRefPtr<Range> WebPage::expandedRangeFromHandle(Range* currentRange, SelectionHandlePosition handlePosition)
1103{
1104    IntRect currentBox = selectionBoxForRange(currentRange);
1105    IntPoint edgeCenter = computeEdgeCenter(currentBox, handlePosition);
1106    static const float maxDistance = 1000;
1107    const float multiple = powf(maxDistance, 1.0/(maxHitTests - 1));
1108    float distance = 1;
1109
1110    RefPtr<Range> bestRange;
1111    IntRect bestRect;
1112
1113    while (distance < maxDistance) {
1114        if (bestRange) {
1115            if (distanceBetweenRectsForPosition(bestRect, currentBox, handlePosition) < distance) {
1116                // Break early, we're unlikely to do any better.
1117                break;
1118            }
1119        }
1120
1121        IntPoint testPoint = edgeCenter;
1122        switch (handlePosition) {
1123        case SelectionHandlePosition::Top:
1124            testPoint.move(0, -distance);
1125            break;
1126        case SelectionHandlePosition::Right:
1127            testPoint.move(distance, 0);
1128            break;
1129        case SelectionHandlePosition::Bottom:
1130            testPoint.move(0, distance);
1131            break;
1132        case SelectionHandlePosition::Left:
1133            testPoint.move(-distance, 0);
1134            break;
1135        }
1136
1137        distance = ceilf(distance * multiple);
1138
1139        RefPtr<Range> newRange;
1140        RefPtr<Range> rangeAtPosition = rangeForBlockAtPoint(testPoint);
1141        if (&currentRange->ownerDocument() != &rangeAtPosition->ownerDocument())
1142            continue;
1143
1144        if (containsRange(rangeAtPosition.get(), currentRange))
1145            newRange = rangeAtPosition;
1146        else if (containsRange(currentRange, rangeAtPosition.get()))
1147            newRange = currentRange;
1148        else
1149            newRange = unionDOMRanges(currentRange, rangeAtPosition.get());
1150
1151        IntRect copyRect = selectionBoxForRange(newRange.get());
1152
1153        // Is it different and bigger than the current?
1154        bool isBetterChoice = !(rectsEssentiallyTheSame(copyRect, currentBox, .05));
1155        if (isBetterChoice) {
1156            switch (handlePosition) {
1157            case SelectionHandlePosition::Top:
1158            case SelectionHandlePosition::Bottom:
1159                isBetterChoice = (copyRect.height() > currentBox.height());
1160                break;
1161            case SelectionHandlePosition::Right:
1162            case SelectionHandlePosition::Left:
1163                isBetterChoice = (copyRect.width() > currentBox.width());
1164                break;
1165            }
1166
1167        }
1168
1169        if (bestRange && isBetterChoice) {
1170            // Furtherore, is it smaller than the best we've found so far?
1171            switch (handlePosition) {
1172            case SelectionHandlePosition::Top:
1173            case SelectionHandlePosition::Bottom:
1174                isBetterChoice = (copyRect.height() < bestRect.height());
1175                break;
1176            case SelectionHandlePosition::Right:
1177            case SelectionHandlePosition::Left:
1178                isBetterChoice = (copyRect.width() < bestRect.width());
1179                break;
1180            }
1181        }
1182
1183        if (isBetterChoice) {
1184            bestRange = newRange;
1185            bestRect = copyRect;
1186        }
1187    }
1188
1189    if (bestRange)
1190        return bestRange;
1191
1192    return currentRange;
1193}
1194
1195PassRefPtr<Range> WebPage::contractedRangeFromHandle(Range* currentRange, SelectionHandlePosition handlePosition, SelectionFlags& flags)
1196{
1197    // Shrinking with a base and extent will always give better results. If we only have a single element,
1198    // see if we can break that down to a base and extent. Shrinking base and extent is comparatively straightforward.
1199    // Shrinking down to another element is unlikely to move just one edge, but we can try that as a fallback.
1200
1201    IntRect currentBox = selectionBoxForRange(currentRange);
1202    IntPoint edgeCenter = computeEdgeCenter(currentBox, handlePosition);
1203    flags = IsBlockSelection;
1204
1205    float maxDistance;
1206
1207    switch (handlePosition) {
1208    case SelectionHandlePosition::Top:
1209    case SelectionHandlePosition::Bottom:
1210        maxDistance = currentBox.height();
1211        break;
1212    case SelectionHandlePosition::Right:
1213    case SelectionHandlePosition::Left:
1214        maxDistance = currentBox.width();
1215        break;
1216    }
1217
1218    const float multiple = powf(maxDistance - 1, 1.0/(maxHitTests - 1));
1219    float distance = 1;
1220    RefPtr<Range> bestRange;
1221    IntRect bestRect;
1222
1223    while (distance < maxDistance) {
1224        if (bestRange) {
1225            float shrankDistance;
1226            switch (handlePosition) {
1227            case SelectionHandlePosition::Top:
1228            case SelectionHandlePosition::Bottom:
1229                shrankDistance = abs(currentBox.height() - bestRect.height());
1230                break;
1231            case SelectionHandlePosition::Right:
1232            case SelectionHandlePosition::Left:
1233                shrankDistance = abs(currentBox.width() - bestRect.width());
1234                break;
1235            }
1236            if (shrankDistance > distance) {
1237                // Certainly not going to do any better than that.
1238                break;
1239            }
1240        }
1241
1242        IntPoint testPoint = edgeCenter;
1243        switch (handlePosition) {
1244        case SelectionHandlePosition::Top:
1245            testPoint.move(0, distance);
1246            break;
1247        case SelectionHandlePosition::Right:
1248            testPoint.move(-distance, 0);
1249            break;
1250        case SelectionHandlePosition::Bottom:
1251            testPoint.move(0, -distance);
1252            break;
1253        case SelectionHandlePosition::Left:
1254            testPoint.move(distance, 0);
1255            break;
1256        }
1257
1258        distance *= multiple;
1259
1260        RefPtr<Range> newRange = rangeForBlockAtPoint(testPoint);
1261        if (&newRange->ownerDocument() != &currentRange->ownerDocument())
1262            continue;
1263
1264        if (handlePosition == SelectionHandlePosition::Top || handlePosition == SelectionHandlePosition::Left)
1265            newRange = Range::create(newRange->startContainer()->document(), newRange->endPosition(), currentRange->endPosition());
1266        else
1267            newRange = Range::create(newRange->startContainer()->document(), currentRange->startPosition(), newRange->startPosition());
1268
1269        IntRect copyRect = selectionBoxForRange(newRange.get());
1270        if (copyRect.isEmpty()) {
1271            bestRange = rangeForBlockAtPoint(testPoint);
1272            break;
1273        }
1274        bool isBetterChoice;
1275        switch (handlePosition) {
1276        case SelectionHandlePosition::Top:
1277        case SelectionHandlePosition::Bottom:
1278            isBetterChoice = (copyRect.height() < currentBox.height());
1279            break;
1280        case SelectionHandlePosition::Left:
1281        case SelectionHandlePosition::Right:
1282            isBetterChoice = (copyRect.width() > bestRect.width());
1283            break;
1284        }
1285
1286        isBetterChoice = isBetterChoice && !areRangesEqual(newRange.get(), currentRange);
1287        if (bestRange && isBetterChoice) {
1288            switch (handlePosition) {
1289            case SelectionHandlePosition::Top:
1290            case SelectionHandlePosition::Bottom:
1291                isBetterChoice = (copyRect.height() > bestRect.height());
1292                break;
1293            case SelectionHandlePosition::Left:
1294            case SelectionHandlePosition::Right:
1295                isBetterChoice = (copyRect.width() > bestRect.width());
1296                break;
1297            }
1298        }
1299        if (isBetterChoice) {
1300            bestRange = newRange;
1301            bestRect = copyRect;
1302        }
1303
1304    }
1305
1306    if (!bestRange)
1307        bestRange = currentRange;
1308
1309    // If we can shrink down to text only, the only reason we wouldn't is that
1310    // there are multiple sub-element blocks beneath us.  If we didn't find
1311    // multiple sub-element blocks, don't shrink to a sub-element block.
1312
1313    Node* node = bestRange->commonAncestorContainer(ASSERT_NO_EXCEPTION);
1314    if (!node->isElementNode())
1315        node = node->parentElement();
1316
1317    RenderObject* renderer = node->renderer();
1318    if (renderer && renderer->childrenInline() && (renderer->isRenderBlock() && !toRenderBlock(renderer)->inlineElementContinuation()) && !renderer->isTable())
1319        flags = None;
1320
1321    return bestRange;
1322}
1323
1324void WebPage::computeExpandAndShrinkThresholdsForHandle(const IntPoint& point, SelectionHandlePosition handlePosition, float& growThreshold, float& shrinkThreshold)
1325{
1326    Frame& frame = m_page->focusController().focusedOrMainFrame();
1327    RefPtr<Range> currentRange = m_currentBlockSelection ? m_currentBlockSelection.get() : frame.selection().selection().toNormalizedRange();
1328    ASSERT(currentRange);
1329
1330    RefPtr<Range> expandedRange = expandedRangeFromHandle(currentRange.get(), handlePosition);
1331    SelectionFlags flags;
1332    RefPtr<Range> contractedRange = contractedRangeFromHandle(currentRange.get(), handlePosition, flags);
1333
1334    IntRect currentBounds = selectionBoxForRange(currentRange.get());
1335    IntRect expandedBounds = selectionBoxForRange(expandedRange.get());
1336    IntRect contractedBounds = selectionBoxForRange(contractedRange.get());
1337
1338    float current;
1339    float expanded;
1340    float contracted;
1341    float maxThreshold;
1342    float minThreshold;
1343
1344    switch (handlePosition) {
1345    case SelectionHandlePosition::Top: {
1346        current = currentBounds.y();
1347        expanded = expandedBounds.y();
1348        contracted = contractedBounds.y();
1349        maxThreshold = FLT_MIN;
1350        minThreshold = FLT_MAX;
1351        break;
1352    }
1353    case SelectionHandlePosition::Right: {
1354        current = currentBounds.maxX();
1355        expanded = expandedBounds.maxX();
1356        contracted = contractedBounds.maxX();
1357        maxThreshold = FLT_MAX;
1358        minThreshold = FLT_MIN;
1359        break;
1360    }
1361    case SelectionHandlePosition::Bottom: {
1362        current = currentBounds.maxY();
1363        expanded = expandedBounds.maxY();
1364        contracted = contractedBounds.maxY();
1365        maxThreshold = FLT_MAX;
1366        minThreshold = FLT_MIN;
1367        break;
1368    }
1369    case SelectionHandlePosition::Left: {
1370        current = currentBounds.x();
1371        expanded = expandedBounds.x();
1372        contracted = contractedBounds.x();
1373        maxThreshold = FLT_MIN;
1374        minThreshold = FLT_MAX;
1375        break;
1376    }
1377    }
1378
1379    static const float fractionToGrow = 0.3;
1380
1381    growThreshold = current + (expanded - current) * fractionToGrow;
1382    shrinkThreshold = current + (contracted - current) * (1 - fractionToGrow);
1383    if (areRangesEqual(expandedRange.get(), currentRange.get()))
1384        growThreshold = maxThreshold;
1385
1386    if (flags & IsBlockSelection && areRangesEqual(contractedRange.get(), currentRange.get()))
1387        shrinkThreshold = minThreshold;
1388}
1389
1390static inline bool shouldExpand(SelectionHandlePosition handlePosition, const IntRect& rect, const IntPoint& point)
1391{
1392    switch (handlePosition) {
1393    case SelectionHandlePosition::Top:
1394        return (point.y() < rect.y());
1395    case SelectionHandlePosition::Left:
1396        return (point.x() < rect.x());
1397    case SelectionHandlePosition::Right:
1398        return (point.x() > rect.maxX());
1399    case SelectionHandlePosition::Bottom:
1400        return (point.y() > rect.maxY());
1401    }
1402}
1403
1404PassRefPtr<WebCore::Range> WebPage::changeBlockSelection(const IntPoint& point, SelectionHandlePosition handlePosition, float& growThreshold, float& shrinkThreshold, SelectionFlags& flags)
1405{
1406    Frame& frame = m_page->focusController().focusedOrMainFrame();
1407    RefPtr<Range> currentRange = m_currentBlockSelection ? m_currentBlockSelection.get() : frame.selection().selection().toNormalizedRange();
1408    RefPtr<Range> newRange = shouldExpand(handlePosition, selectionBoxForRange(currentRange.get()), point) ? expandedRangeFromHandle(currentRange.get(), handlePosition) : contractedRangeFromHandle(currentRange.get(), handlePosition, flags);
1409
1410    if (newRange) {
1411        m_currentBlockSelection = newRange;
1412        frame.selection().setSelectedRange(newRange.get(), VP_DEFAULT_AFFINITY, true);
1413    }
1414
1415    computeExpandAndShrinkThresholdsForHandle(point, handlePosition, growThreshold, shrinkThreshold);
1416    return newRange;
1417}
1418
1419void WebPage::updateBlockSelectionWithTouch(const IntPoint& point, uint32_t touch, uint32_t handlePosition)
1420{
1421    Frame& frame = m_page->focusController().focusedOrMainFrame();
1422    IntPoint adjustedPoint = frame.view()->rootViewToContents(point);
1423
1424    float growThreshold = 0;
1425    float shrinkThreshold = 0;
1426    SelectionFlags flags = IsBlockSelection;
1427
1428    switch (static_cast<SelectionTouch>(touch)) {
1429    case SelectionTouch::Started:
1430        computeExpandAndShrinkThresholdsForHandle(adjustedPoint, static_cast<SelectionHandlePosition>(handlePosition), growThreshold, shrinkThreshold);
1431        break;
1432    case SelectionTouch::Ended:
1433        break;
1434    case SelectionTouch::Moved:
1435        changeBlockSelection(adjustedPoint, static_cast<SelectionHandlePosition>(handlePosition), growThreshold, shrinkThreshold, flags);
1436        break;
1437    default:
1438        return;
1439    }
1440
1441    send(Messages::WebPageProxy::DidUpdateBlockSelectionWithTouch(touch, static_cast<uint32_t>(flags), growThreshold, shrinkThreshold));
1442}
1443
1444void WebPage::clearSelection()
1445{
1446    m_currentBlockSelection = nullptr;
1447    m_page->focusController().focusedOrMainFrame().selection().clear();
1448}
1449
1450void WebPage::updateSelectionWithTouches(const IntPoint& point, uint32_t touches, bool baseIsStart, uint64_t callbackID)
1451{
1452    Frame& frame = m_page->focusController().focusedOrMainFrame();
1453    VisiblePosition position = frame.visiblePositionForPoint(frame.view()->rootViewToContents(point));
1454    if (position.isNull()) {
1455        send(Messages::WebPageProxy::TouchesCallback(point, touches, callbackID));
1456        return;
1457    }
1458
1459    RefPtr<Range> range;
1460    VisiblePosition result;
1461
1462    switch (static_cast<SelectionTouch>(touches)) {
1463    case SelectionTouch::Started:
1464    case SelectionTouch::EndedNotMoving:
1465        break;
1466
1467    case SelectionTouch::Ended:
1468        if (frame.selection().selection().isContentEditable()) {
1469            result = closestWordBoundaryForPosition(position);
1470            if (result.isNotNull())
1471                range = Range::create(*frame.document(), result, result);
1472        } else
1473            range = rangeForPosition(&frame, position, baseIsStart);
1474        break;
1475
1476    case SelectionTouch::EndedMovingForward:
1477        range = rangeAtWordBoundaryForPosition(&frame, position, baseIsStart, DirectionForward);
1478        break;
1479
1480    case SelectionTouch::EndedMovingBackward:
1481        range = rangeAtWordBoundaryForPosition(&frame, position, baseIsStart, DirectionBackward);
1482        break;
1483
1484    case SelectionTouch::Moved:
1485        range = rangeForPosition(&frame, position, baseIsStart);
1486        break;
1487    }
1488    if (range)
1489        frame.selection().setSelectedRange(range.get(), position.affinity(), true);
1490
1491    send(Messages::WebPageProxy::TouchesCallback(point, touches, callbackID));
1492}
1493
1494void WebPage::selectWithTwoTouches(const WebCore::IntPoint& from, const WebCore::IntPoint& to, uint32_t gestureType, uint32_t gestureState, uint64_t callbackID)
1495{
1496    Frame& frame = m_page->focusController().focusedOrMainFrame();
1497    VisiblePosition fromPosition = frame.visiblePositionForPoint(frame.view()->rootViewToContents(from));
1498    VisiblePosition toPosition = frame.visiblePositionForPoint(frame.view()->rootViewToContents(to));
1499    RefPtr<Range> range;
1500    if (fromPosition.isNotNull() && toPosition.isNotNull()) {
1501        if (fromPosition < toPosition)
1502            range = Range::create(*frame.document(), fromPosition, toPosition);
1503        else
1504            range = Range::create(*frame.document(), toPosition, fromPosition);
1505        frame.selection().setSelectedRange(range.get(), fromPosition.affinity(), true);
1506    }
1507
1508    // We can use the same callback for the gestures with one point.
1509    send(Messages::WebPageProxy::GestureCallback(from, gestureType, gestureState, 0, callbackID));
1510}
1511
1512void WebPage::extendSelection(uint32_t granularity)
1513{
1514    Frame& frame = m_page->focusController().focusedOrMainFrame();
1515    // For the moment we handle only WordGranularity.
1516    if (granularity != WordGranularity || !frame.selection().isCaret())
1517        return;
1518
1519    VisiblePosition position = frame.selection().selection().start();
1520    frame.selection().setSelectedRange(wordRangeFromPosition(position).get(), position.affinity(), true);
1521}
1522
1523void WebPage::selectWordBackward()
1524{
1525    Frame& frame = m_page->focusController().focusedOrMainFrame();
1526    if (!frame.selection().isCaret())
1527        return;
1528
1529    VisiblePosition position = frame.selection().selection().start();
1530    VisiblePosition startPosition = positionOfNextBoundaryOfGranularity(position, WordGranularity, DirectionBackward);
1531    if (startPosition.isNotNull() && startPosition != position)
1532        frame.selection().setSelectedRange(Range::create(*frame.document(), startPosition, position).get(), position.affinity(), true);
1533}
1534
1535void WebPage::moveSelectionByOffset(int32_t offset, uint64_t callbackID)
1536{
1537    Frame& frame = m_page->focusController().focusedOrMainFrame();
1538
1539    VisiblePosition startPosition = frame.selection().selection().end();
1540    if (startPosition.isNull())
1541        return;
1542    SelectionDirection direction = offset < 0 ? DirectionBackward : DirectionForward;
1543    VisiblePosition position = startPosition;
1544    for (int i = 0; i < abs(offset); ++i) {
1545        position = positionOfNextBoundaryOfGranularity(position, CharacterGranularity, direction);
1546        if (position.isNull())
1547            break;
1548    }
1549    if (position.isNotNull() && startPosition != position)
1550        frame.selection().setSelectedRange(Range::create(*frame.document(), position, position).get(), position.affinity(), true);
1551    send(Messages::WebPageProxy::VoidCallback(callbackID));
1552}
1553
1554void WebPage::convertSelectionRectsToRootView(FrameView* view, Vector<SelectionRect>& selectionRects)
1555{
1556    for (size_t i = 0; i < selectionRects.size(); ++i) {
1557        SelectionRect& currentRect = selectionRects[i];
1558        currentRect.setRect(view->contentsToRootView(currentRect.rect()));
1559    }
1560}
1561
1562void WebPage::requestDictationContext(uint64_t callbackID)
1563{
1564    Frame& frame = m_page->focusController().focusedOrMainFrame();
1565    VisiblePosition startPosition = frame.selection().selection().start();
1566    VisiblePosition endPosition = frame.selection().selection().end();
1567    const unsigned dictationContextWordCount = 5;
1568
1569    String selectedText;
1570    if (frame.selection().isRange())
1571        selectedText = plainTextReplacingNoBreakSpace(frame.selection().selection().toNormalizedRange().get());
1572
1573    String contextBefore;
1574    if (startPosition != startOfEditableContent(startPosition)) {
1575        VisiblePosition currentPosition = startPosition;
1576        VisiblePosition lastPosition = startPosition;
1577        for (unsigned i = 0; i < dictationContextWordCount; ++i) {
1578            currentPosition = startOfWord(positionOfNextBoundaryOfGranularity(lastPosition, WordGranularity, DirectionBackward));
1579            if (currentPosition.isNull())
1580                break;
1581            lastPosition = currentPosition;
1582        }
1583        if (lastPosition.isNotNull() && lastPosition != startPosition)
1584            contextBefore = plainTextReplacingNoBreakSpace(Range::create(*frame.document(), lastPosition, startPosition).get());
1585    }
1586
1587    String contextAfter;
1588    if (endPosition != endOfEditableContent(endPosition)) {
1589        VisiblePosition currentPosition = endPosition;
1590        VisiblePosition lastPosition = endPosition;
1591        for (unsigned i = 0; i < dictationContextWordCount; ++i) {
1592            currentPosition = endOfWord(positionOfNextBoundaryOfGranularity(lastPosition, WordGranularity, DirectionForward));
1593            if (currentPosition.isNull())
1594                break;
1595            lastPosition = currentPosition;
1596        }
1597        if (lastPosition.isNotNull() && lastPosition != endPosition)
1598            contextAfter = plainTextReplacingNoBreakSpace(Range::create(*frame.document(), endPosition, lastPosition).get());
1599    }
1600
1601    send(Messages::WebPageProxy::DictationContextCallback(selectedText, contextBefore, contextAfter, callbackID));
1602}
1603
1604void WebPage::replaceSelectedText(const String& oldText, const String& newText)
1605{
1606    Frame& frame = m_page->focusController().focusedOrMainFrame();
1607    RefPtr<Range> wordRange = frame.selection().isCaret() ? wordRangeFromPosition(frame.selection().selection().start()) : frame.selection().toNormalizedRange();
1608    if (plainTextReplacingNoBreakSpace(wordRange.get()) != oldText)
1609        return;
1610
1611    frame.editor().setIgnoreCompositionSelectionChange(true);
1612    frame.selection().setSelectedRange(wordRange.get(), UPSTREAM, true);
1613    frame.editor().insertText(newText, 0);
1614    frame.editor().setIgnoreCompositionSelectionChange(false);
1615}
1616
1617void WebPage::replaceDictatedText(const String& oldText, const String& newText)
1618{
1619    Frame& frame = m_page->focusController().focusedOrMainFrame();
1620    if (frame.selection().isNone())
1621        return;
1622
1623    if (frame.selection().isRange()) {
1624        frame.editor().deleteSelectionWithSmartDelete(false);
1625        return;
1626    }
1627    VisiblePosition position = frame.selection().selection().start();
1628    for (size_t i = 0; i < oldText.length(); ++i)
1629        position = position.previous();
1630    if (position.isNull())
1631        position = startOfDocument(static_cast<Node*>(frame.document()->documentElement()));
1632    RefPtr<Range> range = Range::create(*frame.document(), position, frame.selection().selection().start());
1633
1634    if (plainTextReplacingNoBreakSpace(range.get()) != oldText)
1635        return;
1636
1637    // We don't want to notify the client that the selection has changed until we are done inserting the new text.
1638    frame.editor().setIgnoreCompositionSelectionChange(true);
1639    frame.selection().setSelectedRange(range.get(), UPSTREAM, true);
1640    frame.editor().insertText(newText, 0);
1641    frame.editor().setIgnoreCompositionSelectionChange(false);
1642}
1643
1644void WebPage::requestAutocorrectionData(const String& textForAutocorrection, uint64_t callbackID)
1645{
1646    Frame& frame = m_page->focusController().focusedOrMainFrame();
1647    if (!frame.selection().isCaret()) {
1648        send(Messages::WebPageProxy::AutocorrectionDataCallback(Vector<FloatRect>(), String(), 0, 0, callbackID));
1649        return;
1650    }
1651
1652    VisiblePosition position = frame.selection().selection().start();
1653    RefPtr<Range> range = wordRangeFromPosition(position);
1654    if (!range) {
1655        send(Messages::WebPageProxy::AutocorrectionDataCallback(Vector<FloatRect>(), String(), 0, 0, callbackID));
1656        return;
1657    }
1658
1659    String textForRange = plainTextReplacingNoBreakSpace(range.get());
1660    const unsigned maxSearchAttempts = 5;
1661    for (size_t i = 0;  i < maxSearchAttempts && textForRange != textForAutocorrection; ++i)
1662    {
1663        position = range->startPosition().previous();
1664        if (position.isNull() || position == range->startPosition())
1665            break;
1666        range = Range::create(*frame.document(), wordRangeFromPosition(position)->startPosition(), range->endPosition());
1667        textForRange = plainTextReplacingNoBreakSpace(range.get());
1668    }
1669
1670    Vector<SelectionRect> selectionRects;
1671    if (textForRange == textForAutocorrection)
1672        range->collectSelectionRects(selectionRects);
1673
1674    Vector<FloatRect> rectsForText;
1675    rectsForText.resize(selectionRects.size());
1676
1677    convertSelectionRectsToRootView(frame.view(), selectionRects);
1678    for (size_t i = 0; i < selectionRects.size(); i++)
1679        rectsForText[i] = selectionRects[i].rect();
1680
1681    bool multipleFonts = false;
1682    CTFontRef font = nil;
1683    if (const SimpleFontData* fontData = frame.editor().fontForSelection(multipleFonts))
1684        font = fontData->getCTFont();
1685
1686    CGFloat fontSize = CTFontGetSize(font);
1687    uint64_t fontTraits = CTFontGetSymbolicTraits(font);
1688    RetainPtr<NSString> fontName = adoptNS((NSString *)CTFontCopyFamilyName(font));
1689    send(Messages::WebPageProxy::AutocorrectionDataCallback(rectsForText, fontName.get(), fontSize, fontTraits, callbackID));
1690}
1691
1692void WebPage::applyAutocorrection(const String& correction, const String& originalText, uint64_t callbackID)
1693{
1694    bool correctionApplied;
1695    syncApplyAutocorrection(correction, originalText, correctionApplied);
1696    send(Messages::WebPageProxy::StringCallback(correctionApplied ? correction : String(), callbackID));
1697}
1698
1699void WebPage::executeEditCommandWithCallback(const String& commandName, uint64_t callbackID)
1700{
1701    executeEditCommand(commandName);
1702    send(Messages::WebPageProxy::VoidCallback(callbackID));
1703}
1704
1705std::chrono::milliseconds WebPage::eventThrottlingDelay() const
1706{
1707    if (m_isInStableState || m_estimatedLatency <= std::chrono::milliseconds(1000 / 60))
1708        return std::chrono::milliseconds::zero();
1709
1710    return std::chrono::milliseconds(std::min<std::chrono::milliseconds::rep>(m_estimatedLatency.count() * 2, 1000));
1711}
1712
1713void WebPage::syncApplyAutocorrection(const String& correction, const String& originalText, bool& correctionApplied)
1714{
1715    RefPtr<Range> range;
1716    correctionApplied = false;
1717    Frame& frame = m_page->focusController().focusedOrMainFrame();
1718    if (!frame.selection().isCaret())
1719        return;
1720    VisiblePosition position = frame.selection().selection().start();
1721
1722    range = wordRangeFromPosition(position);
1723    String textForRange = plainTextReplacingNoBreakSpace(range.get());
1724    if (textForRange != originalText) {
1725        for (size_t i = 0; i < originalText.length(); ++i)
1726            position = position.previous();
1727        if (position.isNull())
1728            position = startOfDocument(static_cast<Node*>(frame.document()->documentElement()));
1729        range = Range::create(*frame.document(), position, frame.selection().selection().start());
1730        if (range)
1731            textForRange = (range) ? plainTextReplacingNoBreakSpace(range.get()) : emptyString();
1732        unsigned loopCount = 0;
1733        const unsigned maxPositionsAttempts = 10;
1734        while (textForRange.length() && textForRange.length() > originalText.length() && loopCount < maxPositionsAttempts) {
1735            position = position.next();
1736            if (position.isNotNull() && position >= frame.selection().selection().start())
1737                range = NULL;
1738            else
1739                range = Range::create(*frame.document(), position, frame.selection().selection().start());
1740            textForRange = (range) ? plainTextReplacingNoBreakSpace(range.get()) : emptyString();
1741            loopCount++;
1742        }
1743    }
1744    if (textForRange != originalText) {
1745        correctionApplied = false;
1746        return;
1747    }
1748
1749    frame.selection().setSelectedRange(range.get(), UPSTREAM, true);
1750    if (correction.length())
1751        frame.editor().insertText(correction, 0);
1752    else
1753        frame.editor().deleteWithDirection(DirectionBackward, CharacterGranularity, false, true);
1754    correctionApplied = true;
1755}
1756
1757static void computeAutocorrectionContext(Frame& frame, String& contextBefore, String& markedText, String& selectedText, String& contextAfter, uint64_t& location, uint64_t& length)
1758{
1759    RefPtr<Range> range;
1760    VisiblePosition startPosition = frame.selection().selection().start();
1761    VisiblePosition endPosition = frame.selection().selection().end();
1762    location = NSNotFound;
1763    length = 0;
1764    const unsigned minContextWordCount = 3;
1765    const unsigned minContextLenght = 12;
1766    const unsigned maxContextLength = 30;
1767
1768    if (frame.selection().isRange())
1769        selectedText = plainTextReplacingNoBreakSpace(frame.selection().selection().toNormalizedRange().get());
1770
1771    if (frame.editor().hasComposition()) {
1772        range = Range::create(*frame.document(), frame.editor().compositionRange()->startPosition(), startPosition);
1773        String markedTextBefore;
1774        if (range)
1775            markedTextBefore = plainTextReplacingNoBreakSpace(range.get());
1776        range = Range::create(*frame.document(), endPosition, frame.editor().compositionRange()->endPosition());
1777        String markedTextAfter;
1778        if (range)
1779            markedTextAfter = plainTextReplacingNoBreakSpace(range.get());
1780        markedText = markedTextBefore + selectedText + markedTextAfter;
1781        if (!markedText.isEmpty()) {
1782            location = markedTextBefore.length();
1783            length = selectedText.length();
1784        }
1785    } else {
1786        if (startPosition != startOfEditableContent(startPosition)) {
1787            VisiblePosition currentPosition = startPosition;
1788            VisiblePosition previousPosition;
1789            unsigned totalContextLength = 0;
1790            for (unsigned i = 0; i < minContextWordCount; ++i) {
1791                if (contextBefore.length() >= minContextLenght)
1792                    break;
1793                previousPosition = startOfWord(positionOfNextBoundaryOfGranularity(currentPosition, WordGranularity, DirectionBackward));
1794                if (previousPosition.isNull())
1795                    break;
1796                String currentWord = plainTextReplacingNoBreakSpace(Range::create(*frame.document(), previousPosition, currentPosition).get());
1797                totalContextLength += currentWord.length();
1798                if (totalContextLength >= maxContextLength)
1799                    break;
1800                currentPosition = previousPosition;
1801            }
1802            if (currentPosition.isNotNull() && currentPosition != startPosition) {
1803                contextBefore = plainTextReplacingNoBreakSpace(Range::create(*frame.document(), currentPosition, startPosition).get());
1804                if (atBoundaryOfGranularity(currentPosition, ParagraphGranularity, DirectionBackward))
1805                    contextBefore = ASCIILiteral("\n ") + contextBefore;
1806            }
1807        }
1808
1809        if (endPosition != endOfEditableContent(endPosition)) {
1810            VisiblePosition nextPosition;
1811            if (!atBoundaryOfGranularity(endPosition, WordGranularity, DirectionForward) && withinTextUnitOfGranularity(endPosition, WordGranularity, DirectionForward))
1812                nextPosition = positionOfNextBoundaryOfGranularity(endPosition, WordGranularity, DirectionForward);
1813            if (nextPosition.isNotNull())
1814                contextAfter = plainTextReplacingNoBreakSpace(Range::create(*frame.document(), endPosition, nextPosition).get());
1815        }
1816    }
1817}
1818
1819void WebPage::requestAutocorrectionContext(uint64_t callbackID)
1820{
1821    String contextBefore;
1822    String contextAfter;
1823    String selectedText;
1824    String markedText;
1825    uint64_t location;
1826    uint64_t length;
1827
1828    computeAutocorrectionContext(m_page->focusController().focusedOrMainFrame(), contextBefore, markedText, selectedText, contextAfter, location, length);
1829
1830    send(Messages::WebPageProxy::AutocorrectionContextCallback(contextBefore, markedText, selectedText, contextAfter, location, length, callbackID));
1831}
1832
1833void WebPage::getAutocorrectionContext(String& contextBefore, String& markedText, String& selectedText, String& contextAfter, uint64_t& location, uint64_t& length)
1834{
1835    computeAutocorrectionContext(m_page->focusController().focusedOrMainFrame(), contextBefore, markedText, selectedText, contextAfter, location, length);
1836}
1837
1838static Element* containingLinkElement(Element* element)
1839{
1840    for (Element* currentElement = element; currentElement; currentElement = currentElement->parentElement())
1841        if (currentElement->isLink())
1842            return currentElement;
1843    return 0;
1844}
1845
1846void WebPage::getPositionInformation(const IntPoint& point, InteractionInformationAtPosition& info)
1847{
1848    FloatPoint adjustedPoint;
1849    Node* hitNode = m_page->mainFrame().nodeRespondingToClickEvents(point, adjustedPoint);
1850
1851    info.point = point;
1852    info.nodeAtPositionIsAssistedNode = (hitNode == m_assistedNode);
1853    if (m_assistedNode) {
1854        Frame& frame = m_page->focusController().focusedOrMainFrame();
1855        if (frame.editor().hasComposition()) {
1856            const uint32_t kHitAreaWidth = 66;
1857            const uint32_t kHitAreaHeight = 66;
1858            FrameView& view = *frame.view();
1859            IntPoint adjustedPoint(view.rootViewToContents(point));
1860            IntPoint constrainedPoint = m_assistedNode ? constrainPoint(adjustedPoint, &frame, m_assistedNode.get()) : adjustedPoint;
1861            VisiblePosition position = frame.visiblePositionForPoint(constrainedPoint);
1862
1863            RefPtr<Range> compositionRange = frame.editor().compositionRange();
1864            if (position < compositionRange->startPosition())
1865                position = compositionRange->startPosition();
1866            else if (position > compositionRange->endPosition())
1867                position = compositionRange->endPosition();
1868            IntRect caretRect = view.contentsToRootView(position.absoluteCaretBounds());
1869            float deltaX = abs(caretRect.x() + (caretRect.width() / 2) - point.x());
1870            float deltaYFromTheTop = abs(caretRect.y() - point.y());
1871            float deltaYFromTheBottom = abs(caretRect.y() + caretRect.height() - point.y());
1872
1873            info.isNearMarkedText = !(deltaX > kHitAreaWidth || deltaYFromTheTop > kHitAreaHeight || deltaYFromTheBottom > kHitAreaHeight);
1874        }
1875    }
1876    bool elementIsLinkOrImage = false;
1877    if (hitNode) {
1878        info.clickableElementName = hitNode->nodeName();
1879
1880        Element* element = hitNode->isElementNode() ? toElement(hitNode) : 0;
1881        if (element) {
1882            Element* linkElement = nullptr;
1883            if (element->renderer() && element->renderer()->isRenderImage()) {
1884                elementIsLinkOrImage = true;
1885                linkElement = containingLinkElement(element);
1886            } else if (element->isLink()) {
1887                linkElement = element;
1888                elementIsLinkOrImage = true;
1889            }
1890
1891            if (elementIsLinkOrImage) {
1892                // Ensure that the image contains at most 600K pixels, so that it is not too big.
1893                if (RefPtr<WebImage> snapshot = snapshotNode(*element, SnapshotOptionsShareable, 600 * 1024))
1894                    info.image = snapshot->bitmap();
1895            }
1896            if (linkElement)
1897                info.url = [(NSURL *)linkElement->document().completeURL(stripLeadingAndTrailingHTMLSpaces(linkElement->getAttribute(HTMLNames::hrefAttr))) absoluteString];
1898            info.title = element->fastGetAttribute(HTMLNames::titleAttr).string();
1899            if (linkElement && info.title.isEmpty())
1900                info.title = element->innerText();
1901            if (element->renderer())
1902                info.bounds = element->renderer()->absoluteBoundingBoxRect(true);
1903        }
1904    }
1905
1906    if (!elementIsLinkOrImage) {
1907        HitTestResult result = m_page->mainFrame().eventHandler().hitTestResultAtPoint((point), HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent | HitTestRequest::AllowChildFrameContent);
1908        hitNode = result.innerNode();
1909        // Hit test could return HTMLHtmlElement that has no renderer, if the body is smaller than the document.
1910        if (hitNode && hitNode->renderer()) {
1911            RenderObject* renderer = hitNode->renderer();
1912            m_page->focusController().setFocusedFrame(result.innerNodeFrame());
1913            info.bounds = renderer->absoluteBoundingBoxRect(true);
1914            // We don't want to select blocks that are larger than 97% of the visible area of the document.
1915            const static CGFloat factor = 0.97;
1916            info.isSelectable = renderer->style().userSelect() != SELECT_NONE && info.bounds.height() < result.innerNodeFrame()->view()->unobscuredContentRect().height() * factor;
1917        }
1918    }
1919}
1920
1921void WebPage::requestPositionInformation(const IntPoint& point)
1922{
1923    InteractionInformationAtPosition info;
1924
1925    getPositionInformation(point, info);
1926    send(Messages::WebPageProxy::DidReceivePositionInformation(info));
1927}
1928
1929void WebPage::startInteractionWithElementAtPosition(const WebCore::IntPoint& point)
1930{
1931    FloatPoint adjustedPoint;
1932    m_interactionNode = m_page->mainFrame().nodeRespondingToClickEvents(point, adjustedPoint);
1933}
1934
1935void WebPage::stopInteraction()
1936{
1937    m_interactionNode = nullptr;
1938}
1939
1940void WebPage::performActionOnElement(uint32_t action)
1941{
1942    if (!m_interactionNode || !m_interactionNode->isHTMLElement())
1943        return;
1944
1945    HTMLElement* element = toHTMLElement(m_interactionNode.get());
1946    if (!element->renderer())
1947        return;
1948
1949    if (static_cast<SheetAction>(action) == SheetAction::Copy) {
1950        if (element->renderer()->isRenderImage()) {
1951            Element* linkElement = containingLinkElement(element);
1952
1953            if (!linkElement)
1954                m_interactionNode->document().frame()->editor().writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(), *element, toRenderImage(element->renderer())->cachedImage()->url(), String());
1955            else
1956                m_interactionNode->document().frame()->editor().copyURL(linkElement->document().completeURL(stripLeadingAndTrailingHTMLSpaces(linkElement->getAttribute(HTMLNames::hrefAttr))), linkElement->textContent());
1957        } else if (element->isLink()) {
1958            m_interactionNode->document().frame()->editor().copyURL(element->document().completeURL(stripLeadingAndTrailingHTMLSpaces(element->getAttribute(HTMLNames::hrefAttr))), element->textContent());
1959        }
1960    } else if (static_cast<SheetAction>(action) == SheetAction::SaveImage) {
1961        if (!element->renderer()->isRenderImage())
1962            return;
1963        CachedImage* cachedImage = toRenderImage(element->renderer())->cachedImage();
1964        if (cachedImage) {
1965            SharedMemory::Handle handle;
1966            RefPtr<SharedBuffer> buffer = cachedImage->resourceBuffer()->sharedBuffer();
1967            if (buffer) {
1968                uint64_t bufferSize = buffer->size();
1969                RefPtr<SharedMemory> sharedMemoryBuffer = SharedMemory::create(bufferSize);
1970                memcpy(sharedMemoryBuffer->data(), buffer->data(), bufferSize);
1971                sharedMemoryBuffer->createHandle(handle, SharedMemory::ReadOnly);
1972                send(Messages::WebPageProxy::SaveImageToLibrary(handle, bufferSize));
1973            }
1974        }
1975    }
1976}
1977
1978static inline bool isAssistableNode(Node* node)
1979{
1980    if (isHTMLSelectElement(node))
1981        return true;
1982    if (isHTMLTextAreaElement(node))
1983        return !toHTMLTextAreaElement(node)->isReadOnlyNode();
1984    if (isHTMLInputElement(node)) {
1985        HTMLInputElement* element = toHTMLInputElement(node);
1986        return !element->isReadOnlyNode() && (element->isTextField() || element->isDateField() || element->isDateTimeLocalField() || element->isMonthField() || element->isTimeField());
1987    }
1988
1989    return node->isContentEditable();
1990}
1991
1992static inline Element* nextFocusableElement(Node* startNode, Page* page, bool isForward)
1993{
1994    RefPtr<KeyboardEvent> key = KeyboardEvent::create();
1995
1996    Element* nextElement = toElement(startNode);
1997    do {
1998        nextElement = isForward ? page->focusController().nextFocusableElement(FocusNavigationScope::focusNavigationScopeOf(&nextElement->document()), nextElement, key.get())
1999            : page->focusController().previousFocusableElement(FocusNavigationScope::focusNavigationScopeOf(&nextElement->document()), nextElement, key.get());
2000    } while (nextElement && !isAssistableNode(nextElement));
2001
2002    return nextElement;
2003}
2004
2005static inline bool hasFocusableElement(Node* startNode, Page* page, bool isForward)
2006{
2007    return nextFocusableElement(startNode, page, isForward) != nil;
2008}
2009
2010void WebPage::focusNextAssistedNode(bool isForward)
2011{
2012    Element* nextElement = nextFocusableElement(m_assistedNode.get(), m_page.get(), isForward);
2013    m_userIsInteracting = true;
2014    if (nextElement)
2015        nextElement->focus();
2016    m_userIsInteracting = false;
2017}
2018
2019void WebPage::getAssistedNodeInformation(AssistedNodeInformation& information)
2020{
2021    layoutIfNeeded();
2022
2023    // FIXME: This should return the selection rect, but when this is called at focus time
2024    // we don't have a selection yet. Using the last interaction location is a reasonable approximation for now.
2025    // FIXME: should the selection rect always be inside the elementRect?
2026    information.selectionRect = IntRect(m_lastInteractionLocation, IntSize(1, 1));
2027
2028    if (RenderObject* renderer = m_assistedNode->renderer()) {
2029        Frame& elementFrame = m_page->focusController().focusedOrMainFrame();
2030        information.elementRect = elementFrame.view()->contentsToRootView(renderer->absoluteBoundingBoxRect());
2031        information.nodeFontSize = renderer->style().fontDescription().computedSize();
2032
2033        bool inFixed = false;
2034        renderer->localToContainerPoint(FloatPoint(), nullptr, 0, &inFixed);
2035        information.insideFixedPosition = inFixed;
2036
2037        if (inFixed && elementFrame.isMainFrame()) {
2038            FrameView* frameView = elementFrame.view();
2039            IntRect currentFixedPositionRect = frameView->customFixedPositionLayoutRect();
2040            frameView->setCustomFixedPositionLayoutRect(frameView->renderView()->documentRect());
2041            information.elementRect = frameView->contentsToRootView(renderer->absoluteBoundingBoxRect());
2042            frameView->setCustomFixedPositionLayoutRect(currentFixedPositionRect);
2043
2044            if (!information.elementRect.contains(information.selectionRect))
2045                information.selectionRect.setLocation(information.elementRect.location());
2046        }
2047    } else
2048        information.elementRect = IntRect();
2049
2050    information.minimumScaleFactor = minimumPageScaleFactor();
2051    information.maximumScaleFactor = maximumPageScaleFactor();
2052    information.allowsUserScaling = m_viewportConfiguration.allowsUserScaling();
2053    information.hasNextNode = hasFocusableElement(m_assistedNode.get(), m_page.get(), true);
2054    information.hasPreviousNode = hasFocusableElement(m_assistedNode.get(), m_page.get(), false);
2055
2056    if (isHTMLSelectElement(m_assistedNode.get())) {
2057        HTMLSelectElement* element = toHTMLSelectElement(m_assistedNode.get());
2058        information.elementType = InputType::Select;
2059        size_t count = element->listItems().size();
2060        int parentGroupID = 0;
2061        // The parent group ID indicates the group the option belongs to and is 0 for group elements.
2062        // If there are option elements in between groups, they are given it's own group identifier.
2063        // If a select does not have groups, all the option elements have group ID 0.
2064        for (size_t i = 0; i < count; ++i) {
2065            HTMLElement* item = element->listItems()[i];
2066            if (isHTMLOptionElement(item)) {
2067                HTMLOptionElement* option = toHTMLOptionElement(item);
2068                information.selectOptions.append(OptionItem(option->text(), false, parentGroupID, option->selected(), option->fastHasAttribute(WebCore::HTMLNames::disabledAttr)));
2069            } else if (isHTMLOptGroupElement(item)) {
2070                HTMLOptGroupElement* group = toHTMLOptGroupElement(item);
2071                parentGroupID++;
2072                information.selectOptions.append(OptionItem(group->groupLabelText(), true, 0, false, group->fastHasAttribute(WebCore::HTMLNames::disabledAttr)));
2073            }
2074        }
2075        information.selectedIndex = element->selectedIndex();
2076        information.isMultiSelect = element->multiple();
2077    } else if (isHTMLTextAreaElement(m_assistedNode.get())) {
2078        HTMLTextAreaElement* element = toHTMLTextAreaElement(m_assistedNode.get());
2079        information.autocapitalizeType = static_cast<WebAutocapitalizeType>(element->autocapitalizeType());
2080        information.isAutocorrect = element->autocorrect();
2081        information.elementType = InputType::TextArea;
2082        information.isReadOnly = element->isReadOnly();
2083        information.value = element->value();
2084    } else if (isHTMLInputElement(m_assistedNode.get())) {
2085        HTMLInputElement* element = toHTMLInputElement(m_assistedNode.get());
2086        HTMLFormElement* form = element->form();
2087        if (form)
2088            information.formAction = form->getURLAttribute(WebCore::HTMLNames::actionAttr);
2089        information.autocapitalizeType = static_cast<WebAutocapitalizeType>(element->autocapitalizeType());
2090        information.isAutocorrect = element->autocorrect();
2091        if (element->isPasswordField())
2092            information.elementType = InputType::Password;
2093        else if (element->isSearchField())
2094            information.elementType = InputType::Search;
2095        else if (element->isEmailField())
2096            information.elementType = InputType::Email;
2097        else if (element->isTelephoneField())
2098            information.elementType = InputType::Phone;
2099        else if (element->isNumberField())
2100            information.elementType = element->getAttribute("pattern") == "\\d*" || element->getAttribute("pattern") == "[0-9]*" ? InputType::NumberPad : InputType::Number;
2101        else if (element->isDateTimeLocalField())
2102            information.elementType = InputType::DateTimeLocal;
2103        else if (element->isDateField())
2104            information.elementType = InputType::Date;
2105        else if (element->isDateTimeField())
2106            information.elementType = InputType::DateTime;
2107        else if (element->isTimeField())
2108            information.elementType = InputType::Time;
2109        else if (element->isWeekField())
2110            information.elementType = InputType::Week;
2111        else if (element->isMonthField())
2112            information.elementType = InputType::Month;
2113        else if (element->isURLField())
2114            information.elementType = InputType::URL;
2115        else if (element->isText()) {
2116            const AtomicString& pattern = element->fastGetAttribute(HTMLNames::patternAttr);
2117            if (pattern == "\\d*" || pattern == "[0-9]*")
2118                information.elementType = InputType::NumberPad;
2119            else {
2120                information.elementType = InputType::Text;
2121                if (!information.formAction.isEmpty()
2122                    && (element->getNameAttribute().contains("search") || element->getIdAttribute().contains("search") || element->fastGetAttribute(HTMLNames::titleAttr).contains("search")))
2123                    information.elementType = InputType::Search;
2124            }
2125        }
2126
2127        information.isReadOnly = element->isReadOnly();
2128        information.value = element->value();
2129        information.valueAsNumber = element->valueAsNumber();
2130        information.title = element->title();
2131    } else if (m_assistedNode->hasEditableStyle()) {
2132        information.elementType = InputType::ContentEditable;
2133        information.isAutocorrect = true;   // FIXME: Should we look at the attribute?
2134        information.autocapitalizeType = WebAutocapitalizeTypeSentences; // FIXME: Should we look at the attribute?
2135        information.isReadOnly = false;
2136    }
2137}
2138
2139void WebPage::elementDidFocus(WebCore::Node* node)
2140{
2141    if (node->hasTagName(WebCore::HTMLNames::selectTag) || node->hasTagName(WebCore::HTMLNames::inputTag) || node->hasTagName(WebCore::HTMLNames::textareaTag) || node->hasEditableStyle()) {
2142        m_assistedNode = node;
2143        AssistedNodeInformation information;
2144        getAssistedNodeInformation(information);
2145        RefPtr<API::Object> userData;
2146
2147        // FIXME: We check m_userIsInteracting so that we don't begin an input session for a
2148        // programmatic focus that doesn't cause the keyboard to appear. But this misses the case of
2149        // a programmatic focus happening while the keyboard is already shown. Once we have a way to
2150        // know the keyboard state in the Web Process, we should refine the condition.
2151        if (m_userIsInteracting)
2152            m_formClient->willBeginInputSession(this, toElement(node), WebFrame::fromCoreFrame(*node->document().frame()), userData);
2153
2154        send(Messages::WebPageProxy::StartAssistingNode(information, m_userIsInteracting, m_hasPendingBlurNotification, InjectedBundleUserMessageEncoder(userData.get())));
2155        m_hasPendingBlurNotification = false;
2156    }
2157}
2158
2159void WebPage::elementDidBlur(WebCore::Node* node)
2160{
2161    if (m_assistedNode == node) {
2162        m_hasPendingBlurNotification = true;
2163        dispatch_async(dispatch_get_main_queue(), ^{
2164            if (m_hasPendingBlurNotification)
2165                send(Messages::WebPageProxy::StopAssistingNode());
2166            m_hasPendingBlurNotification = false;
2167        });
2168        m_assistedNode = 0;
2169    }
2170}
2171
2172void WebPage::setViewportConfigurationMinimumLayoutSize(const FloatSize& size)
2173{
2174    resetTextAutosizingBeforeLayoutIfNeeded(m_viewportConfiguration.minimumLayoutSize(), size);
2175    m_viewportConfiguration.setMinimumLayoutSize(size);
2176    viewportConfigurationChanged();
2177}
2178
2179void WebPage::setViewportConfigurationMinimumLayoutSizeForMinimalUI(const FloatSize& size)
2180{
2181    resetTextAutosizingBeforeLayoutIfNeeded(m_viewportConfiguration.minimumLayoutSizeForMinimalUI(), size);
2182    m_viewportConfiguration.setMinimumLayoutSizeForMinimalUI(size);
2183    viewportConfigurationChanged();
2184}
2185
2186void WebPage::setMaximumUnobscuredSize(const FloatSize& maximumUnobscuredSize)
2187{
2188    m_maximumUnobscuredSize = maximumUnobscuredSize;
2189    updateViewportSizeForCSSViewportUnits();
2190}
2191
2192void WebPage::setDeviceOrientation(int32_t deviceOrientation)
2193{
2194    if (deviceOrientation == m_deviceOrientation)
2195        return;
2196    m_deviceOrientation = deviceOrientation;
2197    m_page->mainFrame().orientationChanged();
2198}
2199
2200static inline bool withinEpsilon(float a, float b)
2201{
2202    return fabs(a - b) < std::numeric_limits<float>::epsilon();
2203}
2204
2205void WebPage::resetTextAutosizingBeforeLayoutIfNeeded(const FloatSize& oldSize, const FloatSize& newSize)
2206{
2207    if (oldSize.width() == newSize.width())
2208        return;
2209
2210    for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
2211        Document* document = frame->document();
2212        if (!document || !document->renderView())
2213            continue;
2214        document->renderView()->resetTextAutosizing();
2215    }
2216}
2217
2218void WebPage::dynamicViewportSizeUpdate(const FloatSize& minimumLayoutSize, const FloatSize& minimumLayoutSizeForMinimalUI, const WebCore::FloatSize& maximumUnobscuredSize, const FloatRect& targetExposedContentRect, const FloatRect& targetUnobscuredRect, const WebCore::FloatRect& targetUnobscuredRectInScrollViewCoordinates, double targetScale, int32_t deviceOrientation)
2219{
2220    TemporaryChange<bool> dynamicSizeUpdateGuard(m_inDynamicSizeUpdate, true);
2221    // FIXME: this does not handle the cases where the content would change the content size or scroll position from JavaScript.
2222    // To handle those cases, we would need to redo this computation on every change until the next visible content rect update.
2223
2224    FrameView& frameView = *m_page->mainFrame().view();
2225    IntSize oldContentSize = frameView.contentsSize();
2226    float oldPageScaleFactor = m_page->pageScaleFactor();
2227
2228    m_dynamicSizeUpdateHistory.add(std::make_pair(oldContentSize, oldPageScaleFactor), IntPoint(frameView.scrollOffset()));
2229
2230    RefPtr<Node> oldNodeAtCenter;
2231    double visibleHorizontalFraction = 1;
2232    float relativeHorizontalPositionInNodeAtCenter = 0;
2233    float relativeVerticalPositionInNodeAtCenter = 0;
2234    {
2235        visibleHorizontalFraction = frameView.unobscuredContentSize().width() / oldContentSize.width();
2236        IntPoint unobscuredContentRectCenter = frameView.unobscuredContentRect().center();
2237
2238        HitTestResult hitTestResult = HitTestResult(unobscuredContentRectCenter);
2239
2240        if (RenderView* mainFrameRenderView = frameView.renderView()) {
2241            HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent);
2242            mainFrameRenderView->hitTest(request, hitTestResult);
2243        }
2244
2245        if (Node* node = hitTestResult.innerNode()) {
2246            if (RenderObject* renderer = node->renderer()) {
2247                FrameView& containingView = *node->document().frame()->view();
2248                FloatRect boundingBox = containingView.contentsToRootView(renderer->absoluteBoundingBoxRect(true));
2249                relativeHorizontalPositionInNodeAtCenter = (unobscuredContentRectCenter.x() - boundingBox.x()) / boundingBox.width();
2250                relativeVerticalPositionInNodeAtCenter = (unobscuredContentRectCenter.y() - boundingBox.y()) / boundingBox.height();
2251                oldNodeAtCenter = node;
2252            }
2253        }
2254    }
2255
2256    resetTextAutosizingBeforeLayoutIfNeeded(m_viewportConfiguration.minimumLayoutSize(), minimumLayoutSize);
2257    resetTextAutosizingBeforeLayoutIfNeeded(m_viewportConfiguration.minimumLayoutSizeForMinimalUI(), minimumLayoutSizeForMinimalUI);
2258    m_viewportConfiguration.setMinimumLayoutSize(minimumLayoutSize);
2259    m_viewportConfiguration.setMinimumLayoutSizeForMinimalUI(minimumLayoutSizeForMinimalUI);
2260    IntSize newLayoutSize = m_viewportConfiguration.layoutSize();
2261
2262    setFixedLayoutSize(newLayoutSize);
2263    setMaximumUnobscuredSize(maximumUnobscuredSize);
2264
2265    frameView.updateLayoutAndStyleIfNeededRecursive();
2266
2267    IntSize newContentSize = frameView.contentsSize();
2268
2269    double scale = scaleAfterViewportWidthChange(targetScale, m_userHasChangedPageScaleFactor, m_viewportConfiguration, targetUnobscuredRectInScrollViewCoordinates.width(), newContentSize, oldContentSize, visibleHorizontalFraction);
2270    FloatRect newUnobscuredContentRect = targetUnobscuredRect;
2271    FloatRect newExposedContentRect = targetExposedContentRect;
2272
2273    bool scaleChanged = !withinEpsilon(scale, targetScale);
2274    if (scaleChanged) {
2275        // The target scale the UI is using cannot be reached by the content. We need to compute new targets based
2276        // on the viewport constraint and report everything back to the UIProcess.
2277
2278        // 1) Compute a new unobscured rect centered around the original one.
2279        double scaleDifference = targetScale / scale;
2280        double newUnobscuredRectWidth = targetUnobscuredRect.width() * scaleDifference;
2281        double newUnobscuredRectHeight = targetUnobscuredRect.height() * scaleDifference;
2282        double newUnobscuredRectX = targetUnobscuredRect.x() - (newUnobscuredRectWidth - targetUnobscuredRect.width()) / 2;
2283        double newUnobscuredRectY = targetUnobscuredRect.y() - (newUnobscuredRectHeight - targetUnobscuredRect.height()) / 2;
2284        newUnobscuredContentRect = FloatRect(newUnobscuredRectX, newUnobscuredRectY, newUnobscuredRectWidth, newUnobscuredRectHeight);
2285
2286        // 2) Extend our new unobscuredRect by the obscured margins to get a new exposed rect.
2287        double obscuredTopMargin = (targetUnobscuredRect.y() - targetExposedContentRect.y()) * scaleDifference;
2288        double obscuredLeftMargin = (targetUnobscuredRect.x() - targetExposedContentRect.x()) * scaleDifference;
2289        double obscuredBottomMargin = (targetExposedContentRect.maxY() - targetUnobscuredRect.maxY()) * scaleDifference;
2290        double obscuredRightMargin = (targetExposedContentRect.maxX() - targetUnobscuredRect.maxX()) * scaleDifference;
2291        newExposedContentRect = FloatRect(newUnobscuredRectX - obscuredLeftMargin,
2292                                          newUnobscuredRectY - obscuredTopMargin,
2293                                          newUnobscuredRectWidth + obscuredLeftMargin + obscuredRightMargin,
2294                                          newUnobscuredRectHeight + obscuredTopMargin + obscuredBottomMargin);
2295    }
2296
2297    if (oldContentSize != newContentSize || scaleChanged) {
2298        // Snap the new unobscured rect back into the content rect.
2299        newUnobscuredContentRect.setWidth(std::min(static_cast<float>(newContentSize.width()), newUnobscuredContentRect.width()));
2300        newUnobscuredContentRect.setHeight(std::min(static_cast<float>(newContentSize.height()), newUnobscuredContentRect.height()));
2301
2302        bool positionWasRestoredFromSizeUpdateHistory = false;
2303        const auto& previousPosition = m_dynamicSizeUpdateHistory.find(std::pair<IntSize, float>(newContentSize, scale));
2304        if (previousPosition != m_dynamicSizeUpdateHistory.end()) {
2305            IntPoint restoredPosition = previousPosition->value;
2306            FloatPoint deltaPosition(restoredPosition.x() - newUnobscuredContentRect.x(), restoredPosition.y() - newUnobscuredContentRect.y());
2307            newUnobscuredContentRect.moveBy(deltaPosition);
2308            newExposedContentRect.moveBy(deltaPosition);
2309            positionWasRestoredFromSizeUpdateHistory = true;
2310        } else if (oldContentSize != newContentSize) {
2311            FloatPoint newRelativeContentCenter;
2312
2313            if (RenderObject* renderer = oldNodeAtCenter ? oldNodeAtCenter->renderer() : nullptr) {
2314                FrameView& containingView = *oldNodeAtCenter->document().frame()->view();
2315                FloatRect newBoundingBox = containingView.contentsToRootView(renderer->absoluteBoundingBoxRect(true));
2316                newRelativeContentCenter = FloatPoint(newBoundingBox.x() + relativeHorizontalPositionInNodeAtCenter * newBoundingBox.width(), newBoundingBox.y() + relativeVerticalPositionInNodeAtCenter * newBoundingBox.height());
2317            } else
2318                newRelativeContentCenter = relativeCenterAfterContentSizeChange(targetUnobscuredRect, oldContentSize, newContentSize);
2319
2320            FloatPoint newUnobscuredContentRectCenter = newUnobscuredContentRect.center();
2321            FloatPoint positionDelta(newRelativeContentCenter.x() - newUnobscuredContentRectCenter.x(), newRelativeContentCenter.y() - newUnobscuredContentRectCenter.y());
2322            newUnobscuredContentRect.moveBy(positionDelta);
2323            newExposedContentRect.moveBy(positionDelta);
2324        }
2325
2326        // Make the top/bottom edges "sticky" within 1 pixel.
2327        if (!positionWasRestoredFromSizeUpdateHistory) {
2328            if (targetUnobscuredRect.maxY() > oldContentSize.height() - 1) {
2329                float bottomVerticalPosition = newContentSize.height() - newUnobscuredContentRect.height();
2330                newUnobscuredContentRect.setY(bottomVerticalPosition);
2331                newExposedContentRect.setY(bottomVerticalPosition);
2332            }
2333            if (targetUnobscuredRect.y() < 1) {
2334                newUnobscuredContentRect.setY(0);
2335                newExposedContentRect.setY(0);
2336            }
2337
2338            bool likelyResponsiveDesignViewport = newLayoutSize.width() == minimumLayoutSize.width() && withinEpsilon(scale, 1);
2339            bool contentBleedsOutsideLayoutWidth = newContentSize.width() > newLayoutSize.width();
2340            bool originalScrollPositionWasOnTheLeftEdge = targetUnobscuredRect.x() <= 0;
2341            if (likelyResponsiveDesignViewport && contentBleedsOutsideLayoutWidth && originalScrollPositionWasOnTheLeftEdge) {
2342                // This is a special heuristics for "responsive" design with odd layout. It is quite common for responsive design
2343                // to have content "bleeding" outside of the minimal layout width, usually from an image or table larger than expected.
2344                // In those cases, the design usually does not adapt to the new width and remain at the newLayoutSize except for the
2345                // large boxes.
2346                // It is worth revisiting this special case as web developers get better with responsive design.
2347                newExposedContentRect.setX(0);
2348                newUnobscuredContentRect.setX(0);
2349            }
2350        }
2351
2352        float horizontalAdjustment = 0;
2353        if (newUnobscuredContentRect.maxX() > newContentSize.width())
2354            horizontalAdjustment -= newUnobscuredContentRect.maxX() - newContentSize.width();
2355        float verticalAdjustment = 0;
2356        if (newUnobscuredContentRect.maxY() > newContentSize.height())
2357            verticalAdjustment -= newUnobscuredContentRect.maxY() - newContentSize.height();
2358        if (newUnobscuredContentRect.x() < 0)
2359            horizontalAdjustment += - newUnobscuredContentRect.x();
2360        if (newUnobscuredContentRect.y() < 0)
2361            verticalAdjustment += - newUnobscuredContentRect.y();
2362
2363        FloatPoint adjustmentDelta(horizontalAdjustment, verticalAdjustment);
2364        newUnobscuredContentRect.moveBy(adjustmentDelta);
2365        newExposedContentRect.moveBy(adjustmentDelta);
2366    }
2367
2368    frameView.setScrollVelocity(0, 0, 0, monotonicallyIncreasingTime());
2369
2370    IntPoint roundedUnobscuredContentRectPosition = roundedIntPoint(newUnobscuredContentRect.location());
2371    frameView.setUnobscuredContentSize(newUnobscuredContentRect.size());
2372    m_drawingArea->setExposedContentRect(newExposedContentRect);
2373
2374    scalePage(scale, roundedUnobscuredContentRectPosition);
2375
2376    frameView.updateLayoutAndStyleIfNeededRecursive();
2377    IntRect fixedPositionLayoutRect = enclosingIntRect(frameView.viewportConstrainedObjectsRect());
2378    frameView.setCustomFixedPositionLayoutRect(fixedPositionLayoutRect);
2379
2380    frameView.setCustomSizeForResizeEvent(expandedIntSize(targetUnobscuredRectInScrollViewCoordinates.size()));
2381    setDeviceOrientation(deviceOrientation);
2382    frameView.setScrollOffset(roundedUnobscuredContentRectPosition);
2383
2384    send(Messages::WebPageProxy::DynamicViewportUpdateChangedTarget(pageScaleFactor(), frameView.scrollPosition()));
2385}
2386
2387void WebPage::synchronizeDynamicViewportUpdate(double& newTargetScale, FloatPoint& newScrollPosition, uint64_t& nextValidLayerTreeTransactionID)
2388{
2389    newTargetScale = pageScaleFactor();
2390    newScrollPosition = m_page->mainFrame().view()->scrollPosition();
2391    nextValidLayerTreeTransactionID = toRemoteLayerTreeDrawingArea(*m_drawingArea).nextTransactionID();
2392}
2393
2394void WebPage::resetViewportDefaultConfiguration(WebFrame* frame)
2395{
2396    if (m_useTestingViewportConfiguration) {
2397        m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::testingParameters());
2398        return;
2399    }
2400
2401    if (!frame) {
2402        m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::webpageParameters());
2403        return;
2404    }
2405
2406    Document* document = frame->coreFrame()->document();
2407    if (document->isImageDocument())
2408        m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::imageDocumentParameters());
2409    else if (document->isTextDocument())
2410        m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::textDocumentParameters());
2411    else
2412        m_viewportConfiguration.setDefaultConfiguration(ViewportConfiguration::webpageParameters());
2413}
2414
2415void WebPage::viewportConfigurationChanged()
2416{
2417    setFixedLayoutSize(m_viewportConfiguration.layoutSize());
2418
2419    double initialScale = m_viewportConfiguration.initialScale();
2420    double scale;
2421    if (m_userHasChangedPageScaleFactor)
2422        scale = std::max(std::min(pageScaleFactor(), m_viewportConfiguration.maximumScale()), m_viewportConfiguration.minimumScale());
2423    else
2424        scale = initialScale;
2425
2426    m_page->setZoomedOutPageScaleFactor(m_viewportConfiguration.minimumScale());
2427
2428    updateViewportSizeForCSSViewportUnits();
2429
2430    FrameView& frameView = *mainFrameView();
2431    IntPoint scrollPosition = frameView.scrollPosition();
2432    if (!m_hasReceivedVisibleContentRectsAfterDidCommitLoad) {
2433        FloatSize minimumLayoutSizeInScrollViewCoordinates = m_viewportConfiguration.activeMinimumLayoutSizeInScrollViewCoordinates();
2434        minimumLayoutSizeInScrollViewCoordinates.scale(1 / scale);
2435        IntSize minimumLayoutSizeInDocumentCoordinates = roundedIntSize(minimumLayoutSizeInScrollViewCoordinates);
2436        frameView.setUnobscuredContentSize(minimumLayoutSizeInDocumentCoordinates);
2437        frameView.setScrollVelocity(0, 0, 0, monotonicallyIncreasingTime());
2438
2439        // FIXME: We could send down the obscured margins to find a better exposed rect and unobscured rect.
2440        // It is not a big deal at the moment because the tile coverage will always extend past the obscured bottom inset.
2441        m_drawingArea->setExposedContentRect(FloatRect(scrollPosition, minimumLayoutSizeInDocumentCoordinates));
2442    }
2443    scalePage(scale, scrollPosition);
2444
2445    if (!m_hasReceivedVisibleContentRectsAfterDidCommitLoad) {
2446        // This takes scale into account, so do after the scale change.
2447        frameView.setCustomFixedPositionLayoutRect(enclosingIntRect(frameView.viewportConstrainedObjectsRect()));
2448
2449        frameView.setCustomSizeForResizeEvent(expandedIntSize(m_viewportConfiguration.activeMinimumLayoutSizeInScrollViewCoordinates()));
2450    }
2451}
2452
2453void WebPage::updateViewportSizeForCSSViewportUnits()
2454{
2455    FloatSize largestUnobscuredRect = m_maximumUnobscuredSize;
2456    if (largestUnobscuredRect.isEmpty())
2457        largestUnobscuredRect = m_viewportConfiguration.minimumLayoutSizeForMinimalUI();
2458
2459    FrameView& frameView = *mainFrameView();
2460    largestUnobscuredRect.scale(1 / m_viewportConfiguration.initialScale());
2461    frameView.setViewportSizeForCSSViewportUnits(roundedIntSize(largestUnobscuredRect));
2462}
2463
2464void WebPage::applicationWillResignActive()
2465{
2466    [[NSNotificationCenter defaultCenter] postNotificationName:WebUIApplicationWillResignActiveNotification object:nil];
2467}
2468
2469void WebPage::applicationWillEnterForeground()
2470{
2471    [[NSNotificationCenter defaultCenter] postNotificationName:WebUIApplicationWillEnterForegroundNotification object:nil];
2472}
2473
2474void WebPage::applicationDidBecomeActive()
2475{
2476    [[NSNotificationCenter defaultCenter] postNotificationName:WebUIApplicationDidBecomeActiveNotification object:nil];
2477}
2478
2479static inline void adjustVelocityDataForBoundedScale(double& horizontalVelocity, double& verticalVelocity, double& scaleChangeRate, double exposedRectScale, double boundedScale)
2480{
2481    if (scaleChangeRate) {
2482        horizontalVelocity = 0;
2483        verticalVelocity = 0;
2484    }
2485
2486    if (exposedRectScale != boundedScale)
2487        scaleChangeRate = 0;
2488}
2489
2490static inline FloatRect adjustExposedRectForBoundedScale(const FloatRect& exposedRect, double exposedRectScale, double newScale)
2491{
2492    if (exposedRectScale < newScale)
2493        return exposedRect;
2494
2495    return adjustExposedRectForNewScale(exposedRect, exposedRectScale, newScale);
2496}
2497
2498void WebPage::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visibleContentRectUpdateInfo, double oldestTimestamp)
2499{
2500    // Skip any VisibleContentRectUpdate that have been queued before DidCommitLoad suppresses the updates in the UIProcess.
2501    if (visibleContentRectUpdateInfo.lastLayerTreeTransactionID() < m_firstLayerTreeTransactionIDAfterDidCommitLoad)
2502        return;
2503
2504    m_hasReceivedVisibleContentRectsAfterDidCommitLoad = true;
2505    m_isInStableState = visibleContentRectUpdateInfo.inStableState();
2506
2507    double scaleNoiseThreshold = 0.005;
2508    double filteredScale = visibleContentRectUpdateInfo.scale();
2509    double currentScale = m_page->pageScaleFactor();
2510    if (!m_isInStableState && fabs(filteredScale - m_page->pageScaleFactor()) < scaleNoiseThreshold) {
2511        // Tiny changes of scale during interactive zoom cause content to jump by one pixel, creating
2512        // visual noise. We filter those useless updates.
2513        filteredScale = currentScale;
2514    }
2515
2516    double boundedScale = std::min(m_viewportConfiguration.maximumScale(), std::max(m_viewportConfiguration.minimumScale(), filteredScale));
2517
2518    // Skip progressively redrawing tiles if pinch-zooming while the system is under memory pressure.
2519    if (boundedScale != currentScale && !m_isInStableState && memoryPressureHandler().isUnderMemoryPressure())
2520        return;
2521
2522    if (m_isInStableState)
2523        m_hasStablePageScaleFactor = true;
2524    else {
2525        if (m_oldestNonStableUpdateVisibleContentRectsTimestamp == std::chrono::milliseconds::zero())
2526            m_oldestNonStableUpdateVisibleContentRectsTimestamp = std::chrono::milliseconds(static_cast<std::chrono::milliseconds::rep>(oldestTimestamp * 1000));
2527    }
2528
2529    FloatRect exposedRect = visibleContentRectUpdateInfo.exposedRect();
2530    FloatRect adjustedExposedRect = adjustExposedRectForBoundedScale(exposedRect, visibleContentRectUpdateInfo.scale(), boundedScale);
2531    m_drawingArea->setExposedContentRect(adjustedExposedRect);
2532
2533    IntPoint scrollPosition = roundedIntPoint(visibleContentRectUpdateInfo.unobscuredRect().location());
2534
2535    float floatBoundedScale = boundedScale;
2536    bool hasSetPageScale = false;
2537    if (floatBoundedScale != currentScale) {
2538        m_scaleWasSetByUIProcess = true;
2539        m_hasStablePageScaleFactor = m_isInStableState;
2540
2541        m_dynamicSizeUpdateHistory.clear();
2542
2543        m_page->setPageScaleFactor(floatBoundedScale, scrollPosition, m_isInStableState);
2544        hasSetPageScale = true;
2545
2546        if (LayerTreeHost* layerTreeHost = m_drawingArea->layerTreeHost())
2547            layerTreeHost->deviceOrPageScaleFactorChanged();
2548        send(Messages::WebPageProxy::PageScaleFactorDidChange(floatBoundedScale));
2549    }
2550
2551    if (!hasSetPageScale && m_isInStableState) {
2552        m_page->setPageScaleFactor(floatBoundedScale, scrollPosition, true);
2553        hasSetPageScale = true;
2554    }
2555
2556    FrameView& frameView = *m_page->mainFrame().view();
2557    if (scrollPosition != frameView.scrollPosition())
2558        m_dynamicSizeUpdateHistory.clear();
2559
2560    frameView.setUnobscuredContentSize(visibleContentRectUpdateInfo.unobscuredRect().size());
2561
2562    double horizontalVelocity = visibleContentRectUpdateInfo.horizontalVelocity();
2563    double verticalVelocity = visibleContentRectUpdateInfo.verticalVelocity();
2564    double scaleChangeRate = visibleContentRectUpdateInfo.scaleChangeRate();
2565    adjustVelocityDataForBoundedScale(horizontalVelocity, verticalVelocity, scaleChangeRate, visibleContentRectUpdateInfo.scale(), boundedScale);
2566
2567    frameView.setScrollVelocity(horizontalVelocity, verticalVelocity, scaleChangeRate, visibleContentRectUpdateInfo.timestamp());
2568
2569    if (m_isInStableState)
2570        m_page->mainFrame().view()->setCustomFixedPositionLayoutRect(enclosingIntRect(visibleContentRectUpdateInfo.customFixedPositionRect()));
2571
2572    if (!visibleContentRectUpdateInfo.isChangingObscuredInsetsInteractively())
2573        frameView.setCustomSizeForResizeEvent(expandedIntSize(visibleContentRectUpdateInfo.unobscuredRectInScrollViewCoordinates().size()));
2574
2575    frameView.setConstrainsScrollingToContentEdge(false);
2576    frameView.setScrollOffset(scrollPosition);
2577    frameView.setConstrainsScrollingToContentEdge(true);
2578}
2579
2580void WebPage::willStartUserTriggeredZooming()
2581{
2582    m_userHasChangedPageScaleFactor = true;
2583}
2584
2585#if ENABLE(WEBGL)
2586WebCore::WebGLLoadPolicy WebPage::webGLPolicyForURL(WebFrame*, const String&)
2587{
2588    return WKShouldBlockWebGL() ? WebGLBlockCreation : WebGLAllowCreation;
2589}
2590
2591WebCore::WebGLLoadPolicy WebPage::resolveWebGLPolicyForURL(WebFrame*, const String&)
2592{
2593    return WKShouldBlockWebGL() ? WebGLBlockCreation : WebGLAllowCreation;
2594}
2595#endif
2596
2597void WebPage::zoomToRect(FloatRect rect, double minimumScale, double maximumScale)
2598{
2599    send(Messages::WebPageProxy::ZoomToRect(rect, minimumScale, maximumScale));
2600}
2601
2602void WebPage::dispatchAsynchronousTouchEvents(const Vector<WebTouchEvent, 1>& queue)
2603{
2604    bool ignored;
2605    for (const WebTouchEvent& event : queue)
2606        dispatchTouchEvent(event, ignored);
2607}
2608
2609void WebPage::computePagesForPrintingAndStartDrawingToPDF(uint64_t frameID, const PrintInfo& printInfo, uint32_t firstPage, PassRefPtr<Messages::WebPage::ComputePagesForPrintingAndStartDrawingToPDF::DelayedReply> reply)
2610{
2611    Vector<WebCore::IntRect> pageRects;
2612    double totalScaleFactor = 1;
2613    computePagesForPrintingImpl(frameID, printInfo, pageRects, totalScaleFactor);
2614    std::size_t pageCount = pageRects.size();
2615    reply->send(WTF::move(pageRects), totalScaleFactor);
2616
2617    RetainPtr<CFMutableDataRef> pdfPageData;
2618    drawPagesToPDFImpl(frameID, printInfo, firstPage, pageCount - firstPage, pdfPageData);
2619    send(Messages::WebPageProxy::DidFinishDrawingPagesToPDF(IPC::DataReference(CFDataGetBytePtr(pdfPageData.get()), CFDataGetLength(pdfPageData.get()))));
2620}
2621
2622void WebPage::contentSizeCategoryDidChange(const String& contentSizeCategory)
2623{
2624    RenderThemeIOS::setContentSizeCategory(contentSizeCategory);
2625    Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
2626}
2627
2628String WebPage::platformUserAgent(const URL&) const
2629{
2630    return String();
2631}
2632
2633} // namespace WebKit
2634
2635#endif // PLATFORM(IOS)
2636