blob: 40421c329fad48a0c6908fbbf2ef59e6170f0a11 [file] [log] [blame]
/*
* 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 "HTMLFrameOwnerElement.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLTextAreaElement.h"
#include "Node.h"
#include "NodeTraversal.h"
#include "Range.h"
#include "RenderObject.h"
#include "RenderText.h"
#include "RenderTextControl.h"
#include "TextIterator.h"
#include "VisiblePosition.h"
#include "VisibleSelection.h"
#include "VisibleUnits.h"
#include "htmlediting.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 && node != stopNode; node = NodeTraversal::next(node)) {
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 isShadowHostTextInputElement(Node* node)
{
if (!node)
return false;
Element* element = node->shadowHost();
return element && DOMSupport::isTextInputElement(element);
}
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 = toElement(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);
}
bool isElementReadOnly(const Element* element)
{
return element->fastHasAttribute(HTMLNames::readonlyAttr);
}
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;
}
bool elementHasContinuousSpellCheckingEnabled(const PassRefPtr<WebCore::Element> element)
{
return element && element->document()->frame() && element->document()->frame()->editor().isContinuousSpellCheckingEnabled();
}
// 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->isDisabledFormControl())
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);
}
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) {
int leftDistance = 0;
int rightDistance = 0;
VisibleSelection leftSelection(previousWordPosition(selection.start()));
bool leftSelectionIsOnWord = !isWhitespace(leftSelection.visibleStart().characterAfter()) && leftSelection.start().containerNode() == selection.start().containerNode();
if (leftSelectionIsOnWord) {
VisibleSelection rangeSelection(endOfWord(leftSelection.start()), selection.visibleStart());
leftDistance = TextIterator::rangeLength(rangeSelection.toNormalizedRange().get());
}
VisibleSelection rightSelection = previousWordPosition(nextWordPosition(selection.start()));
bool rightSelectionIsOnWord = !isWhitespace(rightSelection.visibleStart().characterAfter()) && rightSelection.start().containerNode() == selection.start().containerNode();
if (rightSelectionIsOnWord) {
VisibleSelection rangeSelection = VisibleSelection(rightSelection.visibleStart(), selection.visibleStart());
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;
}
int offsetFromStartOfBlock(const VisiblePosition offset)
{
RefPtr<Range> range = makeRange(startOfBlock(offset), offset);
if (!range)
return -1;
return range->text().latin1().length();
}
VisibleSelection visibleSelectionForFocusedBlock(Element* element)
{
int textLength = inputElementText(element).length();
if (DOMSupport::toTextControlElement(element)) {
RenderTextControl* textRender = toRenderTextControl(element->renderer());
if (!textRender)
return VisibleSelection();
VisiblePosition startPosition = textRender->visiblePositionForIndex(0);
VisiblePosition endPosition;
if (textLength)
endPosition = textRender->visiblePositionForIndex(textLength);
else
endPosition = startPosition;
return VisibleSelection(startPosition, endPosition);
}
// Must be content editable, generate the range.
RefPtr<Range> selectionRange = TextIterator::rangeFromLocationAndLength(element, 0, textLength);
if (!selectionRange)
return VisibleSelection();
if (!textLength)
return VisibleSelection(selectionRange->startPosition(), DOWNSTREAM);
return VisibleSelection(selectionRange->startPosition(), selectionRange->endPosition());
}
// 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(PassRefPtr<Range> range)
{
return trimWhitespaceFromRange(VisiblePosition(range->startPosition()), VisiblePosition(range->endPosition()));
}
PassRefPtr<Range> trimWhitespaceFromRange(VisiblePosition startPosition, VisiblePosition endPosition)
{
if (startPosition == endPosition || isRangeTextAllWhitespace(startPosition, endPosition))
return 0;
while (isWhitespace(startPosition.characterAfter()))
startPosition = startPosition.next();
while (isWhitespace(endPosition.characterBefore()))
endPosition = endPosition.previous();
return makeRange(startPosition, endPosition);
}
bool isRangeTextAllWhitespace(VisiblePosition startPosition, VisiblePosition endPosition)
{
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;
// Check if the current frame is an iframe. If so, continue checking with the iframe's owner element.
if (!currentRenderer->parent() && currentRenderer->isRenderView() && currentRenderer->frame() && currentRenderer->frame()->ownerElement()) {
currentRenderer = currentRenderer->frame()->ownerElement()->renderer();
continue;
}
currentRenderer = currentRenderer->parent();
}
return false;
}
Element* selectionContainerElement(const VisibleSelection& selection)
{
if (!selection.isRange())
return 0;
Node* startContainer = 0;
if (selection.firstRange())
startContainer = selection.firstRange()->startContainer();
if (!startContainer)
return 0;
Element* element = 0;
if (startContainer->isInShadowTree())
element = startContainer->shadowHost();
else if (startContainer->isElementNode())
element = toElement(startContainer);
else
element = startContainer->parentElement();
return element;
}
BlackBerry::Platform::RequestedHandlePosition elementHandlePositionAttribute(const WebCore::Element* element)
{
BlackBerry::Platform::RequestedHandlePosition position = BlackBerry::Platform::SmartPlacement;
if (!element)
return position;
DEFINE_STATIC_LOCAL(QualifiedName, qualifiedAttrNameForHandlePosition, (nullAtom, "data-blackberry-text-selection-handle-position", nullAtom));
AtomicString attributeString;
if (element->fastHasAttribute(qualifiedAttrNameForHandlePosition))
attributeString = element->fastGetAttribute(qualifiedAttrNameForHandlePosition);
if (attributeString.isNull() || attributeString.isEmpty())
return position;
if (equalIgnoringCase(attributeString, "above"))
position = BlackBerry::Platform::Above;
else if (equalIgnoringCase(attributeString, "below"))
position = BlackBerry::Platform::Below;
return position;
}
bool isElementAndDocumentAttached(const WebCore::Element* element)
{
return element && element->attached() && element->document() && element->document()->attached();
}
} // DOMSupport
} // WebKit
} // BlackBerry