| /* |
| * Copyright (C) 2012 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "WebPage.h" |
| |
| #import "EditorState.h" |
| #import "InteractionInformationAtPosition.h" |
| #import "WebChromeClient.h" |
| #import "WebCoreArgumentCoders.h" |
| #import "WebFrame.h" |
| #import "WebPageProxyMessages.h" |
| #import "WebProcess.h" |
| #import "WKGestureTypes.h" |
| #import <CoreText/CTFont.h> |
| #import <WebCore/Chrome.h> |
| #import <WebCore/Element.h> |
| #import <WebCore/EventHandler.h> |
| #import <WebCore/FocusController.h> |
| #import <WebCore/FloatQuad.h> |
| #import <WebCore/Frame.h> |
| #import <WebCore/FrameView.h> |
| #import <WebCore/HitTestResult.h> |
| #import <WebCore/HTMLElementTypeHelpers.h> |
| #import <WebCore/MainFrame.h> |
| #import <WebCore/Node.h> |
| #import <WebCore/NotImplemented.h> |
| #import <WebCore/Page.h> |
| #import <WebCore/PlatformKeyboardEvent.h> |
| #import <WebCore/PlatformMouseEvent.h> |
| #import <WebCore/RenderImage.h> |
| #import <WebCore/SharedBuffer.h> |
| #import <WebCore/TextIterator.h> |
| #import <WebCore/VisibleUnits.h> |
| #import <WebCore/WebEvent.h> |
| |
| using namespace WebCore; |
| |
| namespace WebKit { |
| |
| void WebPage::platformInitialize() |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::platformPreferencesDidChange(const WebPreferencesStore&) |
| { |
| notImplemented(); |
| } |
| |
| bool WebPage::executeKeypressCommandsInternal(const Vector<WebCore::KeypressCommand>&, KeyboardEvent*) |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| bool WebPage::handleEditingKeyboardEvent(KeyboardEvent* event, bool) |
| { |
| bool eventWasHandled = false; |
| bool sendResult = WebProcess::shared().parentProcessConnection()->sendSync(Messages::WebPageProxy::InterpretKeyEvent(editorState(), event->keyEvent()->type() == PlatformKeyboardEvent::Char), |
| Messages::WebPageProxy::InterpretKeyEvent::Reply(eventWasHandled), m_pageID); |
| if (!sendResult) |
| return false; |
| |
| return eventWasHandled; |
| } |
| |
| void WebPage::sendComplexTextInputToPlugin(uint64_t, const String&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::setComposition(const String& text, Vector<WebCore::CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd) |
| { |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| |
| if (frame.selection().selection().isContentEditable()) |
| frame.editor().setComposition(text, underlines, selectionStart, selectionEnd); |
| } |
| |
| void WebPage::confirmComposition() |
| { |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| frame.editor().confirmComposition(); |
| } |
| |
| void WebPage::cancelComposition(EditorState&) |
| { |
| notImplemented(); |
| } |
| |
| static PassRefPtr<Range> convertToRange(Frame* frame, NSRange nsrange) |
| { |
| if (nsrange.location > INT_MAX) |
| return 0; |
| if (nsrange.length > INT_MAX || nsrange.location + nsrange.length > INT_MAX) |
| nsrange.length = INT_MAX - nsrange.location; |
| |
| // our critical assumption is that we are only called by input methods that |
| // concentrate on a given area containing the selection |
| // We have to do this because of text fields and textareas. The DOM for those is not |
| // directly in the document DOM, so serialization is problematic. Our solution is |
| // to use the root editable element of the selection start as the positional base. |
| // That fits with AppKit's idea of an input context. |
| return TextIterator::rangeFromLocationAndLength(frame->selection().rootEditableElementOrDocumentElement(), nsrange.location, nsrange.length); |
| } |
| |
| void WebPage::insertText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd) |
| { |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| |
| if (replacementRangeStart != NSNotFound) { |
| RefPtr<Range> replacementRange = convertToRange(&frame, NSMakeRange(replacementRangeStart, replacementRangeEnd - replacementRangeStart)); |
| if (replacementRange) |
| frame.selection().setSelection(VisibleSelection(replacementRange.get(), SEL_DEFAULT_AFFINITY)); |
| } |
| |
| if (!frame.editor().hasComposition()) { |
| // An insertText: might be handled by other responders in the chain if we don't handle it. |
| // One example is space bar that results in scrolling down the page. |
| frame.editor().insertText(text, 0); |
| } else |
| frame.editor().confirmComposition(text); |
| } |
| |
| void WebPage::insertDictatedText(const String&, uint64_t, uint64_t, const Vector<WebCore::DictationAlternative>&, bool&, EditorState&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::getMarkedRange(uint64_t&, uint64_t&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::getSelectedRange(uint64_t&, uint64_t&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::getAttributedSubstringFromRange(uint64_t, uint64_t, AttributedString&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::characterIndexForPoint(IntPoint, uint64_t&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::firstRectForCharacterRange(uint64_t, uint64_t, WebCore::IntRect&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::executeKeypressCommands(const Vector<WebCore::KeypressCommand>&, bool&, EditorState&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::performDictionaryLookupAtLocation(const FloatPoint&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::performDictionaryLookupForSelection(Frame*, const VisibleSelection&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::performDictionaryLookupForRange(Frame*, Range*, NSDictionary *) |
| { |
| notImplemented(); |
| } |
| |
| bool WebPage::performNonEditingBehaviorForSelector(const String&, WebCore::KeyboardEvent*) |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| bool WebPage::performDefaultBehaviorForKeyEvent(const WebKeyboardEvent&) |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| void WebPage::registerUIProcessAccessibilityTokens(const IPC::DataReference&, const IPC::DataReference&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::readSelectionFromPasteboard(const String&, bool&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::getStringSelectionForPasteboard(String&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::getDataSelectionForPasteboard(const String, SharedMemory::Handle&, uint64_t&) |
| { |
| notImplemented(); |
| } |
| |
| WKAccessibilityWebPageObject* WebPage::accessibilityRemoteObject() |
| { |
| notImplemented(); |
| return 0; |
| } |
| |
| bool WebPage::platformHasLocalDataForURL(const WebCore::URL&) |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| String WebPage::cachedSuggestedFilenameForURL(const URL&) |
| { |
| notImplemented(); |
| return String(); |
| } |
| |
| String WebPage::cachedResponseMIMETypeForURL(const URL&) |
| { |
| notImplemented(); |
| return String(); |
| } |
| |
| PassRefPtr<SharedBuffer> WebPage::cachedResponseDataForURL(const URL&) |
| { |
| notImplemented(); |
| return 0; |
| } |
| |
| bool WebPage::platformCanHandleRequest(const WebCore::ResourceRequest&) |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| void WebPage::shouldDelayWindowOrderingEvent(const WebKit::WebMouseEvent&, bool&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::acceptsFirstMouse(int, const WebKit::WebMouseEvent&, bool&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::computePagesForPrintingPDFDocument(uint64_t, const PrintInfo&, Vector<IntRect>&) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::drawPagesToPDFFromPDFDocument(CGContextRef, PDFDocument *, const PrintInfo&, uint32_t, uint32_t) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::advanceToNextMisspelling(bool) |
| { |
| notImplemented(); |
| } |
| |
| void WebPage::handleTap(const IntPoint& point) |
| { |
| Frame& mainframe = m_page->mainFrame(); |
| FloatPoint adjustedPoint; |
| mainframe.nodeRespondingToClickEvents(point, adjustedPoint); |
| IntPoint roundedAdjustedPoint = roundedIntPoint(adjustedPoint); |
| |
| mainframe.eventHandler().mouseMoved(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, NoButton, PlatformEvent::MouseMoved, 0, false, false, false, false, 0)); |
| mainframe.eventHandler().handleMousePressEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MousePressed, 1, false, false, false, false, 0)); |
| mainframe.eventHandler().handleMouseReleaseEvent(PlatformMouseEvent(roundedAdjustedPoint, roundedAdjustedPoint, LeftButton, PlatformEvent::MouseReleased, 1, false, false, false, false, 0)); |
| } |
| |
| void WebPage::tapHighlightAtPosition(uint64_t requestID, const FloatPoint& position) |
| { |
| Frame& mainframe = m_page->mainFrame(); |
| FloatPoint adjustedPoint; |
| Node* node = mainframe.nodeRespondingToClickEvents(position, adjustedPoint); |
| |
| if (!node) |
| return; |
| |
| RenderObject *renderer = node->renderer(); |
| |
| Vector<FloatQuad> quads; |
| if (renderer) { |
| renderer->absoluteQuads(quads); |
| Color highlightColor = node->computedStyle()->tapHighlightColor(); |
| |
| RoundedRect::Radii borderRadii; |
| if (renderer->isBox()) { |
| RenderBox* box = toRenderBox(renderer); |
| borderRadii = box->borderRadii(); |
| } |
| |
| send(Messages::WebPageProxy::DidGetTapHighlightGeometries(requestID, highlightColor, quads, roundedIntSize(borderRadii.topLeft()), roundedIntSize(borderRadii.topRight()), roundedIntSize(borderRadii.bottomLeft()), roundedIntSize(borderRadii.bottomRight()))); |
| } |
| } |
| |
| void WebPage::blurAssistedNode() |
| { |
| if (m_assistedNode && m_assistedNode->isElementNode()) |
| toElement(m_assistedNode.get())->blur(); |
| } |
| |
| static FloatQuad innerFrameQuad(Frame* frame, Node* assistedNode) |
| { |
| frame->document()->updateLayoutIgnorePendingStylesheets(); |
| RenderObject* renderer; |
| if (assistedNode->hasTagName(HTMLNames::textareaTag) || assistedNode->hasTagName(HTMLNames::inputTag)) |
| renderer = assistedNode->renderer(); |
| else |
| renderer = assistedNode->rootEditableElement()->renderer(); |
| |
| if (!renderer) |
| return FloatQuad(); |
| |
| RenderStyle& style = renderer->style(); |
| IntRect boundingBox = renderer->absoluteBoundingBoxRect(true /* use transforms*/); |
| |
| boundingBox.move(style.borderLeftWidth(), style.borderTopWidth()); |
| boundingBox.setWidth(boundingBox.width() - style.borderLeftWidth() - style.borderRightWidth()); |
| boundingBox.setHeight(boundingBox.height() - style.borderBottomWidth() - style.borderTopWidth()); |
| |
| return FloatQuad(boundingBox); |
| } |
| |
| static IntPoint constrainPoint(const IntPoint& point, Frame* frame, Node* assistedNode) |
| { |
| const int DEFAULT_CONSTRAIN_INSET = 2; |
| IntRect innerFrame = innerFrameQuad(frame, assistedNode).enclosingBoundingBox(); |
| IntPoint constrainedPoint = point; |
| |
| int minX = innerFrame.x() + DEFAULT_CONSTRAIN_INSET; |
| int maxX = innerFrame.maxX() - DEFAULT_CONSTRAIN_INSET; |
| int minY = innerFrame.y() + DEFAULT_CONSTRAIN_INSET; |
| int maxY = innerFrame.maxY() - DEFAULT_CONSTRAIN_INSET; |
| |
| if (point.x() < minX) |
| constrainedPoint.setX(minX); |
| else if (point.x() > maxX) |
| constrainedPoint.setX(maxX); |
| |
| if (point.y() < minY) |
| constrainedPoint.setY(minY); |
| else if (point.y() >= maxY) |
| constrainedPoint.setY(maxY); |
| |
| return constrainedPoint; |
| } |
| |
| void WebPage::selectWithGesture(const IntPoint& point, uint32_t granularity, uint32_t gestureType, uint32_t gestureState, uint64_t callbackID) |
| { |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| FloatPoint adjustedPoint(point); |
| |
| IntPoint constrainedPoint = m_assistedNode ? constrainPoint(point, &frame, m_assistedNode.get()) : point; |
| VisiblePosition position = frame.visiblePositionForPoint(constrainedPoint); |
| if (position.isNull()) { |
| send(Messages::WebPageProxy::GestureCallback(point, gestureType, gestureState, 0, callbackID)); |
| return; |
| } |
| RefPtr<Range> range; |
| switch (static_cast<WKGestureType>(gestureType)) { |
| case WKGestureOneFingerTap: |
| { |
| VisiblePosition result; |
| // move the the position at the end of the word |
| if (atBoundaryOfGranularity(position, LineGranularity, DirectionForward)) { |
| // Don't cross line boundaries. |
| result = position; |
| } else if (withinTextUnitOfGranularity(position, WordGranularity, DirectionForward)) { |
| // The position lies within a word. |
| RefPtr<Range> wordRange = enclosingTextUnitOfGranularity(position, WordGranularity, DirectionForward); |
| |
| result = wordRange->startPosition(); |
| if (distanceBetweenPositions(position, result) > 1) |
| result = wordRange->endPosition(); |
| } else if (atBoundaryOfGranularity(position, WordGranularity, DirectionBackward)) { |
| // The position is at the end of a word. |
| result = position; |
| } else { |
| // The position is not within a word. |
| // Go to the next boundary. |
| result = positionOfNextBoundaryOfGranularity(position, WordGranularity, DirectionForward); |
| |
| // If there is no such boundary we go to the end of the element. |
| if (result.isNull()) |
| result = endOfEditableContent(position); |
| } |
| if (result.isNotNull()) |
| range = Range::create(*frame.document(), result, result); |
| if (range) |
| m_shouldReturnWordAtSelection = true; |
| } |
| break; |
| |
| case WKGestureLoupe: |
| range = Range::create(*frame.document(), position, position); |
| break; |
| |
| case WKGestureTapAndAHalf: |
| switch (static_cast<WKGestureRecognizerState>(gestureState)) { |
| case WKGestureRecognizerStateBegan: |
| range = wordRangeFromPosition(position); |
| m_currentWordRange = Range::create(*frame.document(), range->startPosition(), range->endPosition()); |
| break; |
| case WKGestureRecognizerStateChanged: |
| { |
| range = Range::create(*frame.document(), m_currentWordRange->startPosition(), m_currentWordRange->endPosition()); |
| ExceptionCode ec; |
| if (position < range->startPosition()) |
| range->setStart(position.deepEquivalent(), ec); |
| if (position > range->endPosition()) |
| range->setEnd(position.deepEquivalent(), ec); |
| } |
| break; |
| case WKGestureRecognizerStateEnded: |
| case WKGestureRecognizerStateCancelled: |
| m_currentWordRange = nullptr; |
| break; |
| case WKGestureRecognizerStateFailed: |
| case WKGestureRecognizerStatePossible: |
| ASSERT_NOT_REACHED(); |
| } |
| break; |
| |
| case WKGestureOneFingerDoubleTap: |
| if (atBoundaryOfGranularity(position, LineGranularity, DirectionForward)) { |
| // Double-tap at end of line only places insertion point there. |
| // This helps to get the callout for pasting at ends of lines, |
| // paragraphs, and documents. |
| range = Range::create(*frame.document(), position, position); |
| } else |
| range = wordRangeFromPosition(position); |
| break; |
| |
| case WKGestureTwoFingerSingleTap: |
| // Single tap with two fingers selects the entire paragraph. |
| range = enclosingTextUnitOfGranularity(position, ParagraphGranularity, DirectionForward); |
| break; |
| |
| case WKGestureOneFingerTripleTap: |
| if (atBoundaryOfGranularity(position, LineGranularity, DirectionForward)) { |
| // Triple-tap at end of line only places insertion point there. |
| // This helps to get the callout for pasting at ends of lines, |
| // paragraphs, and documents. |
| range = Range::create(*frame.document(), position, position); |
| } else |
| range = enclosingTextUnitOfGranularity(position, ParagraphGranularity, DirectionForward); |
| break; |
| |
| case WKGestureMakeWebSelection: |
| // FIXME: Here we should implement the logic for block selections. |
| range = wordRangeFromPosition(position); |
| break; |
| |
| default: |
| break; |
| } |
| if (range) |
| frame.selection().setSelectedRange(range.get(), position.affinity(), true); |
| |
| send(Messages::WebPageProxy::GestureCallback(point, gestureType, gestureState, 0, callbackID)); |
| m_shouldReturnWordAtSelection = false; |
| } |
| |
| static PassRefPtr<Range> rangeForPosition(Frame* frame, const VisiblePosition& position, bool baseIsStart) |
| { |
| RefPtr<Range> range; |
| VisiblePosition result = position; |
| |
| if (baseIsStart) { |
| VisiblePosition selectionStart = frame->selection().selection().visibleStart(); |
| bool wouldFlip = position <= selectionStart; |
| |
| if (wouldFlip) |
| result = selectionStart.next(); |
| |
| if (result.isNotNull()) |
| range = Range::create(*frame->document(), selectionStart, result); |
| } else { |
| VisiblePosition selectionEnd = frame->selection().selection().visibleEnd(); |
| bool wouldFlip = position >= selectionEnd; |
| |
| if (wouldFlip) |
| result = selectionEnd.previous(); |
| |
| if (result.isNotNull()) |
| range = Range::create(*frame->document(), result, selectionEnd); |
| } |
| |
| return range.release(); |
| } |
| |
| static PassRefPtr<Range> rangeAtWordBoundaryForPosition(Frame* frame, const VisiblePosition& position, bool baseIsStart, SelectionDirection direction) |
| { |
| SelectionDirection sameDirection = baseIsStart ? DirectionForward : DirectionBackward; |
| SelectionDirection oppositeDirection = baseIsStart ? DirectionBackward : DirectionForward; |
| VisiblePosition base = baseIsStart ? frame->selection().selection().visibleStart() : frame->selection().selection().visibleEnd(); |
| VisiblePosition extent = baseIsStart ? frame->selection().selection().visibleEnd() : frame->selection().selection().visibleStart(); |
| VisiblePosition initialExtent = position; |
| |
| if (atBoundaryOfGranularity(extent, WordGranularity, sameDirection)) { |
| // This is a word boundary. Leave selection where it is. |
| return 0; |
| } |
| |
| if (atBoundaryOfGranularity(extent, WordGranularity, oppositeDirection)) { |
| // This is a word boundary in the wrong direction. Nudge the selection to a character before proceeding. |
| extent = baseIsStart ? extent.previous() : extent.next(); |
| } |
| |
| // Extend to the boundary of the word. |
| |
| VisiblePosition wordBoundary = positionOfNextBoundaryOfGranularity(extent, WordGranularity, sameDirection); |
| if (wordBoundary.isNotNull() |
| && atBoundaryOfGranularity(wordBoundary, WordGranularity, sameDirection) |
| && initialExtent != wordBoundary) { |
| extent = wordBoundary; |
| return (base < extent) ? Range::create(*frame->document(), base, extent) : Range::create(*frame->document(), extent, base); |
| } |
| // Conversely, if the initial extent equals the current word boundary, then |
| // run the rest of this function to see if the selection should extend |
| // the other direction to the other word. |
| |
| // If this is where the extent was initially, then iterate in the other direction in the document until we hit the next word. |
| while (extent.isNotNull() |
| && !atBoundaryOfGranularity(extent, WordGranularity, sameDirection) |
| && extent != base |
| && !atBoundaryOfGranularity(extent, LineBoundary, sameDirection) |
| && !atBoundaryOfGranularity(extent, LineBoundary, oppositeDirection)) { |
| extent = baseIsStart ? extent.next() : extent.previous(); |
| } |
| |
| // Don't let the smart extension make the extent equal the base. |
| // Expand out to word boundary. |
| if (extent.isNull() || extent == base) |
| extent = wordBoundary; |
| if (extent.isNull()) |
| return 0; |
| |
| return (base < extent) ? Range::create(*frame->document(), base, extent) : Range::create(*frame->document(), extent, base); |
| } |
| |
| void WebPage::updateSelectionWithTouches(const IntPoint& point, uint32_t touches, bool baseIsStart, uint64_t callbackID) |
| { |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| VisiblePosition position = frame.visiblePositionForPoint(point); |
| if (position.isNull()) { |
| send(Messages::WebPageProxy::TouchesCallback(point, touches, callbackID)); |
| return; |
| } |
| |
| RefPtr<Range> range; |
| VisiblePosition result; |
| |
| switch (static_cast<WKSelectionTouch>(touches)) { |
| case WKSelectionTouchStarted: |
| case WKSelectionTouchEndedNotMoving: |
| break; |
| |
| case WKSelectionTouchEnded: |
| if (frame.selection().selection().isContentEditable()) { |
| result = closestWordBoundaryForPosition(position); |
| if (result.isNotNull()) |
| range = Range::create(*frame.document(), result, result); |
| } else |
| range = rangeForPosition(&frame, position, baseIsStart); |
| break; |
| |
| case WKSelectionTouchEndedMovingForward: |
| range = rangeAtWordBoundaryForPosition(&frame, position, baseIsStart, DirectionForward); |
| break; |
| |
| case WKSelectionTouchEndedMovingBackward: |
| range = rangeAtWordBoundaryForPosition(&frame, position, baseIsStart, DirectionBackward); |
| break; |
| |
| case WKSelectionTouchMoved: |
| range = rangeForPosition(&frame, position, baseIsStart); |
| break; |
| } |
| if (range) |
| frame.selection().setSelectedRange(range.get(), position.affinity(), true); |
| |
| send(Messages::WebPageProxy::TouchesCallback(point, touches, callbackID)); |
| } |
| |
| void WebPage::selectWithTwoTouches(const WebCore::IntPoint& from, const WebCore::IntPoint& to, uint32_t gestureType, uint32_t gestureState, uint64_t callbackID) |
| { |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| VisiblePosition fromPosition = frame.visiblePositionForPoint(from); |
| VisiblePosition toPosition = frame.visiblePositionForPoint(to); |
| RefPtr<Range> range; |
| if (fromPosition.isNotNull() && toPosition.isNotNull()) { |
| if (fromPosition < toPosition) |
| range = Range::create(*frame.document(), fromPosition, toPosition); |
| else |
| range = Range::create(*frame.document(), toPosition, fromPosition); |
| frame.selection().setSelectedRange(range.get(), fromPosition.affinity(), true); |
| } |
| |
| // We can use the same callback for the gestures with one point. |
| send(Messages::WebPageProxy::GestureCallback(from, gestureType, gestureState, 0, callbackID)); |
| } |
| |
| void WebPage::extendSelection(uint32_t granularity) |
| { |
| // For the moment we handle only WordGranularity. |
| if (granularity != WordGranularity) |
| return; |
| |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| ASSERT(frame.selection().isCaret()); |
| VisiblePosition position = frame.selection().selection().start(); |
| frame.selection().setSelectedRange(wordRangeFromPosition(position).get(), position.affinity(), true); |
| } |
| |
| void WebPage::requestAutocorrectionData(const String& textForAutocorrection, uint64_t callbackID) |
| { |
| RefPtr<Range> range; |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| ASSERT(frame.selection().isCaret()); |
| VisiblePosition position = frame.selection().selection().start(); |
| Vector<SelectionRect> selectionRects; |
| |
| range = wordRangeFromPosition(position); |
| String textForRange = plainText(range.get()); |
| const unsigned maxSearchAttempts = 5; |
| for (size_t i = 0; i < maxSearchAttempts && textForRange != textForAutocorrection; ++i) |
| { |
| position = range->startPosition().previous(); |
| if (position.isNull() || position == range->startPosition()) |
| break; |
| range = Range::create(*frame.document(), wordRangeFromPosition(position)->startPosition(), range->endPosition()); |
| textForRange = plainText(range.get()); |
| } |
| if (textForRange == textForAutocorrection) |
| range->collectSelectionRects(selectionRects); |
| |
| Vector<FloatRect> rectsForText; |
| rectsForText.resize(selectionRects.size()); |
| |
| for (size_t i = 0; i < selectionRects.size(); i++) |
| rectsForText[i] = selectionRects[i].rect(); |
| |
| bool multipleFonts = false; |
| CTFontRef font = nil; |
| if (const SimpleFontData* fontData = frame.editor().fontForSelection(multipleFonts)) |
| font = fontData->getCTFont(); |
| |
| CGFloat fontSize = CTFontGetSize(font); |
| uint64_t fontTraits = CTFontGetSymbolicTraits(font); |
| RetainPtr<NSString> fontName = adoptNS((NSString *)CTFontCopyFamilyName(font)); |
| send(Messages::WebPageProxy::AutocorrectionDataCallback(rectsForText, fontName.get(), fontSize, fontTraits, callbackID)); |
| } |
| |
| void WebPage::applyAutocorrection(const String& correction, const String& originalText, uint64_t callbackID) |
| { |
| RefPtr<Range> range; |
| Frame& frame = m_page->focusController().focusedOrMainFrame(); |
| ASSERT(frame.selection().isCaret()); |
| VisiblePosition position = frame.selection().selection().start(); |
| |
| range = wordRangeFromPosition(position); |
| String textForRange = plainText(range.get()); |
| if (textForRange != originalText) { |
| for (size_t i = 0; i < originalText.length(); ++i) |
| position = position.previous(); |
| if (position.isNull()) |
| position = startOfDocument(static_cast<Node*>(frame.document()->documentElement())); |
| range = Range::create(*frame.document(), position, frame.selection().selection().start()); |
| if (range) |
| textForRange = (range) ? plainText(range.get()) : emptyString(); |
| unsigned loopCount = 0; |
| const unsigned maxPositionsAttempts = 10; |
| while (textForRange.length() && textForRange.length() > originalText.length() && loopCount < maxPositionsAttempts) { |
| position = position.next(); |
| if (position.isNotNull() && position >= frame.selection().selection().start()) |
| range = NULL; |
| else |
| range = Range::create(*frame.document(), position, frame.selection().selection().start()); |
| textForRange = (range) ? plainText(range.get()) : emptyString(); |
| loopCount++; |
| } |
| } |
| if (textForRange != originalText) { |
| send(Messages::WebPageProxy::StringCallback(String(), callbackID)); |
| return; |
| } |
| |
| frame.selection().setSelectedRange(range.get(), UPSTREAM, true); |
| if (correction.length()) |
| frame.editor().insertText(correction, 0); |
| else |
| frame.editor().deleteWithDirection(DirectionBackward, CharacterGranularity, false, true); |
| |
| send(Messages::WebPageProxy::StringCallback(correction, callbackID)); |
| } |
| |
| static void computeAutocorrectionContext(Frame& frame, String& contextBefore, String& markedText, String& selectedText, String& contextAfter, uint64_t& location, uint64_t& length) |
| { |
| RefPtr<Range> range; |
| VisiblePosition startPosition = frame.selection().selection().start(); |
| VisiblePosition endPosition = frame.selection().selection().end(); |
| location = NSNotFound; |
| length = 0; |
| const unsigned minContextWordCount = 3; |
| const unsigned minContextLenght = 12; |
| const unsigned maxContextLength = 30; |
| |
| if (frame.selection().isRange()) |
| selectedText = plainText(frame.selection().selection().toNormalizedRange().get()); |
| |
| if (frame.editor().hasComposition()) { |
| range = Range::create(*frame.document(), frame.editor().compositionRange()->startPosition(), startPosition); |
| String markedTextBefore; |
| if (range) |
| markedTextBefore = plainText(range.get()); |
| range = Range::create(*frame.document(), endPosition, frame.editor().compositionRange()->endPosition()); |
| String markedTextAfter; |
| if (range) |
| markedTextAfter = plainText(range.get()); |
| markedText = markedTextBefore + selectedText + markedTextAfter; |
| if (!markedText.isEmpty()) { |
| location = markedTextBefore.length(); |
| length = selectedText.length(); |
| } |
| } else { |
| if (startPosition != startOfEditableContent(startPosition)) { |
| VisiblePosition currentPosition = startPosition; |
| VisiblePosition previousPosition; |
| unsigned totalContextLength = 0; |
| for (unsigned i = 0; i < minContextWordCount; ++i) { |
| if (contextBefore.length() >= minContextLenght) |
| break; |
| previousPosition = startOfWord(positionOfNextBoundaryOfGranularity(currentPosition, WordGranularity, DirectionBackward)); |
| if (previousPosition.isNull()) |
| break; |
| String currentWord = plainText(Range::create(*frame.document(), previousPosition, currentPosition).get()); |
| totalContextLength += currentWord.length(); |
| if (totalContextLength >= maxContextLength) |
| break; |
| currentPosition = previousPosition; |
| } |
| if (currentPosition.isNotNull() && currentPosition != startPosition) { |
| contextBefore = plainText(Range::create(*frame.document(), currentPosition, startPosition).get()); |
| if (atBoundaryOfGranularity(currentPosition, ParagraphGranularity, DirectionBackward)) |
| contextBefore = ASCIILiteral("\n ") + contextBefore; |
| } |
| } |
| |
| if (endPosition != endOfEditableContent(endPosition)) { |
| VisiblePosition nextPosition; |
| if (!atBoundaryOfGranularity(endPosition, WordGranularity, DirectionForward) && withinTextUnitOfGranularity(endPosition, WordGranularity, DirectionForward)) |
| nextPosition = positionOfNextBoundaryOfGranularity(endPosition, WordGranularity, DirectionForward); |
| if (nextPosition.isNotNull()) |
| contextAfter = plainText(Range::create(*frame.document(), endPosition, nextPosition).get()); |
| } |
| } |
| } |
| |
| void WebPage::requestAutocorrectionContext(uint64_t callbackID) |
| { |
| String contextBefore; |
| String contextAfter; |
| String selectedText; |
| String markedText; |
| uint64_t location; |
| uint64_t length; |
| |
| computeAutocorrectionContext(m_page->focusController().focusedOrMainFrame(), contextBefore, markedText, selectedText, contextAfter, location, length); |
| |
| send(Messages::WebPageProxy::AutocorrectionContextCallback(contextBefore, markedText, selectedText, contextAfter, location, length, callbackID)); |
| } |
| |
| void WebPage::getAutocorrectionContext(String& contextBefore, String& markedText, String& selectedText, String& contextAfter, uint64_t& location, uint64_t& length) |
| { |
| computeAutocorrectionContext(m_page->focusController().focusedOrMainFrame(), contextBefore, markedText, selectedText, contextAfter, location, length); |
| } |
| |
| void WebPage::getPositionInformation(const IntPoint& point, InteractionInformationAtPosition& info) |
| { |
| FloatPoint adjustedPoint; |
| Node* hitNode = m_page->mainFrame().nodeRespondingToClickEvents(point, adjustedPoint); |
| |
| info.point = point; |
| info.nodeAtPositionIsAssistedNode = (hitNode == m_assistedNode); |
| if (hitNode) { |
| info.clickableElementName = hitNode->nodeName(); |
| |
| const HTMLElement* element = toHTMLElement(hitNode); |
| if (!element) |
| return; |
| |
| if (element->renderer() && element->renderer()->isRenderImage()) { |
| URL url = toRenderImage(element->renderer())->cachedImage()->url(); |
| if (!url.string().isNull()) |
| info.url = url.string(); |
| } else if (element->isLink()) |
| info.url = element->getAttribute(HTMLNames::hrefAttr).string(); |
| } else { |
| Frame& frame = m_page->mainFrame(); |
| hitNode = frame.eventHandler().hitTestResultAtPoint((point), HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent).innerNode(); |
| if (hitNode->isTextNode()) { |
| VisiblePosition position = frame.visiblePositionForPoint(point); |
| RefPtr<Range> range = wordRangeFromPosition(position); |
| if (range) |
| range->collectSelectionRects(info.selectionRects); |
| } else { |
| // FIXME: implement the logic for the block selection. |
| } |
| |
| } |
| } |
| |
| void WebPage::requestPositionInformation(const IntPoint& point) |
| { |
| InteractionInformationAtPosition info; |
| |
| getPositionInformation(point, info); |
| send(Messages::WebPageProxy::DidReceivePositionInformation(info)); |
| } |
| |
| void WebPage::elementDidFocus(WebCore::Node* node) |
| { |
| m_assistedNode = node; |
| if (node->hasTagName(WebCore::HTMLNames::inputTag) || node->hasTagName(WebCore::HTMLNames::textareaTag) || node->hasEditableStyle()) |
| send(Messages::WebPageProxy::StartAssistingNode(WebCore::IntRect(), true, true)); |
| } |
| |
| void WebPage::elementDidBlur(WebCore::Node* node) |
| { |
| if (m_assistedNode == node) { |
| send(Messages::WebPageProxy::StopAssistingNode()); |
| m_assistedNode = 0; |
| } |
| } |
| |
| void WebPage::didFinishScrolling(const WebCore::FloatPoint& contentOffset) |
| { |
| m_page->mainFrame().view()->setScrollOffset(WebCore::IntPoint(contentOffset)); |
| } |
| |
| void WebPage::didFinishZooming(float newScale) |
| { |
| m_page->setPageScaleFactor(newScale, m_page->mainFrame().view()->scrollPosition()); |
| } |
| |
| } // namespace WebKit |