| /* |
| * Copyright (C) 2011, 2012 Research In Motion Limited. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include "DOMSupport.h" |
| |
| #include "FloatQuad.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "HTMLFormElement.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLTextAreaElement.h" |
| #include "Node.h" |
| #include "Range.h" |
| #include "RenderObject.h" |
| #include "RenderText.h" |
| #include "RenderTextControl.h" |
| #include "TextIterator.h" |
| #include "VisiblePosition.h" |
| #include "VisibleSelection.h" |
| |
| #include "htmlediting.h" |
| #include "visible_units.h" |
| |
| #include <limits> |
| |
| #include <wtf/text/WTFString.h> |
| |
| using WTF::Vector; |
| |
| using namespace WebCore; |
| |
| namespace BlackBerry { |
| namespace WebKit { |
| namespace DOMSupport { |
| |
| void visibleTextQuads(const VisibleSelection& selection, Vector<FloatQuad>& quads) |
| { |
| if (!selection.isRange()) |
| return; |
| |
| // Make sure that both start and end have valid nodes associated otherwise |
| // this can crash. See PR 220628. |
| if (!selection.start().anchorNode() || !selection.end().anchorNode()) |
| return; |
| |
| visibleTextQuads(*(selection.firstRange()), quads, true /* useSelectionHeight */); |
| } |
| |
| void visibleTextQuads(const Range& range, Vector<FloatQuad>& quads, bool useSelectionHeight) |
| { |
| // Range::textQuads includes hidden text, which we don't want. |
| // To work around this, this is a copy of it which skips hidden elements. |
| Node* startContainer = range.startContainer(); |
| Node* endContainer = range.endContainer(); |
| |
| if (!startContainer || !endContainer) |
| return; |
| |
| Node* stopNode = range.pastLastNode(); |
| for (Node* node = range.firstNode(); node != stopNode; node = node->traverseNextNode()) { |
| RenderObject* r = node->renderer(); |
| if (!r || !r->isText()) |
| continue; |
| |
| if (r->style()->visibility() != VISIBLE) |
| continue; |
| |
| RenderText* renderText = toRenderText(r); |
| int startOffset = node == startContainer ? range.startOffset() : 0; |
| int endOffset = node == endContainer ? range.endOffset() : std::numeric_limits<int>::max(); |
| renderText->absoluteQuadsForRange(quads, startOffset, endOffset, useSelectionHeight); |
| } |
| } |
| |
| bool isTextInputElement(Element* element) |
| { |
| return element->isTextFormControl() |
| || element->hasTagName(HTMLNames::textareaTag) |
| || element->isContentEditable(); |
| } |
| |
| bool isPasswordElement(const Element* element) |
| { |
| return element && element->hasTagName(HTMLNames::inputTag) |
| && static_cast<const HTMLInputElement*>(element)->isPasswordField(); |
| } |
| |
| WTF::String inputElementText(Element* element) |
| { |
| if (!element) |
| return WTF::String(); |
| |
| WTF::String elementText; |
| if (element->hasTagName(HTMLNames::inputTag)) { |
| const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element); |
| elementText = inputElement->value(); |
| } else if (element->hasTagName(HTMLNames::textareaTag)) { |
| const HTMLTextAreaElement* inputElement = static_cast<const HTMLTextAreaElement*>(element); |
| elementText = inputElement->value(); |
| } else if (element->isContentEditable()) { |
| RefPtr<Range> rangeForNode = rangeOfContents(element); |
| elementText = rangeForNode.get()->text(); |
| } |
| return elementText; |
| } |
| |
| WTF::String webWorksContext(const WebCore::Element* element) |
| { |
| if (!element) |
| return WTF::String(); |
| |
| DEFINE_STATIC_LOCAL(QualifiedName, webworksContextAttr, (nullAtom, "data-webworks-context", nullAtom)); |
| if (element->fastHasAttribute(webworksContextAttr)) |
| return element->fastGetAttribute(webworksContextAttr); |
| |
| return WTF::String(); |
| } |
| |
| bool isElementTypePlugin(const Element* element) |
| { |
| if (!element) |
| return false; |
| |
| if (element->hasTagName(HTMLNames::objectTag) |
| || element->hasTagName(HTMLNames::embedTag) |
| || element->hasTagName(HTMLNames::appletTag)) |
| return true; |
| |
| return false; |
| } |
| |
| HTMLTextFormControlElement* toTextControlElement(Node* node) |
| { |
| if (!(node && node->isElementNode())) |
| return 0; |
| |
| Element* element = static_cast<Element*>(node); |
| if (!element->isFormControlElement()) |
| return 0; |
| |
| HTMLFormControlElement* formElement = static_cast<HTMLFormControlElement*>(element); |
| if (!formElement->isTextFormControl()) |
| return 0; |
| |
| return static_cast<HTMLTextFormControlElement*>(formElement); |
| } |
| |
| bool isPopupInputField(const Element* element) |
| { |
| return isDateTimeInputField(element) || isColorInputField(element); |
| } |
| |
| bool isDateTimeInputField(const Element* element) |
| { |
| if (!element->hasTagName(HTMLNames::inputTag)) |
| return false; |
| |
| const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element); |
| |
| // The following types have popup's. |
| if (inputElement->isDateField() |
| || inputElement->isDateTimeField() |
| || inputElement->isDateTimeLocalField() |
| || inputElement->isTimeField() |
| || inputElement->isMonthField()) |
| return true; |
| |
| return false; |
| } |
| |
| bool isColorInputField(const Element* element) |
| { |
| if (!element->hasTagName(HTMLNames::inputTag)) |
| return false; |
| |
| const HTMLInputElement* inputElement = static_cast<const HTMLInputElement*>(element); |
| |
| #if ENABLE(INPUT_TYPE_COLOR) |
| if (inputElement->isColorControl()) |
| return true; |
| #endif |
| |
| return false; |
| } |
| |
| // This is a Tristate return to allow us to override name matching when |
| // the attribute is expressly requested for a field. Default indicates |
| // that the setting is On which is the default but not expressly requested |
| // for the element being checked. On indicates that it is directly added |
| // to the element. |
| AttributeState elementSupportsAutocorrect(const Element* element) |
| { |
| DEFINE_STATIC_LOCAL(QualifiedName, autocorrectAttr, (nullAtom, "autocorrect", nullAtom)); |
| return elementAttributeState(element, autocorrectAttr); |
| } |
| |
| AttributeState elementSupportsAutocomplete(const Element* element) |
| { |
| return elementAttributeState(element, HTMLNames::autocompleteAttr); |
| } |
| |
| AttributeState elementSupportsSpellCheck(const Element* element) |
| { |
| return elementAttributeState(element, HTMLNames::spellcheckAttr); |
| } |
| |
| AttributeState elementAttributeState(const Element* element, const QualifiedName& attributeName) |
| { |
| // First we check the input item itself. If the attribute is not defined, |
| // we check its parent form. |
| if (element->fastHasAttribute(attributeName)) { |
| AtomicString attributeString = element->fastGetAttribute(attributeName); |
| if (equalIgnoringCase(attributeString, "off")) |
| return Off; |
| if (equalIgnoringCase(attributeString, "on")) |
| return On; |
| // If we haven't returned, it wasn't set properly. Check the form for an explicit setting |
| // because the attribute was provided, but invalid. |
| } |
| if (element->isFormControlElement()) { |
| const HTMLFormControlElement* formElement = static_cast<const HTMLFormControlElement*>(element); |
| if (formElement->form() && formElement->form()->fastHasAttribute(attributeName)) { |
| AtomicString attributeString = formElement->form()->fastGetAttribute(attributeName); |
| if (equalIgnoringCase(attributeString, "off")) |
| return Off; |
| if (equalIgnoringCase(attributeString, "on")) |
| return On; |
| } |
| } |
| |
| return Default; |
| } |
| |
| // Check if this is an input field that will be focused & require input support. |
| bool isTextBasedContentEditableElement(Element* element) |
| { |
| if (!element) |
| return false; |
| |
| if (element->isReadOnlyNode() || !element->isEnabledFormControl()) |
| return false; |
| |
| if (isPopupInputField(element)) |
| return false; |
| |
| return element->isTextFormControl() || element->isContentEditable(); |
| } |
| |
| IntRect transformedBoundingBoxForRange(const Range& range) |
| { |
| // Based on Range::boundingBox, which does not handle transforms, and |
| // RenderObject::absoluteBoundingBoxRect, which does. |
| IntRect result; |
| Vector<FloatQuad> quads; |
| visibleTextQuads(range, quads); |
| const size_t n = quads.size(); |
| for (size_t i = 0; i < n; ++i) |
| result.unite(quads[i].enclosingBoundingBox()); |
| |
| return result; |
| } |
| |
| VisibleSelection visibleSelectionForInputElement(Element* element) |
| { |
| return visibleSelectionForRangeInputElement(element, 0, inputElementText(element).length()); |
| } |
| |
| VisibleSelection visibleSelectionForRangeInputElement(Element* element, int start, int end) |
| { |
| if (DOMSupport::toTextControlElement(element)) { |
| RenderTextControl* textRender = toRenderTextControl(element->renderer()); |
| if (!textRender) |
| return VisibleSelection(); |
| |
| VisiblePosition startPosition = textRender->visiblePositionForIndex(start); |
| VisiblePosition endPosition; |
| if (start == end) |
| endPosition = startPosition; |
| else |
| endPosition = textRender->visiblePositionForIndex(end); |
| |
| return VisibleSelection(startPosition, endPosition); |
| } |
| |
| // Must be content editable, generate the range. |
| RefPtr<Range> selectionRange = TextIterator::rangeFromLocationAndLength(element, start, end - start); |
| |
| if (!selectionRange) |
| return VisibleSelection(); |
| |
| if (start == end) |
| return VisibleSelection(selectionRange.get()->startPosition(), DOWNSTREAM); |
| |
| VisiblePosition visibleStart(selectionRange->startPosition(), DOWNSTREAM); |
| VisiblePosition visibleEnd(selectionRange->endPosition(), SEL_DEFAULT_AFFINITY); |
| |
| return VisibleSelection(visibleStart, visibleEnd); |
| } |
| |
| Node* DOMContainerNodeForPosition(const Position& position) |
| { |
| Node* nodeAtPos = position.containerNode(); |
| if (nodeAtPos && nodeAtPos->isInShadowTree()) |
| nodeAtPos = nodeAtPos->shadowAncestorNode(); |
| |
| return nodeAtPos; |
| } |
| |
| bool isPositionInNode(Node* node, const Position& position) |
| { |
| if (!node) |
| return false; |
| |
| Node* domNodeAtPos = DOMContainerNodeForPosition(position); |
| if (!domNodeAtPos) |
| return false; |
| |
| int offset = 0; |
| if (domNodeAtPos == position.containerNode()) |
| offset = position.computeOffsetInContainerNode(); |
| |
| RefPtr<Range> rangeForNode = rangeOfContents(node); |
| int ec; |
| |
| return rangeForNode->isPointInRange(domNodeAtPos, offset, ec); |
| } |
| |
| static bool matchesReservedStringEmail(const AtomicString& string) |
| { |
| return string.contains("email", false /* caseSensitive */); |
| } |
| |
| static bool matchesReservedStringUrl(const AtomicString& string) |
| { |
| return equalIgnoringCase("url", string); |
| } |
| |
| bool elementIdOrNameIndicatesEmail(const HTMLInputElement* inputElement) |
| { |
| if (!inputElement) |
| return false; |
| |
| if (matchesReservedStringEmail(inputElement->getIdAttribute())) |
| return true; |
| |
| if (inputElement->fastHasAttribute(HTMLNames::nameAttr)) { |
| if (matchesReservedStringEmail(inputElement->fastGetAttribute(HTMLNames::nameAttr))) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool elementIdOrNameIndicatesUrl(const HTMLInputElement* inputElement) |
| { |
| if (!inputElement) |
| return false; |
| |
| if (matchesReservedStringUrl(inputElement->getIdAttribute())) |
| return true; |
| |
| if (inputElement->fastHasAttribute(HTMLNames::nameAttr)) { |
| if (matchesReservedStringUrl(inputElement->fastGetAttribute(HTMLNames::nameAttr))) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool matchesReservedStringPreventingAutocomplete(const AtomicString& string) |
| { |
| if (matchesReservedStringEmail(string) |
| || matchesReservedStringUrl(string) |
| || string.contains("user", false /* caseSensitive */) |
| || string.contains("name", false /* caseSensitive */) |
| || string.contains("login", false /* caseSensitive */)) |
| return true; |
| |
| return false; |
| } |
| |
| // This checks to see if an input element has a name or id attribute set to |
| // username or email. These are rough checks to avoid major sites that use |
| // login fields as input type=text and auto correction interfers with. |
| bool elementIdOrNameIndicatesNoAutocomplete(const Element* element) |
| { |
| if (!element->hasTagName(HTMLNames::inputTag)) |
| return false; |
| |
| AtomicString idAttribute = element->getIdAttribute(); |
| if (matchesReservedStringPreventingAutocomplete(idAttribute)) |
| return true; |
| |
| if (element->fastHasAttribute(HTMLNames::nameAttr)) { |
| AtomicString nameAttribute = element->fastGetAttribute(HTMLNames::nameAttr); |
| if (matchesReservedStringPreventingAutocomplete(nameAttribute)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool elementPatternIndicatesNumber(const HTMLInputElement* inputElement) |
| { |
| return elementPatternMatches("[0-9]", inputElement); |
| } |
| |
| bool elementPatternIndicatesHexadecimal(const HTMLInputElement* inputElement) |
| { |
| return elementPatternMatches("[0-9a-fA-F]", inputElement); |
| } |
| |
| bool elementPatternMatches(const char* pattern, const HTMLInputElement* inputElement) |
| { |
| WTF::String patternString(pattern); |
| if (!inputElement || patternString.isEmpty()) |
| return false; |
| |
| if (inputElement->fastHasAttribute(HTMLNames::patternAttr)) { |
| WTF::String patternAttribute = inputElement->fastGetAttribute(HTMLNames::patternAttr); |
| if (patternAttribute.startsWith(patternString)) { |
| // The pattern is for hexadecimal, make sure nothing else is permitted. |
| |
| // Check if it was an exact match. |
| if (patternAttribute.length() == patternString.length()) |
| return true; |
| |
| // Check for * |
| if (patternAttribute.length() == patternString.length() + 1 && patternAttribute[patternString.length()] == '*') |
| return true; |
| |
| // Is the regex specifying a character count? |
| if (patternAttribute[patternString.length()] != '{' || !patternAttribute.endsWith("}")) |
| return false; |
| |
| // Make sure the number in the regex is actually a number. |
| unsigned count = 0; |
| patternString = patternString + "{%d}"; |
| return (sscanf(patternAttribute.latin1().data(), patternString.latin1().data() + '\0', &count) == 1) && count > 0; |
| } |
| } |
| return false; |
| } |
| |
| IntPoint convertPointToFrame(const Frame* sourceFrame, const Frame* targetFrame, const IntPoint& point, const bool clampToTargetFrame) |
| { |
| ASSERT(sourceFrame && targetFrame); |
| if (sourceFrame == targetFrame) |
| return point; |
| |
| ASSERT(sourceFrame->view() && targetFrame->view()); |
| ASSERT(targetFrame->tree()); |
| |
| Frame* targetFrameParent = targetFrame->tree()->parent(); |
| IntRect targetFrameRect = targetFrame->view()->frameRect(); |
| IntPoint targetPoint = point; |
| |
| // Convert the target frame rect to source window content coordinates. This is only required |
| // if the parent frame is not the source. If the parent is the source, subframeRect |
| // is already in source content coordinates. |
| if (targetFrameParent != sourceFrame) |
| targetFrameRect = sourceFrame->view()->windowToContents(targetFrameParent->view()->contentsToWindow(targetFrameRect)); |
| |
| // Requested point is outside of target frame, return InvalidPoint. |
| if (clampToTargetFrame && !targetFrameRect.contains(targetPoint)) |
| targetPoint = IntPoint(targetPoint.x() < targetFrameRect.x() ? targetFrameRect.x() : std::min(targetPoint.x(), targetFrameRect.maxX()), |
| targetPoint.y() < targetFrameRect.y() ? targetFrameRect.y() : std::min(targetPoint.y(), targetFrameRect.maxY())); |
| else if (!targetFrameRect.contains(targetPoint)) |
| return InvalidPoint; |
| |
| // Adjust the points to be relative to the target. |
| return targetFrame->view()->windowToContents(sourceFrame->view()->contentsToWindow(targetPoint)); |
| } |
| |
| VisibleSelection visibleSelectionForClosestActualWordStart(const VisibleSelection& selection) |
| { |
| // VisibleSelection validation has a special case when the caret is at the end of a paragraph where |
| // it selects the paragraph marker. As well, if the position is at the end of a word, it will select |
| // only the space between words. We want to select an actual word so we move the selection to |
| // the start of the leftmost word if the character after the selection point is whitespace. |
| if (selection.selectionType() != VisibleSelection::RangeSelection && isWhitespace(selection.visibleStart().characterAfter())) { |
| VisibleSelection leftSelection(previousWordPosition(selection.start())); |
| bool leftSelectionIsOnWord = !isWhitespace(leftSelection.visibleStart().characterAfter()); |
| |
| VisibleSelection rangeSelection(endOfWord(leftSelection.start()), selection.visibleStart()); |
| int leftDistance = TextIterator::rangeLength(rangeSelection.toNormalizedRange().get()); |
| |
| VisibleSelection rightSelection(nextWordPosition(selection.start())); |
| rightSelection = previousWordPosition(rightSelection.start()); |
| bool rightSelectionIsOnWord = !isWhitespace(rightSelection.visibleStart().characterAfter()); |
| |
| rangeSelection = VisibleSelection(rightSelection.visibleStart(), selection.visibleStart()); |
| int rightDistance = TextIterator::rangeLength(rangeSelection.toNormalizedRange().get()); |
| |
| // Make sure we found an actual word. If not, return the original selection. |
| if (!leftSelectionIsOnWord && !rightSelectionIsOnWord) |
| return selection; |
| |
| if (!rightSelectionIsOnWord || (leftSelectionIsOnWord && leftDistance < rightDistance)) { |
| // Left is closer or right is invalid. |
| return leftSelection; |
| } |
| |
| // Right is closer or equal, or left was invalid. |
| return rightSelection; |
| } |
| |
| // No adjustment required. |
| return selection; |
| } |
| |
| // This function is copied from WebCore/page/Page.cpp. |
| Frame* incrementFrame(Frame* curr, bool forward, bool wrapFlag) |
| { |
| return forward |
| ? curr->tree()->traverseNextWithWrap(wrapFlag) |
| : curr->tree()->traversePreviousWithWrap(wrapFlag); |
| } |
| |
| PassRefPtr<Range> trimWhitespaceFromRange(VisiblePosition startPosition, VisiblePosition endPosition) |
| { |
| if (isEmptyRangeOrAllSpaces(startPosition, endPosition)) |
| return VisibleSelection(endPosition, endPosition).toNormalizedRange(); |
| |
| while (isWhitespace(startPosition.characterAfter())) |
| startPosition = startPosition.next(); |
| |
| while (isWhitespace(endPosition.characterBefore())) |
| endPosition = endPosition.previous(); |
| |
| return VisibleSelection(startPosition, endPosition).toNormalizedRange(); |
| } |
| |
| bool isEmptyRangeOrAllSpaces(VisiblePosition startPosition, VisiblePosition endPosition) |
| { |
| if (startPosition == endPosition) |
| return true; |
| |
| while (isWhitespace(startPosition.characterAfter())) { |
| startPosition = startPosition.next(); |
| |
| if (startPosition == endPosition) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool isFixedPositionOrHasFixedPositionAncestor(RenderObject* renderer) |
| { |
| RenderObject* currentRenderer = renderer; |
| while (currentRenderer) { |
| |
| if (currentRenderer->isOutOfFlowPositioned() && currentRenderer->style()->position() == FixedPosition) |
| return true; |
| |
| currentRenderer = currentRenderer->parent(); |
| } |
| |
| return false; |
| } |
| |
| } // DOMSupport |
| } // WebKit |
| } // BlackBerry |