X-Git-Url: https://git.webkit.org/?p=WebKit-https.git;a=blobdiff_plain;f=WebCore%2Fkhtml%2Fediting%2Fhtmlediting.cpp;h=3707381b2fa764d343ddf30484d701b9c1f4bd87;hp=f3fafb83165686964e97e654cc45c531bd75f4f4;hb=10405f2105e17b0c0b11bfb72750d82876fbe65a;hpb=950c43bc90689ee414052ec4584d3813a194a01b diff --git a/WebCore/khtml/editing/htmlediting.cpp b/WebCore/khtml/editing/htmlediting.cpp index f3fafb831656..3707381b2fa7 100644 --- a/WebCore/khtml/editing/htmlediting.cpp +++ b/WebCore/khtml/editing/htmlediting.cpp @@ -120,7 +120,7 @@ static inline bool nextCharacterIsCollapsibleWhitespace(const Position &pos) static const int spacesPerTab = 4; -static bool isTableStructureNode(const NodeImpl *node) +bool isTableStructureNode(const NodeImpl *node) { RenderObject *r = node->renderer(); return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol())); @@ -142,47 +142,6 @@ static DOMString &nonBreakingSpaceString() return nonBreakingSpaceString; } -static DOMString &styleSpanClassString() -{ - static DOMString styleSpanClassString = AppleStyleSpanClass; - return styleSpanClassString; -} - -static bool isEmptyStyleSpan(const NodeImpl *node) -{ - if (!node || !node->isHTMLElement() || node->id() != ID_SPAN) - return false; - - const HTMLElementImpl *elem = static_cast(node); - CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl(); - return (!inlineStyleDecl || inlineStyleDecl->length() == 0) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString(); -} - -static bool isStyleSpan(const NodeImpl *node) -{ - if (!node || !node->isHTMLElement()) - return false; - - const HTMLElementImpl *elem = static_cast(node); - return elem->id() == ID_SPAN && elem->getAttribute(ATTR_CLASS) == styleSpanClassString(); -} - -static bool isEmptyFontTag(const NodeImpl *node) -{ - if (!node || node->id() != ID_FONT) - return false; - - const ElementImpl *elem = static_cast(node); - NamedAttrMapImpl *map = elem->attributes(true); // true for read-only - return (!map || map->length() == 1) && elem->getAttribute(ATTR_CLASS) == styleSpanClassString(); -} - -static DOMString &blockPlaceholderClassString() -{ - static DOMString blockPlaceholderClassString = "khtml-block-placeholder"; - return blockPlaceholderClassString; -} - static DOMString &matchNearestBlockquoteColorString() { static DOMString matchNearestBlockquoteColorString = "match"; @@ -195,1953 +154,217 @@ static void derefNodesInList(QPtrList &list) it.current()->deref(); } -static int maxRangeOffset(NodeImpl *n) -{ - if (DOM::offsetInCharacters(n->nodeType())) - return n->maxOffset(); - - if (n->isElementNode()) - return n->childNodeCount(); - - return 1; -} - -static int maxDeepOffset(NodeImpl *n) -{ - if (n->isAtomicNode()) - return n->caretMaxOffset(); - - if (n->isElementNode()) - return n->childNodeCount(); - - return 1; -} - -static void debugPosition(const char *prefix, const Position &pos) -{ - if (!prefix) - prefix = ""; - if (pos.isNull()) - LOG(Editing, "%s ", prefix); - else - LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset()); -} - -static void debugNode(const char *prefix, const NodeImpl *node) -{ - if (!prefix) - prefix = ""; - if (!node) - LOG(Editing, "%s ", prefix); - else - LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node); -} - -//------------------------------------------------------------------------------------------ -// StyleChange - -StyleChange::StyleChange(CSSStyleDeclarationImpl *style, ELegacyHTMLStyles usesLegacyStyles) - : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles) -{ - init(style, Position()); -} - -StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position, ELegacyHTMLStyles usesLegacyStyles) - : m_applyBold(false), m_applyItalic(false), m_usesLegacyStyles(usesLegacyStyles) -{ - init(style, position); -} - -void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position) -{ - style->ref(); - CSSMutableStyleDeclarationImpl *mutableStyle = style->makeMutable(); - mutableStyle->ref(); - style->deref(); - - QString styleText(""); - - QValueListConstIterator end; - for (QValueListConstIterator it = mutableStyle->valuesIterator(); it != end; ++it) { - const CSSProperty *property = &*it; - - // If position is empty or the position passed in already has the - // style, just move on. - if (position.isNotNull() && currentlyHasStyle(position, property)) - continue; - - // If needed, figure out if this change is a legacy HTML style change. - if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property)) - continue; - - // Add this property - - if (property->id() == CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT) { - // we have to special-case text decorations - CSSProperty alteredProperty = CSSProperty(CSS_PROP_TEXT_DECORATION, property->value(), property->isImportant()); - styleText += alteredProperty.cssText().string(); - } else { - styleText += property->cssText().string(); - } - } - - mutableStyle->deref(); - - // Save the result for later - m_cssStyle = styleText.stripWhiteSpace(); -} - -StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode) -{ - return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles; -} - -bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty *property) -{ - if (!property || !property->value()) { - return false; - } - - DOMString valueText(property->value()->cssText()); - switch (property->id()) { - case CSS_PROP_FONT_WEIGHT: - if (strcasecmp(valueText, "bold") == 0) { - m_applyBold = true; - return true; - } - break; - case CSS_PROP_FONT_STYLE: - if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) { - m_applyItalic = true; - return true; - } - break; - case CSS_PROP_COLOR: { - QColor color(CSSParser::parseColor(valueText)); - m_applyFontColor = color.name(); - return true; - } - case CSS_PROP_FONT_FAMILY: - m_applyFontFace = valueText; - return true; - case CSS_PROP_FONT_SIZE: - if (property->value()->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) { - CSSPrimitiveValueImpl *value = static_cast(property->value()); - float number = value->getFloatValue(CSSPrimitiveValue::CSS_PX); - if (number <= 9) - m_applyFontSize = "1"; - else if (number <= 10) - m_applyFontSize = "2"; - else if (number <= 13) - m_applyFontSize = "3"; - else if (number <= 16) - m_applyFontSize = "4"; - else if (number <= 18) - m_applyFontSize = "5"; - else if (number <= 24) - m_applyFontSize = "6"; - else - m_applyFontSize = "7"; - // Huge quirk in Microsft Entourage is that they understand CSS font-size, but also write - // out legacy 1-7 values in font tags (I guess for mailers that are not CSS-savvy at all, - // like Eudora). Yes, they write out *both*. We need to write out both as well. Return false. - return false; - } - else { - // Can't make sense of the number. Put no font size. - return true; - } - } - return false; -} - -bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property) -{ - ASSERT(pos.isNotNull()); - CSSComputedStyleDeclarationImpl *style = pos.computedStyle(); - ASSERT(style); - style->ref(); - CSSValueImpl *value = style->getPropertyCSSValue(property->id(), DoNotUpdateLayout); - style->deref(); - if (!value) - return false; - value->ref(); - bool result = strcasecmp(value->cssText(), property->value()->cssText()) == 0; - value->deref(); - return result; -} - -//------------------------------------------------------------------------------------------ -// CompositeEditCommand - -CompositeEditCommand::CompositeEditCommand(DocumentImpl *document) - : EditCommand(document) -{ -} - -void CompositeEditCommand::doUnapply() -{ - if (m_cmds.count() == 0) { - return; - } - - for (int i = m_cmds.count() - 1; i >= 0; --i) - m_cmds[i]->unapply(); - - setState(NotApplied); -} - -void CompositeEditCommand::doReapply() -{ - if (m_cmds.count() == 0) { - return; - } - - for (QValueList::ConstIterator it = m_cmds.begin(); it != m_cmds.end(); ++it) - (*it)->reapply(); - - setState(Applied); -} - -// -// sugary-sweet convenience functions to help create and apply edit commands in composite commands -// -void CompositeEditCommand::applyCommandToComposite(EditCommandPtr &cmd) -{ - cmd.setStartingSelection(endingSelection()); - cmd.setEndingSelection(endingSelection()); - cmd.setParent(this); - cmd.apply(); - m_cmds.append(cmd); -} - -void CompositeEditCommand::applyStyle(CSSStyleDeclarationImpl *style, EditAction editingAction) -{ - EditCommandPtr cmd(new ApplyStyleCommand(document(), style, editingAction)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::insertParagraphSeparator() -{ - EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document())); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::insertNodeBefore(NodeImpl *insertChild, NodeImpl *refChild) -{ - ASSERT(refChild->id() != ID_BODY); - EditCommandPtr cmd(new InsertNodeBeforeCommand(document(), insertChild, refChild)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::insertNodeAfter(NodeImpl *insertChild, NodeImpl *refChild) -{ - ASSERT(refChild->id() != ID_BODY); - if (refChild->parentNode()->lastChild() == refChild) { - appendNode(insertChild, refChild->parentNode()); - } - else { - ASSERT(refChild->nextSibling()); - insertNodeBefore(insertChild, refChild->nextSibling()); - } -} - -void CompositeEditCommand::insertNodeAt(NodeImpl *insertChild, NodeImpl *refChild, long offset) -{ - if (refChild->hasChildNodes() || (refChild->renderer() && refChild->renderer()->isBlockFlow())) { - NodeImpl *child = refChild->firstChild(); - for (long i = 0; child && i < offset; i++) - child = child->nextSibling(); - if (child) - insertNodeBefore(insertChild, child); - else - appendNode(insertChild, refChild); - } - else if (refChild->caretMinOffset() >= offset) { - insertNodeBefore(insertChild, refChild); - } - else if (refChild->isTextNode() && refChild->caretMaxOffset() > offset) { - splitTextNode(static_cast(refChild), offset); - insertNodeBefore(insertChild, refChild); - } - else { - insertNodeAfter(insertChild, refChild); - } -} - -void CompositeEditCommand::appendNode(NodeImpl *appendChild, NodeImpl *parent) -{ - EditCommandPtr cmd(new AppendNodeCommand(document(), appendChild, parent)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::removeFullySelectedNode(NodeImpl *node) -{ - if (isTableStructureNode(node) || node == node->rootEditableElement()) { - // Do not remove an element of table structure; remove its contents. - // Likewise for the root editable element. - NodeImpl *child = node->firstChild(); - while (child) { - NodeImpl *remove = child; - child = child->nextSibling(); - removeFullySelectedNode(remove); - } - } - else { - removeNode(node); - } -} - -void CompositeEditCommand::removeChildrenInRange(NodeImpl *node, int from, int to) -{ - NodeImpl *nodeToRemove = node->childNode(from); - for (int i = from; i < to; i++) { - ASSERT(nodeToRemove); - NodeImpl *next = nodeToRemove->nextSibling(); - removeNode(nodeToRemove); - nodeToRemove = next; - } -} - -void CompositeEditCommand::removeNode(NodeImpl *removeChild) -{ - EditCommandPtr cmd(new RemoveNodeCommand(document(), removeChild)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::removeNodePreservingChildren(NodeImpl *removeChild) -{ - EditCommandPtr cmd(new RemoveNodePreservingChildrenCommand(document(), removeChild)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::splitTextNode(TextImpl *text, long offset) -{ - EditCommandPtr cmd(new SplitTextNodeCommand(document(), text, offset)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::splitElement(ElementImpl *element, NodeImpl *atChild) -{ - EditCommandPtr cmd(new SplitElementCommand(document(), element, atChild)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::mergeIdenticalElements(DOM::ElementImpl *first, DOM::ElementImpl *second) -{ - EditCommandPtr cmd(new MergeIdenticalElementsCommand(document(), first, second)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::wrapContentsInDummySpan(DOM::ElementImpl *element) -{ - EditCommandPtr cmd(new WrapContentsInDummySpanCommand(document(), element)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::splitTextNodeContainingElement(DOM::TextImpl *text, long offset) -{ - EditCommandPtr cmd(new SplitTextNodeContainingElementCommand(document(), text, offset)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::joinTextNodes(TextImpl *text1, TextImpl *text2) -{ - EditCommandPtr cmd(new JoinTextNodesCommand(document(), text1, text2)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::inputText(const DOMString &text, bool selectInsertedText) -{ - InsertTextCommand *impl = new InsertTextCommand(document()); - EditCommandPtr cmd(impl); - applyCommandToComposite(cmd); - impl->input(text, selectInsertedText); -} - -void CompositeEditCommand::insertTextIntoNode(TextImpl *node, long offset, const DOMString &text) -{ - EditCommandPtr cmd(new InsertIntoTextNode(document(), node, offset, text)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::deleteTextFromNode(TextImpl *node, long offset, long count) -{ - EditCommandPtr cmd(new DeleteFromTextNodeCommand(document(), node, offset, count)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::replaceTextInNode(TextImpl *node, long offset, long count, const DOMString &replacementText) -{ - EditCommandPtr deleteCommand(new DeleteFromTextNodeCommand(document(), node, offset, count)); - applyCommandToComposite(deleteCommand); - EditCommandPtr insertCommand(new InsertIntoTextNode(document(), node, offset, replacementText)); - applyCommandToComposite(insertCommand); -} - -void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete) -{ - if (endingSelection().isRange()) { - EditCommandPtr cmd(new DeleteSelectionCommand(document(), smartDelete, mergeBlocksAfterDelete)); - applyCommandToComposite(cmd); - } -} - -void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete) -{ - if (selection.isRange()) { - EditCommandPtr cmd(new DeleteSelectionCommand(document(), selection, smartDelete, mergeBlocksAfterDelete)); - applyCommandToComposite(cmd); - } -} - -void CompositeEditCommand::removeCSSProperty(CSSStyleDeclarationImpl *decl, int property) -{ - EditCommandPtr cmd(new RemoveCSSPropertyCommand(document(), decl, property)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::removeNodeAttribute(ElementImpl *element, int attribute) -{ - DOMString value = element->getAttribute(attribute); - if (value.isEmpty()) - return; - EditCommandPtr cmd(new RemoveNodeAttributeCommand(document(), element, attribute)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::setNodeAttribute(ElementImpl *element, int attribute, const DOMString &value) -{ - EditCommandPtr cmd(new SetNodeAttributeCommand(document(), element, attribute, value)); - applyCommandToComposite(cmd); -} - -void CompositeEditCommand::rebalanceWhitespace() -{ - Selection selection = endingSelection(); - if (selection.isCaretOrRange()) { - EditCommandPtr startCmd(new RebalanceWhitespaceCommand(document(), endingSelection().start())); - applyCommandToComposite(startCmd); - if (selection.isRange()) { - EditCommandPtr endCmd(new RebalanceWhitespaceCommand(document(), endingSelection().end())); - applyCommandToComposite(endCmd); - } - } -} - -void CompositeEditCommand::deleteInsignificantText(TextImpl *textNode, int start, int end) -{ - if (!textNode || !textNode->renderer() || start >= end) - return; - - RenderText *textRenderer = static_cast(textNode->renderer()); - InlineTextBox *box = textRenderer->firstTextBox(); - if (!box) { - // whole text node is empty - removeNode(textNode); - return; - } - - long length = textNode->length(); - if (start >= length || end > length) - return; - - int removed = 0; - InlineTextBox *prevBox = 0; - DOMStringImpl *str = 0; - - // This loop structure works to process all gaps preceding a box, - // and also will look at the gap after the last box. - while (prevBox || box) { - int gapStart = prevBox ? prevBox->m_start + prevBox->m_len : 0; - if (end < gapStart) - // No more chance for any intersections - break; - - int gapEnd = box ? box->m_start : length; - bool indicesIntersect = start <= gapEnd && end >= gapStart; - int gapLen = gapEnd - gapStart; - if (indicesIntersect && gapLen > 0) { - gapStart = kMax(gapStart, start); - gapEnd = kMin(gapEnd, end); - if (!str) { - str = textNode->string()->substring(start, end - start); - str->ref(); - } - // remove text in the gap - str->remove(gapStart - start - removed, gapLen); - removed += gapLen; - } - - prevBox = box; - if (box) - box = box->nextTextBox(); - } - - if (str) { - // Replace the text between start and end with our pruned version. - if (str->l > 0) { - replaceTextInNode(textNode, start, end - start, str); - } - else { - // Assert that we are not going to delete all of the text in the node. - // If we were, that should have been done above with the call to - // removeNode and return. - ASSERT(start > 0 || (unsigned long)end - start < textNode->length()); - deleteTextFromNode(textNode, start, end - start); - } - str->deref(); - } -} - -void CompositeEditCommand::deleteInsignificantText(const Position &start, const Position &end) -{ - if (start.isNull() || end.isNull()) - return; - - if (RangeImpl::compareBoundaryPoints(start, end) >= 0) - return; - - NodeImpl *node = start.node(); - while (node) { - NodeImpl *next = node->traverseNextNode(); - - if (node->isTextNode()) { - TextImpl *textNode = static_cast(node); - bool isStartNode = node == start.node(); - bool isEndNode = node == end.node(); - int startOffset = isStartNode ? start.offset() : 0; - int endOffset = isEndNode ? end.offset() : textNode->length(); - deleteInsignificantText(textNode, startOffset, endOffset); - } - - if (node == end.node()) - break; - node = next; - } -} - -void CompositeEditCommand::deleteInsignificantTextDownstream(const DOM::Position &pos) -{ - Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream(); - deleteInsignificantText(pos, end); -} - -NodeImpl *CompositeEditCommand::appendBlockPlaceholder(NodeImpl *node) -{ - if (!node) - return NULL; - - ASSERT(node->renderer() && node->renderer()->isBlockFlow()); - - NodeImpl *placeholder = createBlockPlaceholderElement(document()); - appendNode(placeholder, node); - return placeholder; -} - -NodeImpl *CompositeEditCommand::insertBlockPlaceholder(const Position &pos) -{ - if (pos.isNull()) - return NULL; - - ASSERT(pos.node()->renderer() && pos.node()->renderer()->isBlockFlow()); - - NodeImpl *placeholder = createBlockPlaceholderElement(document()); - insertNodeAt(placeholder, pos.node(), pos.offset()); - return placeholder; -} - -NodeImpl *CompositeEditCommand::addBlockPlaceholderIfNeeded(NodeImpl *node) -{ - if (!node) - return false; - - document()->updateLayout(); - - RenderObject *renderer = node->renderer(); - if (!renderer || !renderer->isBlockFlow()) - return false; - - // append the placeholder to make sure it follows - // any unrendered blocks - if (renderer->height() == 0) { - return appendBlockPlaceholder(node); - } - - return NULL; -} - -bool CompositeEditCommand::removeBlockPlaceholder(NodeImpl *node) -{ - NodeImpl *placeholder = findBlockPlaceholder(node); - if (placeholder) { - removeNode(placeholder); - return true; - } - return false; -} - -NodeImpl *CompositeEditCommand::findBlockPlaceholder(NodeImpl *node) -{ - if (!node) - return 0; - - document()->updateLayout(); - - RenderObject *renderer = node->renderer(); - if (!renderer || !renderer->isBlockFlow()) - return 0; - - for (NodeImpl *checkMe = node; checkMe; checkMe = checkMe->traverseNextNode(node)) { - if (checkMe->isElementNode()) { - ElementImpl *element = static_cast(checkMe); - if (element->enclosingBlockFlowElement() == node && - element->getAttribute(ATTR_CLASS) == blockPlaceholderClassString()) { - return element; - } - } - } - - return 0; -} - -void CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position &pos) -{ - if (pos.isNull()) - return; - - document()->updateLayout(); - - VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY); - VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos)); - VisiblePosition visibleParagraphEnd(endOfParagraph(visiblePos, IncludeLineBreak)); - Position paragraphStart = visibleParagraphStart.deepEquivalent().upstream(); - Position paragraphEnd = visibleParagraphEnd.deepEquivalent().upstream(); - - // Perform some checks to see if we need to perform work in this function. - if (paragraphStart.node()->isBlockFlow()) { - if (paragraphEnd.node()->isBlockFlow()) { - if (!paragraphEnd.node()->isAncestor(paragraphStart.node())) { - // If the paragraph end is a descendant of paragraph start, then we need to run - // the rest of this function. If not, we can bail here. - return; - } - } - else if (paragraphEnd.node()->enclosingBlockFlowElement() != paragraphStart.node()) { - // The paragraph end is in another block that is an ancestor of the paragraph start. - // We can bail as we have a full block to work with. - ASSERT(paragraphStart.node()->isAncestor(paragraphEnd.node()->enclosingBlockFlowElement())); - return; - } - else if (isEndOfDocument(visibleParagraphEnd)) { - // At the end of the document. We can bail here as well. - return; - } - } - - // Create the block to insert. Most times, this will be a shallow clone of the block containing - // the start of the selection (the start block), except for two cases: - // 1) When the start block is a body element. - // 2) When the start block is a mail blockquote and we are not in a position to insert - // the new block as a peer of the start block. This prevents creating an unwanted - // additional level of quoting. - NodeImpl *startBlock = paragraphStart.node()->enclosingBlockFlowElement(); - NodeImpl *newBlock = 0; - if (startBlock->id() == ID_BODY || (isMailBlockquote(startBlock) && paragraphStart.node() != startBlock)) - newBlock = createDefaultParagraphElement(document()); - else - newBlock = startBlock->cloneNode(false); - - NodeImpl *moveNode = paragraphStart.node(); - if (paragraphStart.offset() >= paragraphStart.node()->caretMaxOffset()) - moveNode = moveNode->traverseNextNode(); - NodeImpl *endNode = paragraphEnd.node(); - - if (paragraphStart.node()->id() == ID_BODY) { - insertNodeAt(newBlock, paragraphStart.node(), 0); - } - else if (paragraphStart.node()->id() == ID_BR) { - insertNodeAfter(newBlock, paragraphStart.node()); - } - else { - insertNodeBefore(newBlock, paragraphStart.upstream().node()); - } - - while (moveNode && !moveNode->isBlockFlow()) { - NodeImpl *next = moveNode->traverseNextSibling(); - removeNode(moveNode); - appendNode(moveNode, newBlock); - if (moveNode == endNode) - break; - moveNode = next; - } -} - -static bool isSpecialElement(NodeImpl *n) -{ - if (!n->isHTMLElement()) - return false; - - if (n->id() == ID_A && n->isLink()) - return true; - - if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL) - return true; - - RenderObject *renderer = n->renderer(); - - if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE)) - return true; - - if (renderer && renderer->style()->isFloating()) - return true; - - if (renderer && renderer->style()->position() != STATIC) - return true; - - return false; -} - -// This version of the function is meant to be called on positions in a document fragment, -// so it does not check for a root editable element, it is assumed these nodes will be put -// somewhere editable in the future -static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos) -{ - VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - - for (NodeImpl *n = pos.node(); n; n = n->parentNode()) { - if (VisiblePosition(n, 0, DOWNSTREAM) != vPos) - return false; - if (isSpecialElement(n)) - return true; - } - - return false; -} - -static bool isFirstVisiblePositionInSpecialElement(const Position& pos) -{ - VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - - for (NodeImpl *n = pos.node(); n; n = n->parentNode()) { - if (VisiblePosition(n, 0, DOWNSTREAM) != vPos) - return false; - if (n->rootEditableElement() == NULL) - return false; - if (isSpecialElement(n)) - return true; - } - - return false; -} - -static Position positionBeforeNode(NodeImpl *node) -{ - return Position(node->parentNode(), node->nodeIndex()); -} - -static Position positionBeforeContainingSpecialElement(const Position& pos) -{ - ASSERT(isFirstVisiblePositionInSpecialElement(pos)); - - VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - - NodeImpl *outermostSpecialElement = NULL; - - for (NodeImpl *n = pos.node(); n; n = n->parentNode()) { - if (VisiblePosition(n, 0, DOWNSTREAM) != vPos) - break; - if (n->rootEditableElement() == NULL) - break; - if (isSpecialElement(n)) - outermostSpecialElement = n; - } - - ASSERT(outermostSpecialElement); - - Position result = positionBeforeNode(outermostSpecialElement); - if (result.isNull() || !result.node()->rootEditableElement()) - return pos; - - return result; -} - -static bool isLastVisiblePositionInSpecialElement(const Position& pos) -{ - // make sure to get a range-compliant version of the position - Position rangePos = VisiblePosition(pos, DOWNSTREAM).position(); - - VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM); - - for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) { - if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos) - return false; - if (n->rootEditableElement() == NULL) - return false; - if (isSpecialElement(n)) - return true; - } - - return false; -} - -static Position positionAfterNode(NodeImpl *node) -{ - return Position(node->parentNode(), node->nodeIndex() + 1); -} - -static Position positionAfterContainingSpecialElement(const Position& pos) -{ - ASSERT(isLastVisiblePositionInSpecialElement(pos)); - - // make sure to get a range-compliant version of the position - Position rangePos = VisiblePosition(pos, DOWNSTREAM).position(); - - VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM); - - NodeImpl *outermostSpecialElement = NULL; - - for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) { - if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos) - break; - if (n->rootEditableElement() == NULL) - break; - if (isSpecialElement(n)) - outermostSpecialElement = n; - } - - ASSERT(outermostSpecialElement); - - Position result = positionAfterNode(outermostSpecialElement); - if (result.isNull() || !result.node()->rootEditableElement()) - return pos; - - return result; -} - -static Position positionOutsideContainingSpecialElement(const Position &pos) -{ - if (isFirstVisiblePositionInSpecialElement(pos)) { - return positionBeforeContainingSpecialElement(pos); - } else if (isLastVisiblePositionInSpecialElement(pos)) { - return positionAfterContainingSpecialElement(pos); - } - - return pos; -} - -static Position positionBeforePossibleContainingSpecialElement(const Position &pos) -{ - if (isFirstVisiblePositionInSpecialElement(pos)) { - return positionBeforeContainingSpecialElement(pos); - } - - return pos; -} - -static Position positionAfterPossibleContainingSpecialElement(const Position &pos) -{ - if (isLastVisiblePositionInSpecialElement(pos)) { - return positionAfterContainingSpecialElement(pos); - } - - return pos; -} - -//========================================================================================== -// Concrete commands -//------------------------------------------------------------------------------------------ -// AppendNodeCommand - -AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChild, NodeImpl *parentNode) - : EditCommand(document), m_appendChild(appendChild), m_parentNode(parentNode) -{ - ASSERT(m_appendChild); - m_appendChild->ref(); - - ASSERT(m_parentNode); - m_parentNode->ref(); -} - -AppendNodeCommand::~AppendNodeCommand() -{ - ASSERT(m_appendChild); - m_appendChild->deref(); - - ASSERT(m_parentNode); - m_parentNode->deref(); -} - -void AppendNodeCommand::doApply() -{ - ASSERT(m_appendChild); - ASSERT(m_parentNode); - - int exceptionCode = 0; - m_parentNode->appendChild(m_appendChild, exceptionCode); - ASSERT(exceptionCode == 0); -} - -void AppendNodeCommand::doUnapply() -{ - ASSERT(m_appendChild); - ASSERT(m_parentNode); - ASSERT(state() == Applied); - - int exceptionCode = 0; - m_parentNode->removeChild(m_appendChild, exceptionCode); - ASSERT(exceptionCode == 0); -} - -//------------------------------------------------------------------------------------------ -// ApplyStyleCommand - -ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclarationImpl *style, EditAction editingAction, EPropertyLevel propertyLevel) - : CompositeEditCommand(document), m_style(style->makeMutable()), m_editingAction(editingAction), m_propertyLevel(propertyLevel) -{ - ASSERT(m_style); - m_style->ref(); -} - -ApplyStyleCommand::~ApplyStyleCommand() -{ - ASSERT(m_style); - m_style->deref(); -} - -void ApplyStyleCommand::doApply() -{ - switch (m_propertyLevel) { - case PropertyDefault: { - // apply the block-centric properties of the style - CSSMutableStyleDeclarationImpl *blockStyle = m_style->copyBlockProperties(); - blockStyle->ref(); - applyBlockStyle(blockStyle); - // apply any remaining styles to the inline elements - // NOTE: hopefully, this string comparison is the same as checking for a non-null diff - if (blockStyle->length() < m_style->length()) { - CSSMutableStyleDeclarationImpl *inlineStyle = m_style->copy(); - inlineStyle->ref(); - applyRelativeFontStyleChange(inlineStyle); - blockStyle->diff(inlineStyle); - applyInlineStyle(inlineStyle); - inlineStyle->deref(); - } - blockStyle->deref(); - break; - } - case ForceBlockProperties: - // Force all properties to be applied as block styles. - applyBlockStyle(m_style); - break; - } - - setEndingSelectionNeedsLayout(); -} - -EditAction ApplyStyleCommand::editingAction() const -{ - return m_editingAction; -} - -void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclarationImpl *style) -{ - // update document layout once before removing styles - // so that we avoid the expense of updating before each and every call - // to check a computed style - document()->updateLayout(); - - // get positions we want to use for applying style - Position start(endingSelection().start()); - Position end(endingSelection().end()); - - // remove current values, if any, of the specified styles from the blocks - // NOTE: tracks the previous block to avoid repeated processing - // Also, gather up all the nodes we want to process in a QPtrList before - // doing anything. This averts any bugs iterating over these nodes - // once you start removing and applying style. - NodeImpl *beyondEnd = end.node()->traverseNextNode(); - QPtrList nodes; - for (NodeImpl *node = start.node(); node != beyondEnd; node = node->traverseNextNode()) - nodes.append(node); - - NodeImpl *prevBlock = 0; - for (QPtrListIterator it(nodes); it.current(); ++it) { - NodeImpl *block = it.current()->enclosingBlockFlowElement(); - if (block != prevBlock && block->isHTMLElement()) { - removeCSSStyle(style, static_cast(block)); - prevBlock = block; - } - } - - // apply specified styles to the block flow elements in the selected range - prevBlock = 0; - for (QPtrListIterator it(nodes); it.current(); ++it) { - NodeImpl *node = it.current(); - if (node->renderer()) { - NodeImpl *block = node->enclosingBlockFlowElement(); - if (block != prevBlock) { - addBlockStyleIfNeeded(style, node); - prevBlock = block; - } - } - } -} - -#define NoFontDelta (0.0f) -#define MinimumFontSize (0.1f) - -void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclarationImpl *style) -{ - if (style->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) { - // Explicit font size overrides any delta. - style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA); - return; - } - - // Get the adjustment amount out of the style. - CSSValueImpl *value = style->getPropertyCSSValue(CSS_PROP__KHTML_FONT_SIZE_DELTA); - if (!value) - return; - value->ref(); - float adjustment = NoFontDelta; - if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) { - CSSPrimitiveValueImpl *primitiveValue = static_cast(value); - if (primitiveValue->primitiveType() == CSSPrimitiveValue::CSS_PX) { - // Only PX handled now. If we handle more types in the future, perhaps - // a switch statement here would be more appropriate. - adjustment = primitiveValue->getFloatValue(CSSPrimitiveValue::CSS_PX); - } - } - style->removeProperty(CSS_PROP__KHTML_FONT_SIZE_DELTA); - value->deref(); - if (adjustment == NoFontDelta) - return; - - // Adjust to the positions we want to use for applying style. - Selection selection = endingSelection(); - Position start(selection.start().downstream()); - Position end(selection.end().upstream()); - if (RangeImpl::compareBoundaryPoints(end, start) < 0) { - Position swap = start; - start = end; - end = swap; - } - - // Join up any adjacent text nodes. - if (start.node()->isTextNode()) { - joinChildTextNodes(start.node()->parentNode(), start, end); - selection = endingSelection(); - start = selection.start(); - end = selection.end(); - } - if (end.node()->isTextNode() && start.node()->parentNode() != end.node()->parentNode()) { - joinChildTextNodes(end.node()->parentNode(), start, end); - selection = endingSelection(); - start = selection.start(); - end = selection.end(); - } - - // Split the start text nodes if needed to apply style. - bool splitStart = splitTextAtStartIfNeeded(start, end); - if (splitStart) { - start = endingSelection().start(); - end = endingSelection().end(); - } - bool splitEnd = splitTextAtEndIfNeeded(start, end); - if (splitEnd) { - start = endingSelection().start(); - end = endingSelection().end(); - } - - NodeImpl *beyondEnd = end.node()->traverseNextNode(); // Calculate loop end point. - start = start.upstream(); // Move upstream to ensure we do not add redundant spans. - NodeImpl *startNode = start.node(); - if (startNode->isTextNode() && start.offset() >= startNode->caretMaxOffset()) // Move out of text node if range does not include its characters. - startNode = startNode->traverseNextNode(); - - // Store away font size before making any changes to the document. - // This ensures that changes to one node won't effect another. - QMap startingFontSizes; - for (const NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) - startingFontSizes.insert(node, computedFontSize(node)); - - // These spans were added by us. If empty after font size changes, they can be removed. - QPtrList emptySpans; - - NodeImpl *lastStyledNode = 0; - for (NodeImpl *node = startNode; node != beyondEnd; node = node->traverseNextNode()) { - HTMLElementImpl *elem = 0; - if (node->isHTMLElement()) { - // Only work on fully selected nodes. - if (!nodeFullySelected(node, start, end)) - continue; - elem = static_cast(node); - } - else if (node->isTextNode() && node->parentNode() != lastStyledNode) { - // Last styled node was not parent node of this text node, but we wish to style this - // text node. To make this possible, add a style span to surround this text node. - elem = static_cast(createStyleSpanElement(document())); - insertNodeBefore(elem, node); - surroundNodeRangeWithElement(node, node, elem); - } - else { - // Only handle HTML elements and text nodes. - continue; - } - lastStyledNode = node; - - CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->getInlineStyleDecl(); - float currentFontSize = computedFontSize(node); - float desiredFontSize = kMax(MinimumFontSize, startingFontSizes[node] + adjustment); - if (inlineStyleDecl->getPropertyCSSValue(CSS_PROP_FONT_SIZE)) { - inlineStyleDecl->removeProperty(CSS_PROP_FONT_SIZE, true); - currentFontSize = computedFontSize(node); - } - if (currentFontSize != desiredFontSize) { - QString desiredFontSizeString = QString::number(desiredFontSize); - desiredFontSizeString += "px"; - inlineStyleDecl->setProperty(CSS_PROP_FONT_SIZE, desiredFontSizeString, false, false); - setNodeAttribute(elem, ATTR_STYLE, inlineStyleDecl->cssText()); - } - if (inlineStyleDecl->length() == 0) { - removeNodeAttribute(elem, ATTR_STYLE); - if (isEmptyStyleSpan(elem)) - emptySpans.append(elem); - } - } - - for (QPtrListIterator it(emptySpans); it.current(); ++it) - removeNodePreservingChildren(it.current()); -} - -#undef NoFontDelta -#undef MinimumFontSize - -void ApplyStyleCommand::applyInlineStyle(CSSMutableStyleDeclarationImpl *style) -{ - // adjust to the positions we want to use for applying style - Position start(endingSelection().start().downstream().equivalentRangeCompliantPosition()); - Position end(endingSelection().end().upstream()); - - if (RangeImpl::compareBoundaryPoints(end, start) < 0) { - Position swap = start; - start = end; - end = swap; - } - - // update document layout once before removing styles - // so that we avoid the expense of updating before each and every call - // to check a computed style - document()->updateLayout(); - - // split the start node and containing element if the selection starts inside of it - bool splitStart = splitTextElementAtStartIfNeeded(start, end); - if (splitStart) { - start = endingSelection().start(); - end = endingSelection().end(); - } - - // split the end node and containing element if the selection ends inside of it - bool splitEnd = splitTextElementAtEndIfNeeded(start, end); - start = endingSelection().start(); - end = endingSelection().end(); - - // Remove style from the selection. - // Use the upstream position of the start for removing style. - // This will ensure we remove all traces of the relevant styles from the selection - // and prevent us from adding redundant ones, as described in: - // Bolding and unbolding creates extraneous tags - removeInlineStyle(style, start.upstream(), end); - start = endingSelection().start(); - end = endingSelection().end(); - - if (splitStart) { - bool mergedStart = mergeStartWithPreviousIfIdentical(start, end); - if (mergedStart) { - start = endingSelection().start(); - end = endingSelection().end(); - } - } - - if (splitEnd) { - mergeEndWithNextIfIdentical(start, end); - start = endingSelection().start(); - end = endingSelection().end(); - } - - // update document layout once before running the rest of the function - // so that we avoid the expense of updating before each and every call - // to check a computed style - document()->updateLayout(); - - if (start.node() == end.node()) { - // simple case...start and end are the same node - addInlineStyleIfNeeded(style, start.node(), end.node()); - } - else { - NodeImpl *node = start.node(); - if (start.offset() >= start.node()->caretMaxOffset()) - node = node->traverseNextNode(); - while (1) { - if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) { - NodeImpl *runStart = node; - while (1) { - NodeImpl *next = node->traverseNextNode(); - // Break if node is the end node, or if the next node does not fit in with - // the current group. - if (node == end.node() || - runStart->parentNode() != next->parentNode() || - (next->isHTMLElement() && next->id() != ID_BR) || - (next->renderer() && !next->renderer()->isInline())) - break; - node = next; - } - // Now apply style to the run we found. - addInlineStyleIfNeeded(style, runStart, node); - } - if (node == end.node()) - break; - node = node->traverseNextNode(); - } - } - - if (splitStart || splitEnd) { - cleanUpEmptyStyleSpans(start, end); - } -} - -//------------------------------------------------------------------------------------------ -// ApplyStyleCommand: style-removal helpers - -bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem) -{ - QValueListConstIterator end; - for (QValueListConstIterator it = style->valuesIterator(); it != end; ++it) { - switch ((*it).id()) { - case CSS_PROP_FONT_WEIGHT: - if (elem->id() == ID_B) - return true; - break; - case CSS_PROP_FONT_STYLE: - if (elem->id() == ID_I) - return true; - } - } - - return false; -} - -void ApplyStyleCommand::removeHTMLStyleNode(HTMLElementImpl *elem) -{ - // This node can be removed. - // EDIT FIXME: This does not handle the case where the node - // has attributes. But how often do people add attributes to tags? - // Not so often I think. - ASSERT(elem); - removeNodePreservingChildren(elem); -} - -void ApplyStyleCommand::removeHTMLFontStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem) -{ - ASSERT(style); - ASSERT(elem); - - if (elem->id() != ID_FONT) - return; - - int exceptionCode = 0; - QValueListConstIterator end; - for (QValueListConstIterator it = style->valuesIterator(); it != end; ++it) { - switch ((*it).id()) { - case CSS_PROP_COLOR: - elem->removeAttribute(ATTR_COLOR, exceptionCode); - ASSERT(exceptionCode == 0); - break; - case CSS_PROP_FONT_FAMILY: - elem->removeAttribute(ATTR_FACE, exceptionCode); - ASSERT(exceptionCode == 0); - break; - case CSS_PROP_FONT_SIZE: - elem->removeAttribute(ATTR_SIZE, exceptionCode); - ASSERT(exceptionCode == 0); - break; - } - } - - if (isEmptyFontTag(elem)) - removeNodePreservingChildren(elem); -} - -void ApplyStyleCommand::removeCSSStyle(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem) -{ - ASSERT(style); - ASSERT(elem); - - CSSMutableStyleDeclarationImpl *decl = elem->inlineStyleDecl(); - if (!decl) - return; - - QValueListConstIterator end; - for (QValueListConstIterator it = style->valuesIterator(); it != end; ++it) { - int propertyID = (*it).id(); - CSSValueImpl *value = decl->getPropertyCSSValue(propertyID); - if (value) { - value->ref(); - removeCSSProperty(decl, propertyID); - value->deref(); - } - } - - if (isEmptyStyleSpan(elem)) - removeNodePreservingChildren(elem); -} - -void ApplyStyleCommand::removeBlockStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end) -{ - ASSERT(start.isNotNull()); - ASSERT(end.isNotNull()); - ASSERT(start.node()->inDocument()); - ASSERT(end.node()->inDocument()); - ASSERT(RangeImpl::compareBoundaryPoints(start, end) <= 0); - -} - -static bool hasTextDecorationProperty(NodeImpl *node) -{ - if (!node->isElementNode()) - return false; - - ElementImpl *element = static_cast(node); - CSSComputedStyleDeclarationImpl style(element); - - CSSValueImpl *value = style.getPropertyCSSValue(CSS_PROP_TEXT_DECORATION, DoNotUpdateLayout); - - if (value) { - value->ref(); - DOMString valueText(value->cssText()); - value->deref(); - if (strcasecmp(valueText,"none") != 0) - return true; - } - - return false; -} - -static NodeImpl* highestAncestorWithTextDecoration(NodeImpl *node) -{ - NodeImpl *result = NULL; - - for (NodeImpl *n = node; n; n = n->parentNode()) { - if (hasTextDecorationProperty(n)) - result = n; - } - - return result; -} - -CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractTextDecorationStyle(NodeImpl *node) -{ - ASSERT(node); - ASSERT(node->isElementNode()); - - // non-html elements not handled yet - if (!node->isHTMLElement()) - return 0; - - HTMLElementImpl *element = static_cast(node); - CSSMutableStyleDeclarationImpl *style = element->inlineStyleDecl(); - if (!style) - return 0; - - style->ref(); - int properties[1] = { CSS_PROP_TEXT_DECORATION }; - CSSMutableStyleDeclarationImpl *textDecorationStyle = style->copyPropertiesInSet(properties, 1); - - CSSValueImpl *property = style->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION); - if (property && strcasecmp(property->cssText(), "none") != 0) { - removeCSSProperty(style, CSS_PROP_TEXT_DECORATION); - } - - style->deref(); - - return textDecorationStyle; -} - -CSSMutableStyleDeclarationImpl *ApplyStyleCommand::extractAndNegateTextDecorationStyle(NodeImpl *node) -{ - ASSERT(node); - ASSERT(node->isElementNode()); - - // non-html elements not handled yet - if (!node->isHTMLElement()) - return 0; - - HTMLElementImpl *element = static_cast(node); - CSSComputedStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(element); - ASSERT(computedStyle); - - computedStyle->ref(); - - int properties[1] = { CSS_PROP_TEXT_DECORATION }; - CSSMutableStyleDeclarationImpl *textDecorationStyle = computedStyle->copyPropertiesInSet(properties, 1); - - - CSSValueImpl *property = computedStyle->getPropertyCSSValue(CSS_PROP_TEXT_DECORATION); - if (property && strcasecmp(property->cssText(), "none") != 0) { - property->ref(); - CSSMutableStyleDeclarationImpl *newStyle = textDecorationStyle->copy(); - - newStyle->ref(); - newStyle->setProperty(CSS_PROP_TEXT_DECORATION, "none"); - applyTextDecorationStyle(node, newStyle); - newStyle->deref(); - - property->deref(); - } - - computedStyle->deref(); - - return textDecorationStyle; -} - -void ApplyStyleCommand::applyTextDecorationStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style) -{ - ASSERT(node); - - if (!style || !style->cssText().length()) - return; - - if (node->isTextNode()) { - HTMLElementImpl *styleSpan = static_cast(createStyleSpanElement(document())); - insertNodeBefore(styleSpan, node); - surroundNodeRangeWithElement(node, node, styleSpan); - node = styleSpan; - } - - if (!node->isElementNode()) - return; - - HTMLElementImpl *element = static_cast(node); - - StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode())); - if (styleChange.cssStyle().length() > 0) { - DOMString cssText = styleChange.cssStyle(); - CSSMutableStyleDeclarationImpl *decl = element->inlineStyleDecl(); - if (decl) - cssText += decl->cssText(); - setNodeAttribute(element, ATTR_STYLE, cssText); - } -} - -void ApplyStyleCommand::pushDownTextDecorationStyleAroundNode(NodeImpl *node, const Position &start, const Position &end, bool force) -{ - NodeImpl *highestAncestor = highestAncestorWithTextDecoration(node); - - if (highestAncestor) { - NodeImpl *nextCurrent; - NodeImpl *nextChild; - for (NodeImpl *current = highestAncestor; current != node; current = nextCurrent) { - ASSERT(current); - - nextCurrent = NULL; - - CSSMutableStyleDeclarationImpl *decoration = force ? extractAndNegateTextDecorationStyle(current) : extractTextDecorationStyle(current); - if (decoration) - decoration->ref(); - - for (NodeImpl *child = current->firstChild(); child; child = nextChild) { - nextChild = child->nextSibling(); - - if (node == child) { - nextCurrent = child; - } else if (node->isAncestor(child)) { - applyTextDecorationStyle(child, decoration); - nextCurrent = child; - } else { - applyTextDecorationStyle(child, decoration); - } - } - - if (decoration) - decoration->deref(); - } - } -} - -void ApplyStyleCommand::pushDownTextDecorationStyleAtBoundaries(const Position &start, const Position &end) -{ - // We need to work in two passes. First we push down any inline - // styles that set text decoration. Then we look for any remaining - // styles (caused by stylesheets) and explicitly negate text - // decoration while pushing down. - - pushDownTextDecorationStyleAroundNode(start.node(), start, end, false); - document()->updateLayout(); - pushDownTextDecorationStyleAroundNode(start.node(), start, end, true); - - pushDownTextDecorationStyleAroundNode(end.node(), start, end, false); - document()->updateLayout(); - pushDownTextDecorationStyleAroundNode(end.node(), start, end, true); -} - -void ApplyStyleCommand::removeInlineStyle(CSSMutableStyleDeclarationImpl *style, const Position &start, const Position &end) -{ - ASSERT(start.isNotNull()); - ASSERT(end.isNotNull()); - ASSERT(start.node()->inDocument()); - ASSERT(end.node()->inDocument()); - ASSERT(RangeImpl::compareBoundaryPoints(start, end) < 0); - - CSSValueImpl *textDecorationSpecialProperty = style->getPropertyCSSValue(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT); - - if (textDecorationSpecialProperty) { - pushDownTextDecorationStyleAtBoundaries(start.downstream(), end.upstream()); - style = style->copy(); - style->setProperty(CSS_PROP_TEXT_DECORATION, textDecorationSpecialProperty->cssText(), style->getPropertyPriority(CSS_PROP__KHTML_TEXT_DECORATIONS_IN_EFFECT)); - } - - // The s and e variables store the positions used to set the ending selection after style removal - // takes place. This will help callers to recognize when either the start node or the end node - // are removed from the document during the work of this function. - Position s = start; - Position e = end; - - NodeImpl *node = start.node(); - while (node) { - NodeImpl *next = node->traverseNextNode(); - if (node->isHTMLElement() && nodeFullySelected(node, start, end)) { - HTMLElementImpl *elem = static_cast(node); - NodeImpl *prev = elem->traversePreviousNodePostOrder(); - NodeImpl *next = elem->traverseNextNode(); - if (isHTMLStyleNode(style, elem)) { - removeHTMLStyleNode(elem); - } - else { - removeHTMLFontStyle(style, elem); - removeCSSStyle(style, elem); - } - if (!elem->inDocument()) { - if (s.node() == elem) { - // Since elem must have been fully selected, and it is at the start - // of the selection, it is clear we can set the new s offset to 0. - ASSERT(s.offset() <= s.node()->caretMinOffset()); - s = Position(next, 0); - } - if (e.node() == elem) { - // Since elem must have been fully selected, and it is at the end - // of the selection, it is clear we can set the new e offset to - // the max range offset of prev. - ASSERT(e.offset() >= maxRangeOffset(e.node())); - e = Position(prev, maxRangeOffset(prev)); - } - } - } - if (node == end.node()) - break; - node = next; - } - - - if (textDecorationSpecialProperty) { - style->deref(); - } - - ASSERT(s.node()->inDocument()); - ASSERT(e.node()->inDocument()); - setEndingSelection(Selection(s, VP_DEFAULT_AFFINITY, e, VP_DEFAULT_AFFINITY)); -} - -bool ApplyStyleCommand::nodeFullySelected(NodeImpl *node, const Position &start, const Position &end) const +static int maxRangeOffset(NodeImpl *n) { - ASSERT(node); - ASSERT(node->isElementNode()); + if (DOM::offsetInCharacters(n->nodeType())) + return n->maxOffset(); + + if (n->isElementNode()) + return n->childNodeCount(); - Position pos = Position(node, node->childNodeCount()).upstream(); - return RangeImpl::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 && - RangeImpl::compareBoundaryPoints(pos, end) <= 0; + return 1; } -bool ApplyStyleCommand::nodeFullyUnselected(NodeImpl *node, const Position &start, const Position &end) const +static int maxDeepOffset(NodeImpl *n) { - ASSERT(node); - ASSERT(node->isElementNode()); + if (n->isAtomicNode()) + return n->caretMaxOffset(); - Position pos = Position(node, node->childNodeCount()).upstream(); - bool isFullyBeforeStart = RangeImpl::compareBoundaryPoints(pos, start) < 0; - bool isFullyAfterEnd = RangeImpl::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0; + if (n->isElementNode()) + return n->childNodeCount(); - return isFullyBeforeStart || isFullyAfterEnd; + return 1; } - -//------------------------------------------------------------------------------------------ -// ApplyStyleCommand: style-application helpers - -bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end) +static void debugPosition(const char *prefix, const Position &pos) { - if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) { - long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0; - TextImpl *text = static_cast(start.node()); - splitTextNode(text, start.offset()); - setEndingSelection(Selection(Position(start.node(), 0), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY)); - return true; - } - return false; + if (!prefix) + prefix = ""; + if (pos.isNull()) + LOG(Editing, "%s ", prefix); + else + LOG(Editing, "%s%s %p : %d", prefix, pos.node()->nodeName().string().latin1(), pos.node(), pos.offset()); } -bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end) +static void debugNode(const char *prefix, const NodeImpl *node) { - if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) { - TextImpl *text = static_cast(end.node()); - splitTextNode(text, end.offset()); - - NodeImpl *prevNode = text->previousSibling(); - ASSERT(prevNode); - NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node(); - ASSERT(startNode); - setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode, prevNode->caretMaxOffset()), SEL_DEFAULT_AFFINITY)); - return true; - } - return false; + if (!prefix) + prefix = ""; + if (!node) + LOG(Editing, "%s ", prefix); + else + LOG(Editing, "%s%s %p", prefix, node->nodeName().string().latin1(), node); } -bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end) +static bool isSpecialElement(NodeImpl *n) { - if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) { - long endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0; - TextImpl *text = static_cast(start.node()); - splitTextNodeContainingElement(text, start.offset()); + if (!n->isHTMLElement()) + return false; - setEndingSelection(Selection(Position(start.node()->parentNode(), start.node()->nodeIndex()), SEL_DEFAULT_AFFINITY, Position(end.node(), end.offset() - endOffsetAdjustment), SEL_DEFAULT_AFFINITY)); + if (n->id() == ID_A && n->isLink()) return true; - } - return false; -} -bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end) -{ - if (end.node()->isTextNode() && end.offset() > end.node()->caretMinOffset() && end.offset() < end.node()->caretMaxOffset()) { - TextImpl *text = static_cast(end.node()); - splitTextNodeContainingElement(text, end.offset()); - - NodeImpl *prevNode = text->parent()->previousSibling()->lastChild(); - ASSERT(prevNode); - NodeImpl *startNode = start.node() == end.node() ? prevNode : start.node(); - ASSERT(startNode); - setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, Position(prevNode->parent(), prevNode->nodeIndex() + 1), SEL_DEFAULT_AFFINITY)); + if (n->id() == ID_UL || n->id() == ID_OL || n->id() == ID_DL) return true; - } - return false; -} - -static bool areIdenticalElements(NodeImpl *first, NodeImpl *second) -{ - // check that tag name and all attribute names and values are identical - if (!first->isElementNode()) - return false; - - if (!second->isElementNode()) - return false; + RenderObject *renderer = n->renderer(); - ElementImpl *firstElement = static_cast(first); - ElementImpl *secondElement = static_cast(second); - - if (firstElement->id() != secondElement->id()) - return false; + if (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE)) + return true; - NamedAttrMapImpl *firstMap = firstElement->attributes(); - NamedAttrMapImpl *secondMap = secondElement->attributes(); + if (renderer && renderer->style()->isFloating()) + return true; - unsigned firstLength = firstMap->length(); + if (renderer && renderer->style()->position() != STATIC) + return true; - if (firstLength != secondMap->length()) - return false; + return false; +} - for (unsigned i = 0; i < firstLength; i++) { - DOM::AttributeImpl *attribute = firstMap->attributeItem(i); - DOM::AttributeImpl *secondAttribute = secondMap->getAttributeItem(attribute->id()); +// This version of the function is meant to be called on positions in a document fragment, +// so it does not check for a root editable element, it is assumed these nodes will be put +// somewhere editable in the future +static bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos) +{ + VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - if (!secondAttribute || attribute->value() != secondAttribute->value()) + for (NodeImpl *n = pos.node(); n; n = n->parentNode()) { + if (VisiblePosition(n, 0, DOWNSTREAM) != vPos) return false; + if (isSpecialElement(n)) + return true; } - - return true; + + return false; } -bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end) +static bool isFirstVisiblePositionInSpecialElement(const Position& pos) { - NodeImpl *startNode = start.node(); - long startOffset = start.offset(); + VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); - if (start.node()->isAtomicNode()) { - if (start.offset() != 0) + for (NodeImpl *n = pos.node(); n; n = n->parentNode()) { + if (VisiblePosition(n, 0, DOWNSTREAM) != vPos) return false; - - if (start.node()->previousSibling()) + if (n->rootEditableElement() == NULL) return false; - - startNode = start.node()->parent(); - startOffset = 0; - } - - if (!startNode->isElementNode()) - return false; - - if (startOffset != 0) - return false; - - NodeImpl *previousSibling = startNode->previousSibling(); - - if (previousSibling && areIdenticalElements(startNode, previousSibling)) { - ElementImpl *previousElement = static_cast(previousSibling); - ElementImpl *element = static_cast(startNode); - NodeImpl *startChild = element->firstChild(); - ASSERT(startChild); - mergeIdenticalElements(previousElement, element); - - long startOffsetAdjustment = startChild->nodeIndex(); - long endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0; - - setEndingSelection(Selection(Position(startNode, startOffsetAdjustment), SEL_DEFAULT_AFFINITY, - Position(end.node(), end.offset() + endOffsetAdjustment), SEL_DEFAULT_AFFINITY)); - - return true; + if (isSpecialElement(n)) + return true; } return false; } -bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end) +static Position positionBeforeNode(NodeImpl *node) { - NodeImpl *endNode = end.node(); - int endOffset = end.offset(); + return Position(node->parentNode(), node->nodeIndex()); +} - if (endNode->isAtomicNode()) { - if (endOffset < endNode->caretMaxOffset()) - return false; +static Position positionBeforeContainingSpecialElement(const Position& pos) +{ + ASSERT(isFirstVisiblePositionInSpecialElement(pos)); - unsigned parentLastOffset = end.node()->parent()->childNodes()->length() - 1; - if (end.node()->nextSibling()) - return false; + VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM); + + NodeImpl *outermostSpecialElement = NULL; - endNode = end.node()->parent(); - endOffset = parentLastOffset; + for (NodeImpl *n = pos.node(); n; n = n->parentNode()) { + if (VisiblePosition(n, 0, DOWNSTREAM) != vPos) + break; + if (n->rootEditableElement() == NULL) + break; + if (isSpecialElement(n)) + outermostSpecialElement = n; } + + ASSERT(outermostSpecialElement); - if (!endNode->isElementNode() || endNode->id() == ID_BR) - return false; - - NodeImpl *nextSibling = endNode->nextSibling(); - - if (nextSibling && areIdenticalElements(endNode, nextSibling)) { - ElementImpl *nextElement = static_cast(nextSibling); - ElementImpl *element = static_cast(endNode); - NodeImpl *nextChild = nextElement->firstChild(); - - mergeIdenticalElements(element, nextElement); + Position result = positionBeforeNode(outermostSpecialElement); + if (result.isNull() || !result.node()->rootEditableElement()) + return pos; + + return result; +} - NodeImpl *startNode = start.node() == endNode ? nextElement : start.node(); - ASSERT(startNode); +static bool isLastVisiblePositionInSpecialElement(const Position& pos) +{ + // make sure to get a range-compliant version of the position + Position rangePos = VisiblePosition(pos, DOWNSTREAM).position(); - int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length(); + VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM); - setEndingSelection(Selection(Position(startNode, start.offset()), SEL_DEFAULT_AFFINITY, - Position(nextElement, endOffset), SEL_DEFAULT_AFFINITY)); - return true; + for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) { + if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos) + return false; + if (n->rootEditableElement() == NULL) + return false; + if (isSpecialElement(n)) + return true; } return false; } -void ApplyStyleCommand::cleanUpEmptyStyleSpans(const Position &start, const Position &end) +static Position positionAfterNode(NodeImpl *node) { - NodeImpl *node; - for (node = start.node(); node && !node->previousSibling(); node = node->parentNode()) { - } + return Position(node->parentNode(), node->nodeIndex() + 1); +} - if (node && isEmptyStyleSpan(node->previousSibling())) { - removeNodePreservingChildren(node->previousSibling()); - } +static Position positionAfterContainingSpecialElement(const Position& pos) +{ + ASSERT(isLastVisiblePositionInSpecialElement(pos)); - if (start.node() == end.node()) { - if (start.node()->isTextNode()) { - for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling() && !last->nextSibling(); last = cur, cur = cur->parentNode()) { - if (isEmptyStyleSpan(cur)) { - removeNodePreservingChildren(cur); - break; - } - } + // make sure to get a range-compliant version of the position + Position rangePos = VisiblePosition(pos, DOWNSTREAM).position(); - } - } else { - if (start.node()->isTextNode()) { - for (NodeImpl *last = start.node(), *cur = last->parentNode(); cur && !last->previousSibling(); last = cur, cur = cur->parentNode()) { - if (isEmptyStyleSpan(cur)) { - removeNodePreservingChildren(cur); - break; - } - } - } + VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM); - if (end.node()->isTextNode()) { - for (NodeImpl *last = end.node(), *cur = last->parentNode(); cur && !last->nextSibling(); last = cur, cur = cur->parentNode()) { - if (isEmptyStyleSpan(cur)) { - removeNodePreservingChildren(cur); - break; - } - } - } - } - - for (node = end.node(); node && !node->nextSibling(); node = node->parentNode()) { - } - if (node && isEmptyStyleSpan(node->nextSibling())) { - removeNodePreservingChildren(node->nextSibling()); - } -} + NodeImpl *outermostSpecialElement = NULL; -void ApplyStyleCommand::surroundNodeRangeWithElement(NodeImpl *startNode, NodeImpl *endNode, ElementImpl *element) -{ - ASSERT(startNode); - ASSERT(endNode); - ASSERT(element); - - NodeImpl *node = startNode; - while (1) { - NodeImpl *next = node->traverseNextNode(); - if (node->childNodeCount() == 0 && node->renderer() && node->renderer()->isInline()) { - removeNode(node); - appendNode(node, element); - } - if (node == endNode) + for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) { + if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos) break; - node = next; + if (n->rootEditableElement() == NULL) + break; + if (isSpecialElement(n)) + outermostSpecialElement = n; } -} + + ASSERT(outermostSpecialElement); -void ApplyStyleCommand::addBlockStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *node) -{ - // Do not check for legacy styles here. Those styles, like and , only apply for - // inline content. - if (!node) - return; + Position result = positionAfterNode(outermostSpecialElement); + if (result.isNull() || !result.node()->rootEditableElement()) + return pos; - HTMLElementImpl *block = static_cast(node->enclosingBlockFlowElement()); - if (!block) - return; - - StyleChange styleChange(style, Position(block, 0), StyleChange::styleModeForParseMode(document()->inCompatMode())); - if (styleChange.cssStyle().length() > 0) { - moveParagraphContentsToNewBlockIfNecessary(Position(node, 0)); - block = static_cast(node->enclosingBlockFlowElement()); - DOMString cssText = styleChange.cssStyle(); - CSSMutableStyleDeclarationImpl *decl = block->inlineStyleDecl(); - if (decl) - cssText += decl->cssText(); - setNodeAttribute(block, ATTR_STYLE, cssText); - } + return result; } -void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclarationImpl *style, NodeImpl *startNode, NodeImpl *endNode) +static Position positionOutsideContainingSpecialElement(const Position &pos) { - StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode())); - int exceptionCode = 0; - - // - // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes. - // - if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) { - ElementImpl *fontElement = createFontElement(document()); - ASSERT(exceptionCode == 0); - insertNodeBefore(fontElement, startNode); - if (styleChange.applyFontColor()) - fontElement->setAttribute(ATTR_COLOR, styleChange.fontColor()); - if (styleChange.applyFontFace()) - fontElement->setAttribute(ATTR_FACE, styleChange.fontFace()); - if (styleChange.applyFontSize()) - fontElement->setAttribute(ATTR_SIZE, styleChange.fontSize()); - surroundNodeRangeWithElement(startNode, endNode, fontElement); - } - - if (styleChange.cssStyle().length() > 0) { - ElementImpl *styleElement = createStyleSpanElement(document()); - styleElement->ref(); - styleElement->setAttribute(ATTR_STYLE, styleChange.cssStyle()); - insertNodeBefore(styleElement, startNode); - styleElement->deref(); - surroundNodeRangeWithElement(startNode, endNode, styleElement); - } - - if (styleChange.applyBold()) { - ElementImpl *boldElement = document()->createHTMLElement("B", exceptionCode); - ASSERT(exceptionCode == 0); - insertNodeBefore(boldElement, startNode); - surroundNodeRangeWithElement(startNode, endNode, boldElement); + if (isFirstVisiblePositionInSpecialElement(pos)) { + return positionBeforeContainingSpecialElement(pos); + } else if (isLastVisiblePositionInSpecialElement(pos)) { + return positionAfterContainingSpecialElement(pos); } - if (styleChange.applyItalic()) { - ElementImpl *italicElement = document()->createHTMLElement("I", exceptionCode); - ASSERT(exceptionCode == 0); - insertNodeBefore(italicElement, startNode); - surroundNodeRangeWithElement(startNode, endNode, italicElement); - } + return pos; } -float ApplyStyleCommand::computedFontSize(const NodeImpl *node) +static Position positionBeforePossibleContainingSpecialElement(const Position &pos) { - float size = 0.0f; - - if (!node) - return size; - - Position pos(const_cast(node), 0); - CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle(); - if (!computedStyle) - return size; - computedStyle->ref(); - - CSSPrimitiveValueImpl *value = static_cast(computedStyle->getPropertyCSSValue(CSS_PROP_FONT_SIZE)); - if (value) { - value->ref(); - size = value->getFloatValue(CSSPrimitiveValue::CSS_PX); - value->deref(); - } + if (isFirstVisiblePositionInSpecialElement(pos)) { + return positionBeforeContainingSpecialElement(pos); + } - computedStyle->deref(); - return size; + return pos; } -void ApplyStyleCommand::joinChildTextNodes(NodeImpl *node, const Position &start, const Position &end) +static Position positionAfterPossibleContainingSpecialElement(const Position &pos) { - if (!node) - return; - - Position newStart = start; - Position newEnd = end; - - NodeImpl *child = node->firstChild(); - while (child) { - NodeImpl *next = child->nextSibling(); - if (child->isTextNode() && next && next->isTextNode()) { - TextImpl *childText = static_cast(child); - TextImpl *nextText = static_cast(next); - if (next == start.node()) - newStart = Position(childText, childText->length() + start.offset()); - if (next == end.node()) - newEnd = Position(childText, childText->length() + end.offset()); - DOMString textToMove = nextText->data(); - insertTextIntoNode(childText, childText->length(), textToMove); - removeNode(next); - // don't move child node pointer. it may want to merge with more text nodes. - } - else { - child = child->nextSibling(); - } + if (isLastVisiblePositionInSpecialElement(pos)) { + return positionAfterContainingSpecialElement(pos); } - setEndingSelection(Selection(newStart, SEL_DEFAULT_AFFINITY, newEnd, SEL_DEFAULT_AFFINITY)); + return pos; } //------------------------------------------------------------------------------------------ @@ -5661,15 +3884,6 @@ bool TypingCommand::isTypingCommand() const return true; } -ElementImpl *floatRefdElement(ElementImpl *element) -{ - assert(!element->parentNode()); - element->setParent(element->getDocument()); - element->deref(); - element->setParent(0); - return element; -} - ElementImpl *createDefaultParagraphElement(DocumentImpl *document) { // We would need this margin-zeroing code back if we ever return to using

elements for default paragraphs. @@ -5680,16 +3894,6 @@ ElementImpl *createDefaultParagraphElement(DocumentImpl *document) return element; } -ElementImpl *createBlockPlaceholderElement(DocumentImpl *document) -{ - int exceptionCode = 0; - ElementImpl *breakNode = document->createHTMLElement("br", exceptionCode); - ASSERT(exceptionCode == 0); - breakNode->ref(); - breakNode->setAttribute(ATTR_CLASS, blockPlaceholderClassString()); - return floatRefdElement(breakNode); -} - ElementImpl *createBreakElement(DocumentImpl *document) { int exceptionCode = 0; @@ -5698,26 +3902,6 @@ ElementImpl *createBreakElement(DocumentImpl *document) return breakNode; } -ElementImpl *createFontElement(DocumentImpl *document) -{ - int exceptionCode = 0; - ElementImpl *fontNode = document->createHTMLElement("font", exceptionCode); - ASSERT(exceptionCode == 0); - fontNode->ref(); - fontNode->setAttribute(ATTR_CLASS, styleSpanClassString()); - return floatRefdElement(fontNode); -} - -ElementImpl *createStyleSpanElement(DocumentImpl *document) -{ - int exceptionCode = 0; - ElementImpl *styleElement = document->createHTMLElement("SPAN", exceptionCode); - ASSERT(exceptionCode == 0); - styleElement->ref(); - styleElement->setAttribute(ATTR_CLASS, styleSpanClassString()); - return floatRefdElement(styleElement); -} - bool isNodeRendered(const NodeImpl *node) { if (!node)