PassRefPtr<FilterEffect> FilterEffectRenderer::buildReferenceFilter(RenderObject* renderer, PassRefPtr<FilterEffect> previousEffect, ReferenceFilterOperation* filterOperation) { #if ENABLE(SVG) if (!renderer) return 0; Document* document = renderer->document(); ASSERT(document); CachedSVGDocumentReference* cachedSVGDocumentReference = filterOperation->cachedSVGDocumentReference(); CachedSVGDocument* cachedSVGDocument = cachedSVGDocumentReference ? cachedSVGDocumentReference->document() : 0; // If we have an SVG document, this is an external reference. Otherwise // we look up the referenced node in the current document. if (cachedSVGDocument) document = cachedSVGDocument->document(); if (!document) return 0; Element* filter = document->getElementById(filterOperation->fragment()); if (!filter) { // Although we did not find the referenced filter, it might exist later // in the document document->accessSVGExtensions()->addPendingResource(filterOperation->fragment(), toElement(renderer->node())); return 0; } RefPtr<FilterEffect> effect; // FIXME: Figure out what to do with SourceAlpha. Right now, we're // using the alpha of the original input layer, which is obviously // wrong. We should probably be extracting the alpha from the // previousEffect, but this requires some more processing. // This may need a spec clarification. RefPtr<SVGFilterBuilder> builder = SVGFilterBuilder::create(previousEffect, SourceAlpha::create(this)); for (Node* node = filter->firstChild(); node; node = node->nextSibling()) { if (!node->isSVGElement()) continue; SVGElement* element = toSVGElement(node); if (!element->isFilterEffect()) continue; SVGFilterPrimitiveStandardAttributes* effectElement = static_cast<SVGFilterPrimitiveStandardAttributes*>(element); effect = effectElement->build(builder.get(), this); if (!effect) continue; effectElement->setStandardAttributes(effect.get()); builder->add(effectElement->result(), effect); m_effects.append(effect); } return effect; #else UNUSED_PARAM(renderer); UNUSED_PARAM(previousEffect); UNUSED_PARAM(filterOperation); return 0; #endif }
void HTMLAppletElement::updateWidget(PluginCreationOption) { setNeedsWidgetUpdate(false); // FIXME: This should ASSERT isFinishedParsingChildren() instead. if (!isFinishedParsingChildren()) return; RenderEmbeddedObject* renderer = renderEmbeddedObject(); Frame* frame = document().frame(); ASSERT(frame); LayoutUnit contentWidth = renderer->style()->width().isFixed() ? LayoutUnit(renderer->style()->width().value()) : renderer->width() - renderer->borderAndPaddingWidth(); LayoutUnit contentHeight = renderer->style()->height().isFixed() ? LayoutUnit(renderer->style()->height().value()) : renderer->height() - renderer->borderAndPaddingHeight(); Vector<String> paramNames; Vector<String> paramValues; paramNames.append("code"); paramValues.append(getAttribute(codeAttr).string()); const AtomicString& codeBase = getAttribute(codebaseAttr); if (!codeBase.isNull()) { KURL codeBaseURL = document().completeURL(codeBase); if (!document().securityOrigin()->canDisplay(codeBaseURL)) { FrameLoader::reportLocalLoadFailed(frame, codeBaseURL.string()); return; } const char javaAppletMimeType[] = "application/x-java-applet"; if (!document().contentSecurityPolicy()->allowObjectFromSource(codeBaseURL) || !document().contentSecurityPolicy()->allowPluginType(javaAppletMimeType, javaAppletMimeType, codeBaseURL)) return; paramNames.append("codeBase"); paramValues.append(codeBase.string()); } const AtomicString& name = document().isHTMLDocument() ? getNameAttribute() : getIdAttribute(); if (!name.isNull()) { paramNames.append("name"); paramValues.append(name.string()); } const AtomicString& archive = getAttribute(archiveAttr); if (!archive.isNull()) { paramNames.append("archive"); paramValues.append(archive.string()); } paramNames.append("baseURL"); KURL baseURL = document().baseURL(); paramValues.append(baseURL.string()); const AtomicString& mayScript = getAttribute(mayscriptAttr); if (!mayScript.isNull()) { paramNames.append("mayScript"); paramValues.append(mayScript.string()); } for (Node* child = firstChild(); child; child = child->nextSibling()) { if (!child->hasTagName(paramTag)) continue; HTMLParamElement* param = toHTMLParamElement(child); if (param->name().isEmpty()) continue; paramNames.append(param->name()); paramValues.append(param->value()); } RefPtr<Widget> widget; if (frame->loader()->allowPlugins(AboutToInstantiatePlugin)) widget = frame->loader()->client()->createJavaAppletWidget(roundedIntSize(LayoutSize(contentWidth, contentHeight)), this, baseURL, paramNames, paramValues); if (!widget) { if (!renderer->showsUnavailablePluginIndicator()) renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing); return; } frame->loader()->setContainsPlugins(); renderer->setWidget(widget); }
Node* StyledMarkupAccumulator::traverseNodesForSerialization(Node* startNode, Node* pastEnd, NodeTraversalMode traversalMode) { const bool shouldEmit = traversalMode == EmitString; Vector<Node*> ancestorsToClose; Node* next; Node* lastClosed = 0; for (Node* n = startNode; n != pastEnd; n = next) { // According to <rdar://problem/5730668>, it is possible for n to blow // past pastEnd and become null here. This shouldn't be possible. // This null check will prevent crashes (but create too much markup) // and the ASSERT will hopefully lead us to understanding the problem. ASSERT(n); if (!n) break; next = NodeTraversal::next(n); bool openedTag = false; if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) // Don't write out empty block containers that aren't fully selected. continue; if (!n->renderer() && !enclosingNodeWithTag(firstPositionInOrBeforeNode(n), selectTag)) { next = NodeTraversal::nextSkippingChildren(n); // Don't skip over pastEnd. if (pastEnd && pastEnd->isDescendantOf(n)) next = pastEnd; } else { // Add the node to the markup if we're not skipping the descendants if (shouldEmit) appendStartTag(n); // If node has no children, close the tag now. if (!n->childNodeCount()) { if (shouldEmit) appendEndTag(n); lastClosed = n; } else { openedTag = true; ancestorsToClose.append(n); } } // If we didn't insert open tag and there's no more siblings or we're at the end of the traversal, take care of ancestors. // FIXME: What happens if we just inserted open tag and reached the end? if (!openedTag && (!n->nextSibling() || next == pastEnd)) { // Close up the ancestors. while (!ancestorsToClose.isEmpty()) { Node* ancestor = ancestorsToClose.last(); if (next != pastEnd && next->isDescendantOf(ancestor)) break; // Not at the end of the range, close ancestors up to sibling of next node. if (shouldEmit) appendEndTag(ancestor); lastClosed = ancestor; ancestorsToClose.removeLast(); } // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. ContainerNode* nextParent = next ? next->parentNode() : 0; if (next != pastEnd && n != nextParent) { Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; for (ContainerNode* parent = lastAncestorClosedOrSelf->parentNode(); parent && parent != nextParent; parent = parent->parentNode()) { // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: if (!parent->renderer()) continue; // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: ASSERT(startNode->isDescendantOf(parent)); if (shouldEmit) wrapWithNode(parent); lastClosed = parent; } } } } return lastClosed; }
static void sortBlock(unsigned from, unsigned to, Vector<Vector<Node*> >& parentMatrix, bool mayContainAttributeNodes) { ASSERT(from + 1 < to); // Should not call this function with less that two nodes to sort. unsigned minDepth = UINT_MAX; for (unsigned i = from; i < to; ++i) { unsigned depth = parentMatrix[i].size() - 1; if (minDepth > depth) minDepth = depth; } // Find the common ancestor. unsigned commonAncestorDepth = minDepth; Node* commonAncestor; while (true) { commonAncestor = parentWithDepth(commonAncestorDepth, parentMatrix[from]); if (commonAncestorDepth == 0) break; bool allEqual = true; for (unsigned i = from + 1; i < to; ++i) { if (commonAncestor != parentWithDepth(commonAncestorDepth, parentMatrix[i])) { allEqual = false; break; } } if (allEqual) break; --commonAncestorDepth; } if (commonAncestorDepth == minDepth) { // One of the nodes is the common ancestor => it is the first in document order. // Find it and move it to the beginning. for (unsigned i = from; i < to; ++i) if (commonAncestor == parentMatrix[i][0]) { parentMatrix[i].swap(parentMatrix[from]); if (from + 2 < to) sortBlock(from + 1, to, parentMatrix, mayContainAttributeNodes); return; } } if (mayContainAttributeNodes && commonAncestor->isElementNode()) { // The attribute nodes and namespace nodes of an element occur before the children of the element. // The namespace nodes are defined to occur before the attribute nodes. // The relative order of namespace nodes is implementation-dependent. // The relative order of attribute nodes is implementation-dependent. unsigned sortedEnd = from; // FIXME: namespace nodes are not implemented. for (unsigned i = sortedEnd; i < to; ++i) { Node* n = parentMatrix[i][0]; if (n->isAttributeNode() && static_cast<Attr*>(n)->ownerElement() == commonAncestor) parentMatrix[i].swap(parentMatrix[sortedEnd++]); } if (sortedEnd != from) { if (to - sortedEnd > 1) sortBlock(sortedEnd, to, parentMatrix, mayContainAttributeNodes); return; } } // Children nodes of the common ancestor induce a subdivision of our node-set. // Sort it according to this subdivision, and recursively sort each group. HashSet<Node*> parentNodes; for (unsigned i = from; i < to; ++i) parentNodes.add(parentWithDepth(commonAncestorDepth + 1, parentMatrix[i])); unsigned previousGroupEnd = from; unsigned groupEnd = from; for (Node* n = commonAncestor->firstChild(); n; n = n->nextSibling()) { // If parentNodes contains the node, perform a linear search to move its children in the node-set to the beginning. if (parentNodes.contains(n)) { for (unsigned i = groupEnd; i < to; ++i) if (parentWithDepth(commonAncestorDepth + 1, parentMatrix[i]) == n) parentMatrix[i].swap(parentMatrix[groupEnd++]); if (groupEnd - previousGroupEnd > 1) sortBlock(previousGroupEnd, groupEnd, parentMatrix, mayContainAttributeNodes); ASSERT(previousGroupEnd != groupEnd); previousGroupEnd = groupEnd; #ifndef NDEBUG parentNodes.remove(n); #endif } } ASSERT(parentNodes.isEmpty()); }
void BreakBlockquoteCommand::doApply() { if (endingSelection().isNone()) return; // Delete the current selection. if (endingSelection().isRange()) deleteSelection(false, false); // This is a scenario that should never happen, but we want to // make sure we don't dereference a null pointer below. ASSERT(!endingSelection().isNone()); if (endingSelection().isNone()) return; VisiblePosition visiblePos = endingSelection().visibleStart(); // pos is a position equivalent to the caret. We use downstream() so that pos will // be in the first node that we need to move (there are a few exceptions to this, see below). Position pos = endingSelection().start().downstream(); // Find the top-most blockquote from the start. Node* topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote); if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode()) return; RefPtr<Element> breakNode = createBreakElement(document()); bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); // If the position is at the beginning of the top quoted content, we don't need to break the quote. // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { insertNodeBefore(breakNode.get(), topBlockquote); setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Insert a break after the top blockquote. insertNodeAfter(breakNode.get(), topBlockquote); // If we're inserting the break at the end of the quoted content, we don't need to break the quote. if (isLastVisPosInNode) { setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); return; } // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph // in the new blockquote. if (lineBreakExistsAtVisiblePosition(visiblePos)) pos = pos.next(); // Adjust the position so we don't split at the beginning of a quote. while (isFirstVisiblePositionInNode(VisiblePosition(pos), enclosingNodeOfType(pos, isMailBlockquote))) pos = pos.previous(); // startNode is the first node that we need to move to the new blockquote. Node* startNode = pos.deprecatedNode(); // Split at pos if in the middle of a text node. if (startNode->isTextNode()) { Text* textNode = toText(startNode); if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) { startNode = startNode->traverseNextNode(); ASSERT(startNode); } else if (pos.deprecatedEditingOffset() > 0) splitTextNode(textNode, pos.deprecatedEditingOffset()); } else if (pos.deprecatedEditingOffset() > 0) { Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset()); startNode = childAtOffset ? childAtOffset : startNode->traverseNextNode(); ASSERT(startNode); } // If there's nothing inside topBlockquote to move, we're finished. if (!startNode->isDescendantOf(topBlockquote)) { setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); return; } // Build up list of ancestors in between the start node and the top blockquote. Vector<RefPtr<Element> > ancestors; for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) ancestors.append(node); // Insert a clone of the top blockquote after the break. RefPtr<Element> clonedBlockquote = static_cast<Element*>(topBlockquote)->cloneElementWithoutChildren(); insertNodeAfter(clonedBlockquote.get(), breakNode.get()); // Clone startNode's ancestors into the cloned blockquote. // On exiting this loop, clonedAncestor is the lowest ancestor // that was cloned (i.e. the clone of either ancestors.last() // or clonedBlockquote if ancestors is empty). RefPtr<Element> clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); // Preserve list item numbering in cloned lists. if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) { Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; // The first child of the cloned list might not be a list item element, // find the first one so that we know where to start numbering. while (listChildNode && !listChildNode->hasTagName(liTag)) listChildNode = listChildNode->nextSibling(); if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem()) setNodeAttribute(static_cast<Element*>(clonedChild.get()), startAttr, String::number(toRenderListItem(listChildNode->renderer())->value())); } appendNode(clonedChild.get(), clonedAncestor.get()); clonedAncestor = clonedChild; } moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor); if (!ancestors.isEmpty()) { // Split the tree up the ancestor chain until the topBlockquote // Throughout this loop, clonedParent is the clone of ancestor's parent. // This is so we can clone ancestor's siblings and place the clones // into the clone corresponding to the ancestor's parent. RefPtr<Element> ancestor; RefPtr<Element> clonedParent; for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); ancestor && ancestor != topBlockquote; ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent); // If the startNode's original parent is now empty, remove it Node* originalParent = ancestors.first().get(); if (!originalParent->hasChildNodes()) removeNode(originalParent); } // Make sure the cloned block quote renders. addBlockPlaceholderIfNeeded(clonedBlockquote.get()); // Put the selection right before the break. setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); rebalanceWhitespace(); }
void InsertParagraphSeparatorCommand::doApply() { bool splitText = false; if (!endingSelection().isNonOrphanedCaretOrRange()) return; Position insertionPosition = endingSelection().start(); EAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { calculateStyleBeforeInsertion(insertionPosition); deleteSelection(false, true); insertionPosition = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The rangeCompliantEquivalent conversion needs to be moved into enclosingBlock. Node* startBlockNode = enclosingBlock(rangeCompliantEquivalent(insertionPosition).node()); Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent(); Element* startBlock = static_cast<Element*>(startBlockNode); if (!startBlockNode || !startBlockNode->isElementNode() || !startBlock->parentNode() || isTableCell(startBlock) || startBlock->hasTagName(formTag) // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342 || (!canonicalPos.isNull() && canonicalPos.node()->renderer() && canonicalPos.node()->renderer()->isTable()) || (!canonicalPos.isNull() && canonicalPos.node()->hasTagName(hrTag))) { applyCommandToComposite(InsertLineBreakCommand::create(document())); return; } // Use the leftmost candidate. insertionPosition = insertionPosition.upstream(); if (!insertionPosition.isCandidate()) insertionPosition = insertionPosition.downstream(); // Adjust the insertion position after the delete insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition); VisiblePosition visiblePos(insertionPosition, affinity); calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item if (breakOutOfEmptyListItem()) return; //--------------------------------------------------------------------- // Prepare for more general cases. bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); bool nestNewBlock = false; // Create block to be inserted. RefPtr<Element> blockToInsert; if (startBlock == startBlock->rootEditableElement()) { blockToInsert = createDefaultParagraphElement(document()); nestNewBlock = true; } else if (shouldUseDefaultParagraphElement(startBlock)) blockToInsert = createDefaultParagraphElement(document()); else blockToInsert = startBlock->cloneElementWithoutChildren(); //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, // including when the block is empty. if (isLastInBlock) { if (nestNewBlock) { if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) { // The block is empty. Create an empty block to // represent the paragraph that we're leaving. RefPtr<Element> extraBlock = createDefaultParagraphElement(document()); appendNode(extraBlock, startBlock); appendBlockPlaceholder(extraBlock); } appendNode(blockToInsert, startBlock); } else { // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted. if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) startBlock = static_cast<Element*>(highestBlockquote); // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, // for div nodes, this can result in nested div tags that are hard to break out of. Element* siblingNode = startBlock; if (blockToInsert->hasTagName(divTag)) siblingNode = highestVisuallyEquivalentDivBelowRoot(startBlock); insertNodeAfter(blockToInsert, siblingNode); } // Recreate the same structure in the new paragraph. Vector<Element*> ancestors; getAncestorsInsideBlock(insertionPosition.node(), startBlock, ancestors); RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert); appendBlockPlaceholder(parent); setEndingSelection(VisibleSelection(Position(parent.get(), 0), DOWNSTREAM)); return; } //--------------------------------------------------------------------- // Handle case when position is in the first visible position in its block, and // similar case where previous position is in another, presumeably nested, block. if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) { Node *refNode; if (isFirstInBlock && !nestNewBlock) refNode = startBlock; else if (insertionPosition.node() == startBlock && nestNewBlock) { refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset()); ASSERT(refNode); // must be true or we'd be in the end of block case } else refNode = insertionPosition.node(); // find ending selection position easily before inserting the paragraph insertionPosition = insertionPosition.downstream(); insertNodeBefore(blockToInsert, refNode); // Recreate the same structure in the new paragraph. Vector<Element*> ancestors; getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(insertionPosition).node(), startBlock, ancestors); appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert)); // In this case, we need to set the new ending selection. setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM)); return; } //--------------------------------------------------------------------- // Handle the (more complicated) general case, // All of the content in the current block after visiblePos is // about to be wrapped in a new paragraph element. Add a br before // it if visiblePos is at the start of a paragraph so that the // content will move down a line. if (isStartOfParagraph(visiblePos)) { RefPtr<Element> br = createBreakElement(document()); insertNodeAt(br.get(), insertionPosition); insertionPosition = positionInParentAfterNode(br.get()); } // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. insertionPosition = insertionPosition.downstream(); // At this point, the insertionPosition's node could be a container, and we want to make sure we include // all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position // before we walk the DOM tree. insertionPosition = VisiblePosition(insertionPosition).deepEquivalent(); // Build up list of ancestors in between the start node and the start block. Vector<Element*> ancestors; getAncestorsInsideBlock(insertionPosition.node(), startBlock, ancestors); // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but pos.downstream() does not give it Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull() && leadingWhitespace.node()->isTextNode()) { Text* textNode = static_cast<Text*>(leadingWhitespace.node()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); replaceTextInNode(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. if (insertionPosition.node()->isTextNode()) { Text* textNode = static_cast<Text*>(insertionPosition.node()); bool atEnd = (unsigned)insertionPosition.deprecatedEditingOffset() >= textNode->length(); if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) { splitTextNode(textNode, insertionPosition.deprecatedEditingOffset()); insertionPosition.moveToOffset(0); visiblePos = VisiblePosition(insertionPosition); splitText = true; } } // Put the added block in the tree. if (nestNewBlock) appendNode(blockToInsert.get(), startBlock); else insertNodeAfter(blockToInsert.get(), startBlock); updateLayout(); // Make clones of ancestors in between the start node and the outer block. RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert); // If the paragraph separator was inserted at the end of a paragraph, an empty line must be // created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph // element. If the first node to be inserted won't be one that will hold an empty line open, add a br. if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos)) appendNode(createBreakElement(document()).get(), blockToInsert.get()); // Move the start node and the siblings of the start node. if (insertionPosition.node() != startBlock) { Node* n = insertionPosition.node(); if (insertionPosition.deprecatedEditingOffset() >= caretMaxOffset(n)) n = n->nextSibling(); while (n && n != blockToInsert) { Node *next = n->nextSibling(); removeNode(n); appendNode(n, parent.get()); n = next; } } // Move everything after the start node. if (!ancestors.isEmpty()) { Element* leftParent = ancestors.first(); while (leftParent && leftParent != startBlock) { parent = parent->parentElement(); if (!parent) break; Node* n = leftParent->nextSibling(); while (n && n != blockToInsert) { Node* next = n->nextSibling(); removeNode(n); appendNode(n, parent.get()); n = next; } leftParent = leftParent->parentElement(); } } // Handle whitespace that occurs after the split if (splitText) { updateLayout(); insertionPosition = Position(insertionPosition.node(), 0); if (!insertionPosition.isRenderedCharacter()) { // Clear out all whitespace and insert one non-breaking space ASSERT(!insertionPosition.node()->renderer() || insertionPosition.node()->renderer()->style()->collapseWhiteSpace()); deleteInsignificantTextDownstream(insertionPosition); if (insertionPosition.node()->isTextNode()) insertTextIntoNode(static_cast<Text*>(insertionPosition.node()), 0, nonBreakingSpaceString()); } } setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM)); applyStyleAfterInsertion(startBlock); }
void IndentOutdentCommand::outdentParagraph() { VisiblePosition visibleStartOfParagraph = startOfParagraph(endingSelection().visibleStart()); VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph); Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote); if (!enclosingNode || !enclosingNode->parentNode()->isContentEditable()) // We can't outdent if there is no place to go! return; // Use InsertListCommand to remove the selection from the list if (enclosingNode->hasTagName(olTag)) { applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList)); return; } if (enclosingNode->hasTagName(ulTag)) { applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList)); return; } // The selection is inside a blockquote i.e. enclosingNode is a blockquote VisiblePosition positionInEnclosingBlock = VisiblePosition(firstPositionInNode(enclosingNode)); // If the blockquote is inline, the start of the enclosing block coincides with // positionInEnclosingBlock. VisiblePosition startOfEnclosingBlock = (enclosingNode->renderer() && enclosingNode->renderer()->isInline()) ? positionInEnclosingBlock : startOfBlock(positionInEnclosingBlock); VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(lastPositionInNode(enclosingNode)); VisiblePosition endOfEnclosingBlock = endOfBlock(lastPositionInEnclosingBlock); if (visibleStartOfParagraph == startOfEnclosingBlock && visibleEndOfParagraph == endOfEnclosingBlock) { // The blockquote doesn't contain anything outside the paragraph, so it can be totally removed. Node* splitPoint = enclosingNode->nextSibling(); removeNodePreservingChildren(enclosingNode); // outdentRegion() assumes it is operating on the first paragraph of an enclosing blockquote, but if there are multiply nested blockquotes and we've // just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true if (splitPoint) { if (ContainerNode* splitPointParent = splitPoint->parentNode()) { if (splitPointParent->hasTagName(blockquoteTag) && !splitPoint->hasTagName(blockquoteTag) && splitPointParent->parentNode()->isContentEditable()) // We can't outdent if there is no place to go! splitElement(static_cast<Element*>(splitPointParent), splitPoint); } } updateLayout(); visibleStartOfParagraph = VisiblePosition(visibleStartOfParagraph.deepEquivalent()); visibleEndOfParagraph = VisiblePosition(visibleEndOfParagraph.deepEquivalent()); if (visibleStartOfParagraph.isNotNull() && !isStartOfParagraph(visibleStartOfParagraph)) insertNodeAt(createBreakElement(document()), visibleStartOfParagraph.deepEquivalent()); if (visibleEndOfParagraph.isNotNull() && !isEndOfParagraph(visibleEndOfParagraph)) insertNodeAt(createBreakElement(document()), visibleEndOfParagraph.deepEquivalent()); return; } Node* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().node()); RefPtr<Node> splitBlockquoteNode = enclosingNode; if (enclosingBlockFlow != enclosingNode) splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingNode, true); else { // We split the blockquote at where we start outdenting. splitElement(static_cast<Element*>(enclosingNode), visibleStartOfParagraph.deepEquivalent().node()); } RefPtr<Node> placeholder = createBreakElement(document()); insertNodeBefore(placeholder, splitBlockquoteNode); moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), positionBeforeNode(placeholder.get()), true); }
void ContainerNode::cloneChildNodes(ContainerNode *clone) { ExceptionCode ec = 0; for (Node* n = firstChild(); n && !ec; n = n->nextSibling()) clone->appendChild(n->cloneNode(true), ec); }
void InsertParagraphSeparatorCommand::doApply() { if (!endingSelection().isNonOrphanedCaretOrRange()) return; Position insertionPosition = endingSelection().start(); EAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { calculateStyleBeforeInsertion(insertionPosition); deleteSelection(false, true); insertionPosition = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The parentAnchoredEquivalent conversion needs to be moved into enclosingBlock. RefPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().containerNode()); Node* listChildNode = enclosingListChild(insertionPosition.parentAnchoredEquivalent().containerNode()); RefPtr<Element> listChild = listChildNode && listChildNode->isHTMLElement() ? toHTMLElement(listChildNode) : 0; Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent(); if (!startBlock || !startBlock->nonShadowBoundaryParentNode() || isTableCell(startBlock.get()) || startBlock->hasTagName(formTag) // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342 || (!canonicalPos.isNull() && isRenderedTable(canonicalPos.deprecatedNode())) || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->hasTagName(hrTag))) { applyCommandToComposite(InsertLineBreakCommand::create(document())); return; } // Use the leftmost candidate. insertionPosition = insertionPosition.upstream(); if (!insertionPosition.isCandidate()) insertionPosition = insertionPosition.downstream(); // Adjust the insertion position after the delete insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition); VisiblePosition visiblePos(insertionPosition, affinity); calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item if (breakOutOfEmptyListItem()) return; //--------------------------------------------------------------------- // Prepare for more general cases. bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); bool nestNewBlock = false; // Create block to be inserted. RefPtr<Element> blockToInsert; if (startBlock->isRootEditableElement()) { blockToInsert = createDefaultParagraphElement(document()); nestNewBlock = true; } else if (shouldUseDefaultParagraphElement(startBlock.get())) { blockToInsert = createDefaultParagraphElement(document()); } else { blockToInsert = startBlock->cloneElementWithoutChildren(); } //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, // including when the block is empty. if (isLastInBlock) { if (nestNewBlock) { if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) { // The block is empty. Create an empty block to // represent the paragraph that we're leaving. RefPtr<Element> extraBlock = createDefaultParagraphElement(document()); appendNode(extraBlock, startBlock); appendBlockPlaceholder(extraBlock); } appendNode(blockToInsert, startBlock); } else { // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted. if (m_pasteBlockqutoeIntoUnquotedArea) { if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) startBlock = toElement(highestBlockquote); } if (listChild && listChild != startBlock) { RefPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren(); appendNode(blockToInsert, listChildToInsert.get()); insertNodeAfter(listChildToInsert.get(), listChild); } else { // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, // for div nodes, this can result in nested div tags that are hard to break out of. Element* siblingNode = startBlock.get(); if (blockToInsert->hasTagName(divTag)) siblingNode = highestVisuallyEquivalentDivBelowRoot(startBlock.get()); insertNodeAfter(blockToInsert, siblingNode); } } // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock.get(), ancestors); RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert); appendBlockPlaceholder(parent); setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle case when position is in the first visible position in its block, and // similar case where previous position is in another, presumeably nested, block. if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) { Node* refNode = 0; insertionPosition = positionOutsideTabSpan(insertionPosition); if (isFirstInBlock && !nestNewBlock) { if (listChild && listChild != startBlock) { RefPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren(); appendNode(blockToInsert, listChildToInsert.get()); insertNodeBefore(listChildToInsert.get(), listChild); } else { refNode = startBlock.get(); } } else if (isFirstInBlock && nestNewBlock) { // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above. ASSERT(startBlock->firstChild()); refNode = startBlock->firstChild(); } else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) { refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset()); ASSERT(refNode); // must be true or we'd be in the end of block case } else refNode = insertionPosition.deprecatedNode(); // find ending selection position easily before inserting the paragraph insertionPosition = insertionPosition.downstream(); if (refNode) insertNodeBefore(blockToInsert, refNode); // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors); appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert)); // In this case, we need to set the new ending selection. setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle the (more complicated) general case, // All of the content in the current block after visiblePos is // about to be wrapped in a new paragraph element. Add a br before // it if visiblePos is at the start of a paragraph so that the // content will move down a line. if (isStartOfParagraph(visiblePos)) { RefPtr<Element> br = createBreakElement(document()); insertNodeAt(br.get(), insertionPosition); insertionPosition = positionInParentAfterNode(br.get()); // If the insertion point is a break element, there is nothing else // we need to do. if (visiblePos.deepEquivalent().anchorNode()->renderer()->isBR()) { setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional())); return; } } // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. insertionPosition = insertionPosition.downstream(); // At this point, the insertionPosition's node could be a container, and we want to make sure we include // all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position // before we walk the DOM tree. insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent()); // If the returned position lies either at the end or at the start of an element that is ignored by editing // we should move to its upstream or downstream position. if (editingIgnoresContent(insertionPosition.deprecatedNode())) { if (insertionPosition.atLastEditingPositionForNode()) insertionPosition = insertionPosition.downstream(); else if (insertionPosition.atFirstEditingPositionForNode()) insertionPosition = insertionPosition.upstream(); } // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but pos.downstream() does not give it Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) { Text* textNode = toText(leadingWhitespace.deprecatedNode()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. Position positionAfterSplit; if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) { RefPtr<Text> textNode = toText(insertionPosition.containerNode()); bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length(); if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) { splitTextNode(textNode, insertionPosition.offsetInContainerNode()); positionAfterSplit = firstPositionInNode(textNode.get()); insertionPosition.moveToPosition(textNode->previousSibling(), insertionPosition.offsetInContainerNode()); visiblePos = VisiblePosition(insertionPosition); } } // If we got detached due to mutation events, just bail out. if (!startBlock->parentNode()) return; // Put the added block in the tree. if (nestNewBlock) { appendNode(blockToInsert.get(), startBlock); } else if (listChild && listChild != startBlock) { RefPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren(); appendNode(blockToInsert.get(), listChildToInsert.get()); insertNodeAfter(listChildToInsert.get(), listChild); } else { insertNodeAfter(blockToInsert.get(), startBlock); } document().updateLayoutIgnorePendingStylesheets(); // If the paragraph separator was inserted at the end of a paragraph, an empty line must be // created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph // element. If the first node to be inserted won't be one that will hold an empty line open, add a br. if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos)) appendNode(createBreakElement(document()).get(), blockToInsert.get()); // Move the start node and the siblings of the start node. if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) { Node* n; if (insertionPosition.containerNode() == startBlock) n = insertionPosition.computeNodeAfterPosition(); else { Node* splitTo = insertionPosition.containerNode(); if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo)) splitTo = NodeTraversal::next(*splitTo, startBlock.get()); ASSERT(splitTo); splitTreeToNode(splitTo, startBlock.get()); for (n = startBlock->firstChild(); n; n = n->nextSibling()) { VisiblePosition beforeNodePosition = positionBeforeNode(n); if (!beforeNodePosition.isNull() && comparePositions(VisiblePosition(insertionPosition), beforeNodePosition) <= 0) break; } } moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert); } // Handle whitespace that occurs after the split if (positionAfterSplit.isNotNull()) { document().updateLayoutIgnorePendingStylesheets(); if (!positionAfterSplit.isRenderedCharacter()) { // Clear out all whitespace and insert one non-breaking space ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style()->collapseWhiteSpace()); deleteInsignificantTextDownstream(positionAfterSplit); if (positionAfterSplit.deprecatedNode()->isTextNode()) insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString()); } } setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); applyStyleAfterInsertion(startBlock.get()); }
bool SVGAltGlyphDefElement::hasValidGlyphElements(Vector<String>& glyphNames) const { // Spec: http://www.w3.org/TR/SVG/text.html#AltGlyphDefElement // An 'altGlyphDef' can contain either of the following: // // 1. In the simplest case, an 'altGlyphDef' contains one or more 'glyphRef' elements. // Each 'glyphRef' element references a single glyph within a particular font. // If all of the referenced glyphs are available, then these glyphs are rendered // instead of the character(s) inside of the referencing 'altGlyph' element. // If any of the referenced glyphs are unavailable, then the character(s) that are // inside of the 'altGlyph' element are rendered as if there were not an 'altGlyph' // element surrounding those characters. // // 2. In the more complex case, an 'altGlyphDef' contains one or more 'altGlyphItem' elements. // Each 'altGlyphItem' represents a candidate set of substitute glyphs. Each 'altGlyphItem' // contains one or more 'glyphRef' elements. Each 'glyphRef' element references a single // glyph within a particular font. The first 'altGlyphItem' in which all referenced glyphs // are available is chosen. The glyphs referenced from this 'altGlyphItem' are rendered // instead of the character(s) that are inside of the referencing 'altGlyph' element. // If none of the 'altGlyphItem' elements result in a successful match (i.e., none of the // 'altGlyphItem' elements has all of its referenced glyphs available), then the character(s) // that are inside of the 'altGlyph' element are rendered as if there were not an 'altGlyph' // element surrounding those characters. // // The spec doesn't tell how to deal with the mixing of <glyphRef> and <altGlyItem>. // However, we determine content model by the the type of the first appearing element // just like Opera 11 does. After the content model is determined we skip elements // which don't comform to it. For example: // a. <altGlyphDef> // <glyphRef id="g1" /> // <altGlyphItem id="i1"> ... </altGlyphItem> // <glyphRef id="g2" /> // </altGlyphDef> // // b. <altGlyphDef> // <altGlyphItem id="i1"> ... </altGlyphItem> // <altGlyphItem id="i2"> ... </altGlyphItem> // <glyphRef id="g1" /> // <glyphRef id="g2" /> // </altGlyphDef> // For a), the content model is 1), so we will use "g1" and "g2" if they are all valid // and "i1" is skipped. // For b), the content model is 2), so we will use <glyphRef> elements contained in // "i1" if they are valid and "g1" and "g2" are skipped. // These 2 variables are used to determine content model. bool fountFirstGlyphRef = false; bool foundFirstAltGlyphItem = false; for (Node* child = firstChild(); child; child = child->nextSibling()) { if (!foundFirstAltGlyphItem && child->hasTagName(SVGNames::glyphRefTag)) { fountFirstGlyphRef = true; String referredGlyphName; if (toSVGGlyphRefElement(child)->hasValidGlyphElement(referredGlyphName)) glyphNames.append(referredGlyphName); else { // As the spec says "If any of the referenced glyphs are unavailable, // then the character(s) that are inside of the 'altGlyph' element are // rendered as if there were not an 'altGlyph' element surrounding // those characters.". glyphNames.clear(); return false; } } else if (!fountFirstGlyphRef && child->hasTagName(SVGNames::altGlyphItemTag)) { foundFirstAltGlyphItem = true; Vector<String> referredGlyphNames; // As the spec says "The first 'altGlyphItem' in which all referenced glyphs // are available is chosen." if (static_cast<SVGAltGlyphItemElement*>(child)->hasValidGlyphElements(glyphNames) && !glyphNames.isEmpty()) return true; } } return !glyphNames.isEmpty(); }
// Result nodes are ordered in axis order. Node test (including merged predicates) is applied. void Step::nodesInAxis(Node* context, NodeSet& nodes) const { ASSERT(nodes.isEmpty()); switch (m_axis) { case ChildAxis: if (context->isAttributeNode()) // In XPath model, attribute nodes do not have children. return; for (Node* n = context->firstChild(); n; n = n->nextSibling()) if (nodeMatches(n, ChildAxis, m_nodeTest)) nodes.append(n); return; case DescendantAxis: if (context->isAttributeNode()) // In XPath model, attribute nodes do not have children. return; for (Node* n = context->firstChild(); n; n = n->traverseNextNode(context)) if (nodeMatches(n, DescendantAxis, m_nodeTest)) nodes.append(n); return; case ParentAxis: if (context->isAttributeNode()) { Element* n = static_cast<Attr*>(context)->ownerElement(); if (nodeMatches(n, ParentAxis, m_nodeTest)) nodes.append(n); } else { ContainerNode* n = context->parentNode(); if (n && nodeMatches(n, ParentAxis, m_nodeTest)) nodes.append(n); } return; case AncestorAxis: { Node* n = context; if (context->isAttributeNode()) { n = static_cast<Attr*>(context)->ownerElement(); if (nodeMatches(n, AncestorAxis, m_nodeTest)) nodes.append(n); } for (n = n->parentNode(); n; n = n->parentNode()) if (nodeMatches(n, AncestorAxis, m_nodeTest)) nodes.append(n); nodes.markSorted(false); return; } case FollowingSiblingAxis: if (context->nodeType() == Node::ATTRIBUTE_NODE || context->nodeType() == Node::XPATH_NAMESPACE_NODE) return; for (Node* n = context->nextSibling(); n; n = n->nextSibling()) if (nodeMatches(n, FollowingSiblingAxis, m_nodeTest)) nodes.append(n); return; case PrecedingSiblingAxis: if (context->nodeType() == Node::ATTRIBUTE_NODE || context->nodeType() == Node::XPATH_NAMESPACE_NODE) return; for (Node* n = context->previousSibling(); n; n = n->previousSibling()) if (nodeMatches(n, PrecedingSiblingAxis, m_nodeTest)) nodes.append(n); nodes.markSorted(false); return; case FollowingAxis: if (context->isAttributeNode()) { Node* p = static_cast<Attr*>(context)->ownerElement(); while ((p = p->traverseNextNode())) if (nodeMatches(p, FollowingAxis, m_nodeTest)) nodes.append(p); } else { for (Node* p = context; !isRootDomNode(p); p = p->parentNode()) { for (Node* n = p->nextSibling(); n; n = n->nextSibling()) { if (nodeMatches(n, FollowingAxis, m_nodeTest)) nodes.append(n); for (Node* c = n->firstChild(); c; c = c->traverseNextNode(n)) if (nodeMatches(c, FollowingAxis, m_nodeTest)) nodes.append(c); } } } return; case PrecedingAxis: { if (context->isAttributeNode()) context = static_cast<Attr*>(context)->ownerElement(); Node* n = context; while (Node* parent = n->parent()) { for (n = n->traversePreviousNode(); n != parent; n = n->traversePreviousNode()) if (nodeMatches(n, PrecedingAxis, m_nodeTest)) nodes.append(n); n = parent; } nodes.markSorted(false); return; } case AttributeAxis: { if (context->nodeType() != Node::ELEMENT_NODE) return; // Avoid lazily creating attribute nodes for attributes that we do not need anyway. if (m_nodeTest.kind() == NodeTest::NameTest && m_nodeTest.data() != starAtom) { RefPtr<Node> n = static_cast<Element*>(context)->getAttributeNodeNS(m_nodeTest.namespaceURI(), m_nodeTest.data()); if (n && n->namespaceURI() != XMLNSNames::xmlnsNamespaceURI) { // In XPath land, namespace nodes are not accessible on the attribute axis. if (nodeMatches(n.get(), AttributeAxis, m_nodeTest)) // Still need to check merged predicates. nodes.append(n.release()); } return; } NamedNodeMap* attrs = context->attributes(); if (!attrs) return; for (unsigned i = 0; i < attrs->length(); ++i) { RefPtr<Attr> attr = attrs->attributeItem(i)->createAttrIfNeeded(static_cast<Element*>(context)); if (nodeMatches(attr.get(), AttributeAxis, m_nodeTest)) nodes.append(attr.release()); } return; } case NamespaceAxis: // XPath namespace nodes are not implemented yet. return; case SelfAxis: if (nodeMatches(context, SelfAxis, m_nodeTest)) nodes.append(context); return; case DescendantOrSelfAxis: if (nodeMatches(context, DescendantOrSelfAxis, m_nodeTest)) nodes.append(context); if (context->isAttributeNode()) // In XPath model, attribute nodes do not have children. return; for (Node* n = context->firstChild(); n; n = n->traverseNextNode(context)) if (nodeMatches(n, DescendantOrSelfAxis, m_nodeTest)) nodes.append(n); return; case AncestorOrSelfAxis: { if (nodeMatches(context, AncestorOrSelfAxis, m_nodeTest)) nodes.append(context); Node* n = context; if (context->isAttributeNode()) { n = static_cast<Attr*>(context)->ownerElement(); if (nodeMatches(n, AncestorOrSelfAxis, m_nodeTest)) nodes.append(n); } for (n = n->parentNode(); n; n = n->parentNode()) if (nodeMatches(n, AncestorOrSelfAxis, m_nodeTest)) nodes.append(n); nodes.markSorted(false); return; } } ASSERT_NOT_REACHED(); }
void RenderEmbeddedObject::updateWidget(bool onlyCreateNonNetscapePlugins) { if (!m_replacementText.isNull() || !node()) // Check the node in case destroy() has been called. return; String url; String serviceType; Vector<String> paramNames; Vector<String> paramValues; Frame* frame = frameView()->frame(); // The calls to SubframeLoader::requestObject within this function can result in a plug-in being initialized. // This can run cause arbitrary JavaScript to run and may result in this RenderObject being detached from // the render tree and destroyed, causing a crash like <rdar://problem/6954546>. By extending our lifetime // artifically to ensure that we remain alive for the duration of plug-in initialization. RenderWidgetProtector protector(this); if (node()->hasTagName(objectTag)) { HTMLObjectElement* objectElement = static_cast<HTMLObjectElement*>(node()); objectElement->setNeedWidgetUpdate(false); if (!objectElement->isFinishedParsingChildren()) return; // Check for a child EMBED tag. HTMLEmbedElement* embed = 0; for (Node* child = objectElement->firstChild(); child; ) { if (child->hasTagName(embedTag)) { embed = static_cast<HTMLEmbedElement*>(child); break; } if (child->hasTagName(objectTag)) child = child->nextSibling(); // Don't descend into nested OBJECT tags else child = child->traverseNextNode(objectElement); // Otherwise descend (EMBEDs may be inside COMMENT tags) } // Use the attributes from the EMBED tag instead of the OBJECT tag including WIDTH and HEIGHT. HTMLElement* embedOrObject; if (embed) { embedOrObject = embed; url = embed->url(); serviceType = embed->serviceType(); } else embedOrObject = objectElement; // If there was no URL or type defined in EMBED, try the OBJECT tag. if (url.isEmpty()) url = objectElement->url(); if (serviceType.isEmpty()) serviceType = objectElement->serviceType(); HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames; // Scan the PARAM children. // Get the URL and type from the params if we don't already have them. // Get the attributes from the params if there is no EMBED tag. Node* child = objectElement->firstChild(); while (child && (url.isEmpty() || serviceType.isEmpty() || !embed)) { if (child->hasTagName(paramTag)) { HTMLParamElement* p = static_cast<HTMLParamElement*>(child); String name = p->name(); if (url.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url"))) url = p->value(); if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) { serviceType = p->value(); int pos = serviceType.find(";"); if (pos != -1) serviceType = serviceType.left(pos); } if (!embed && !name.isEmpty()) { uniqueParamNames.add(name.impl()); paramNames.append(p->name()); paramValues.append(p->value()); } } child = child->nextSibling(); } // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM, // else our Java plugin will misinterpret it. [4004531] String codebase; if (!embed && MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) { codebase = "codebase"; uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already } // Turn the attributes of either the EMBED tag or OBJECT tag into arrays, but don't override PARAM values. NamedNodeMap* attributes = embedOrObject->attributes(); if (attributes) { for (unsigned i = 0; i < attributes->length(); ++i) { Attribute* it = attributes->attributeItem(i); const AtomicString& name = it->name().localName(); if (embed || !uniqueParamNames.contains(name.impl())) { paramNames.append(name.string()); paramValues.append(it->value().string()); } } } mapDataParamToSrc(¶mNames, ¶mValues); // If we still don't have a type, try to map from a specific CLASSID to a type. if (serviceType.isEmpty()) serviceType = serviceTypeForClassId(objectElement->classId()); if (!isURLAllowed(document(), url)) return; // Find out if we support fallback content. m_hasFallbackContent = false; for (Node* child = objectElement->firstChild(); child && !m_hasFallbackContent; child = child->nextSibling()) { if ((!child->isTextNode() && !child->hasTagName(embedTag) && !child->hasTagName(paramTag)) // Discount <embed> and <param> || (child->isTextNode() && !static_cast<Text*>(child)->containsOnlyWhitespace())) m_hasFallbackContent = true; } if (onlyCreateNonNetscapePlugins) { KURL completedURL; if (!url.isEmpty()) completedURL = frame->loader()->completeURL(url); if (frame->loader()->client()->objectContentType(completedURL, serviceType) == ObjectContentNetscapePlugin) return; } bool beforeLoadAllowedLoad = objectElement->dispatchBeforeLoadEvent(url); // beforeload events can modify the DOM, potentially causing // RenderWidget::destroy() to be called. Ensure we haven't been // destroyed before continuing. if (!node()) return; bool success = beforeLoadAllowedLoad && frame->loader()->subframeLoader()->requestObject(this, url, objectElement->getAttribute(nameAttr), serviceType, paramNames, paramValues); if (!success && m_hasFallbackContent) objectElement->renderFallbackContent(); } else if (node()->hasTagName(embedTag)) { HTMLEmbedElement* embedElement = static_cast<HTMLEmbedElement*>(node()); embedElement->setNeedWidgetUpdate(false); url = embedElement->url(); serviceType = embedElement->serviceType(); if (url.isEmpty() && serviceType.isEmpty()) return; if (!isURLAllowed(document(), url)) return; // add all attributes set on the embed object NamedNodeMap* attributes = embedElement->attributes(); if (attributes) { for (unsigned i = 0; i < attributes->length(); ++i) { Attribute* it = attributes->attributeItem(i); paramNames.append(it->name().localName().string()); paramValues.append(it->value().string()); } } if (onlyCreateNonNetscapePlugins) { KURL completedURL; if (!url.isEmpty()) completedURL = frame->loader()->completeURL(url); if (frame->loader()->client()->objectContentType(completedURL, serviceType) == ObjectContentNetscapePlugin) return; } if (embedElement->dispatchBeforeLoadEvent(url)) frame->loader()->subframeLoader()->requestObject(this, url, embedElement->getAttribute(nameAttr), serviceType, paramNames, paramValues); } #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) else if (node()->hasTagName(videoTag) || node()->hasTagName(audioTag)) { HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(node()); KURL kurl; mediaElement->getPluginProxyParams(kurl, paramNames, paramValues); mediaElement->setNeedWidgetUpdate(false); frame->loader()->subframeLoader()->loadMediaPlayerProxyPlugin(node(), kurl, paramNames, paramValues); } #endif }
void InsertListCommand::doApply() { if (endingSelection().isNone()) return; if (endingSelection().isRange() && modifyRange()) return; if (!endingSelection().rootEditableElement()) return; Node* selectionNode = endingSelection().start().node(); const QualifiedName listTag = (m_type == OrderedListType) ? olTag : ulTag; Node* listChildNode = enclosingListChild(selectionNode); bool switchListType = false; if (listChildNode) { // Remove the list chlild. Node* listNode = enclosingList(listChildNode); if (!listNode) listNode = fixOrphanedListChild(listChildNode); if (!listNode->hasTagName(listTag)) // listChildNode will be removed from the list and a list of type m_type will be created. switchListType = true; Node* nextListChild; Node* previousListChild; VisiblePosition start; VisiblePosition end; if (listChildNode->hasTagName(liTag)) { start = VisiblePosition(Position(listChildNode, 0)); end = VisiblePosition(Position(listChildNode, maxDeepOffset(listChildNode))); nextListChild = listChildNode->nextSibling(); previousListChild = listChildNode->previousSibling(); } else { // A paragraph is visually a list item minus a list marker. The paragraph will be moved. start = startOfParagraph(endingSelection().visibleStart()); end = endOfParagraph(endingSelection().visibleEnd()); nextListChild = enclosingListChild(end.next().deepEquivalent().node()); ASSERT(nextListChild != listChildNode); if (enclosingList(nextListChild) != listNode) nextListChild = 0; previousListChild = enclosingListChild(start.previous().deepEquivalent().node()); ASSERT(previousListChild != listChildNode); if (enclosingList(previousListChild) != listNode) previousListChild = 0; } // When removing a list, we must always create a placeholder to act as a point of insertion // for the list content being removed. RefPtr<Element> placeholder = createBreakElement(document()); if (nextListChild && previousListChild) { splitElement(static_cast<Element *>(listNode), nextListChild); insertNodeBefore(placeholder.get(), listNode); } else if (nextListChild) insertNodeBefore(placeholder.get(), listNode); else insertNodeAfter(placeholder.get(), listNode); VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0)); moveParagraphs(start, end, insertionPoint, true); } if (!listChildNode || switchListType || m_forceCreateList) { // Create list. VisiblePosition start = startOfParagraph(endingSelection().visibleStart()); VisiblePosition end = endOfParagraph(endingSelection().visibleEnd()); // Check for adjoining lists. VisiblePosition previousPosition = start.previous(true); VisiblePosition nextPosition = end.next(true); RefPtr<Element> listItemElement = createListItemElement(document()); Node* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node()); Node* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node()); if (previousList && !previousList->hasTagName(listTag)) previousList = 0; if (nextList && !nextList->hasTagName(listTag)) nextList = 0; // Stitch matching adjoining lists together. if (previousList) appendNode(listItemElement.get(), previousList); else if (nextList) appendNode(listItemElement.get(), nextList); else { // Create the list. RefPtr<Element> listElement = m_type == OrderedListType ? createOrderedListElement(document()) : createUnorderedListElement(document()); static_cast<HTMLElement*>(listElement.get())->setId(m_id); appendNode(listItemElement.get(), listElement.get()); insertNodeAt(listElement.get(), start.deepEquivalent().node(), start.deepEquivalent().offset()); } moveParagraph(start, end, VisiblePosition(Position(listItemElement.get(), 0)), true); if (nextList && previousList) mergeIdenticalElements(static_cast<Element*>(previousList), static_cast<Element*>(nextList)); } }
void resolveTree(Element& current, Change change) { ASSERT(change != Detach); if (current.hasCustomStyleResolveCallbacks()) { if (!current.willRecalcStyle(change)) return; } ContainerNode* renderingParentNode = NodeRenderingTraversal::parent(¤t); bool hasParentStyle = renderingParentNode && renderingParentNode->renderStyle(); bool hasDirectAdjacentRules = current.childrenAffectedByDirectAdjacentRules(); bool hasIndirectAdjacentRules = current.childrenAffectedByForwardPositionalRules(); #if PLATFORM(IOS) CheckForVisibilityChangeOnRecalcStyle checkForVisibilityChange(current, current->renderStyle()); #endif if (change > NoChange || current.needsStyleRecalc()) current.resetComputedStyle(); if (hasParentStyle && (change >= Inherit || current.needsStyleRecalc())) change = resolveLocal(current, change); if (change != Detach) { StyleResolverParentPusher parentPusher(¤t); RenderStyle* currentStyle = current.renderStyle(); if (ShadowRoot* shadowRoot = current.shadowRoot()) { if (change >= Inherit || shadowRoot->childNeedsStyleRecalc() || shadowRoot->needsStyleRecalc()) { parentPusher.push(); resolveShadowTree(shadowRoot, currentStyle, change); } } current.updateBeforePseudoElement(change); // FIXME: This check is good enough for :hover + foo, but it is not good enough for :hover + foo + bar. // For now we will just worry about the common case, since it's a lot trickier to get the second case right // without doing way too much re-resolution. bool forceCheckOfNextElementSibling = false; bool forceCheckOfAnyElementSibling = false; for (Node* child = current.firstChild(); child; child = child->nextSibling()) { if (child->isTextNode()) { updateTextStyle(*toText(child), currentStyle, change); continue; } if (!child->isElementNode()) continue; Element* childElement = toElement(child); bool childRulesChanged = childElement->needsStyleRecalc() && childElement->styleChangeType() == FullStyleChange; if ((forceCheckOfNextElementSibling || forceCheckOfAnyElementSibling)) childElement->setNeedsStyleRecalc(); if (change >= Inherit || childElement->childNeedsStyleRecalc() || childElement->needsStyleRecalc()) { parentPusher.push(); resolveTree(*childElement, change); } forceCheckOfNextElementSibling = childRulesChanged && hasDirectAdjacentRules; forceCheckOfAnyElementSibling = forceCheckOfAnyElementSibling || (childRulesChanged && hasIndirectAdjacentRules); } current.updateAfterPseudoElement(change); } current.clearNeedsStyleRecalc(); current.clearChildNeedsStyleRecalc(); if (current.hasCustomStyleResolveCallbacks()) current.didRecalcStyle(change); }
PassRefPtr<SimpleFontData> CSSFontFaceSource::getFontData(const FontDescription& fontDescription, bool syntheticBold, bool syntheticItalic, CSSFontSelector* fontSelector) { // If the font hasn't loaded or an error occurred, then we've got nothing. if (!isValid()) return 0; if (!m_font #if ENABLE(SVG_FONTS) && !m_svgFontFaceElement #endif ) { // We're local. Just return a SimpleFontData from the normal cache. // We don't want to check alternate font family names here, so pass true as the checkingAlternateName parameter. return fontCache()->getCachedFontData(fontDescription, m_string, true); } // See if we have a mapping in our FontData cache. unsigned hashKey = (fontDescription.computedPixelSize() + 1) << 5 | fontDescription.widthVariant() << 3 | (fontDescription.orientation() == Vertical ? 4 : 0) | (syntheticBold ? 2 : 0) | (syntheticItalic ? 1 : 0); RefPtr<SimpleFontData>& fontData = m_fontDataTable.add(hashKey, 0).iterator->value; if (fontData) return fontData; // No release, because fontData is a reference to a RefPtr that is held in the m_fontDataTable. // If we are still loading, then we let the system pick a font. if (isLoaded()) { if (m_font) { #if ENABLE(SVG_FONTS) if (m_hasExternalSVGFont) { // For SVG fonts parse the external SVG document, and extract the <font> element. if (!m_font->ensureSVGFontData()) return 0; if (!m_externalSVGFontElement) { String fragmentIdentifier; size_t start = m_string.find('#'); if (start != notFound) fragmentIdentifier = m_string.string().substring(start + 1); m_externalSVGFontElement = m_font->getSVGFontById(fragmentIdentifier); } if (!m_externalSVGFontElement) return 0; SVGFontFaceElement* fontFaceElement = 0; // Select first <font-face> child for (Node* fontChild = m_externalSVGFontElement->firstChild(); fontChild; fontChild = fontChild->nextSibling()) { if (fontChild->hasTagName(SVGNames::font_faceTag)) { fontFaceElement = static_cast<SVGFontFaceElement*>(fontChild); break; } } if (fontFaceElement) { if (!m_svgFontFaceElement) { // We're created using a CSS @font-face rule, that means we're not associated with a SVGFontFaceElement. // Use the imported <font-face> tag as referencing font-face element for these cases. m_svgFontFaceElement = fontFaceElement; } fontData = SimpleFontData::create(SVGFontData::create(fontFaceElement), fontDescription.computedPixelSize(), syntheticBold, syntheticItalic); } } else #endif { // Create new FontPlatformData from our CGFontRef, point size and ATSFontRef. if (!m_font->ensureCustomFontData()) return 0; fontData = SimpleFontData::create(m_font->platformDataFromCustomData(fontDescription.computedPixelSize(), syntheticBold, syntheticItalic, fontDescription.orientation(), fontDescription.widthVariant(), fontDescription.renderingMode()), true, false); } } else { #if ENABLE(SVG_FONTS) // In-Document SVG Fonts if (m_svgFontFaceElement) fontData = SimpleFontData::create(SVGFontData::create(m_svgFontFaceElement.get()), fontDescription.computedPixelSize(), syntheticBold, syntheticItalic); #endif } } else { // Kick off the load. Do it soon rather than now, because we may be in the middle of layout, // and the loader may invoke arbitrary delegate or event handler code. fontSelector->beginLoadingFontSoon(m_font.get()); // This temporary font is not retained and should not be returned. FontCachePurgePreventer fontCachePurgePreventer; SimpleFontData* temporaryFont = fontCache()->getNonRetainedLastResortFallbackFont(fontDescription); fontData = SimpleFontData::create(temporaryFont->platformData(), true, true); } return fontData; // No release, because fontData is a reference to a RefPtr that is held in the m_fontDataTable. }
bool SelectorChecker::checkOne(const SelectorCheckingContext& context) const { Element* const & element = context.element; const CSSSelector* const & selector = context.selector; ASSERT(element); ASSERT(selector); if (selector->m_match == CSSSelector::Tag) return SelectorChecker::tagMatches(element, selector->tagQName()); if (selector->m_match == CSSSelector::Class) return element->hasClass() && element->classNames().contains(selector->value()); if (selector->m_match == CSSSelector::Id) return element->hasID() && element->idForStyleResolution() == selector->value(); if (selector->isAttributeSelector()) { const QualifiedName& attr = selector->attribute(); if (!element->hasAttributes()) return false; bool caseSensitive = !m_documentIsHTML || HTMLDocument::isCaseSensitiveAttribute(attr); if (!anyAttributeMatches(element, static_cast<CSSSelector::Match>(selector->m_match), attr, selector->value(), caseSensitive)) return false; } if (selector->m_match == CSSSelector::PseudoClass) { // Handle :not up front. if (selector->pseudoType() == CSSSelector::PseudoNot) { const CSSSelectorList* selectorList = selector->selectorList(); // FIXME: We probably should fix the parser and make it never produce :not rules with missing selector list. if (!selectorList) return false; SelectorCheckingContext subContext(context); subContext.isSubSelector = true; for (subContext.selector = selectorList->first(); subContext.selector; subContext.selector = subContext.selector->tagHistory()) { // :not cannot nest. I don't really know why this is a // restriction in CSS3, but it is, so let's honor it. // the parser enforces that this never occurs ASSERT(subContext.selector->pseudoType() != CSSSelector::PseudoNot); // We select between :visited and :link when applying. We don't know which one applied (or not) yet. if (subContext.selector->pseudoType() == CSSSelector::PseudoVisited || (subContext.selector->pseudoType() == CSSSelector::PseudoLink && subContext.visitedMatchType == VisitedMatchEnabled)) return true; if (!checkOne(subContext)) return true; } } else if (context.hasScrollbarPseudo) { // CSS scrollbars match a specific subset of pseudo classes, and they have specialized rules for each // (since there are no elements involved). return checkScrollbarPseudoClass(context, element->document(), selector); } else if (context.hasSelectionPseudo) { if (selector->pseudoType() == CSSSelector::PseudoWindowInactive) return !element->document()->page()->focusController()->isActive(); } // Normal element pseudo class checking. switch (selector->pseudoType()) { // Pseudo classes: case CSSSelector::PseudoNot: break; // Already handled up above. case CSSSelector::PseudoEmpty: { bool result = true; for (Node* n = element->firstChild(); n; n = n->nextSibling()) { if (n->isElementNode()) { result = false; break; } if (n->isTextNode()) { Text* textNode = toText(n); if (!textNode->data().isEmpty()) { result = false; break; } } } if (m_mode == ResolvingStyle) { element->setStyleAffectedByEmpty(); if (context.elementStyle) context.elementStyle->setEmptyState(result); else if (element->renderStyle() && (element->document()->styleSheetCollection()->usesSiblingRules() || element->renderStyle()->unique())) element->renderStyle()->setEmptyState(result); } return result; } case CSSSelector::PseudoFirstChild: // first-child matches the first child that is an element if (Element* parentElement = element->parentElement()) { bool result = isFirstChildElement(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); parentElement->setChildrenAffectedByFirstChildRules(); if (result && childStyle) childStyle->setFirstChildState(); } return result; } break; case CSSSelector::PseudoFirstOfType: // first-of-type matches the first element of its type if (Element* parentElement = element->parentElement()) { bool result = isFirstOfType(element, element->tagQName()); if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByForwardPositionalRules(); return result; } break; case CSSSelector::PseudoLastChild: // last-child matches the last child that is an element if (Element* parentElement = element->parentElement()) { bool result = parentElement->isFinishedParsingChildren() && isLastChildElement(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); parentElement->setChildrenAffectedByLastChildRules(); if (result && childStyle) childStyle->setLastChildState(); } return result; } break; case CSSSelector::PseudoLastOfType: // last-of-type matches the last element of its type if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByBackwardPositionalRules(); if (!parentElement->isFinishedParsingChildren()) return false; return isLastOfType(element, element->tagQName()); } break; case CSSSelector::PseudoOnlyChild: if (Element* parentElement = element->parentElement()) { bool firstChild = isFirstChildElement(element); bool onlyChild = firstChild && parentElement->isFinishedParsingChildren() && isLastChildElement(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); parentElement->setChildrenAffectedByFirstChildRules(); parentElement->setChildrenAffectedByLastChildRules(); if (firstChild && childStyle) childStyle->setFirstChildState(); if (onlyChild && childStyle) childStyle->setLastChildState(); } return onlyChild; } break; case CSSSelector::PseudoOnlyOfType: // FIXME: This selector is very slow. if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) { parentElement->setChildrenAffectedByForwardPositionalRules(); parentElement->setChildrenAffectedByBackwardPositionalRules(); } if (!parentElement->isFinishedParsingChildren()) return false; return isFirstOfType(element, element->tagQName()) && isLastOfType(element, element->tagQName()); } break; case CSSSelector::PseudoNthChild: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { int count = 1 + countElementsBefore(element); if (m_mode == ResolvingStyle) { RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle(); element->setChildIndex(count); if (childStyle) childStyle->setUnique(); parentElement->setChildrenAffectedByForwardPositionalRules(); } if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoNthOfType: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { int count = 1 + countElementsOfTypeBefore(element, element->tagQName()); if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByForwardPositionalRules(); if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoNthLastChild: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByBackwardPositionalRules(); if (!parentElement->isFinishedParsingChildren()) return false; int count = 1 + countElementsAfter(element); if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoNthLastOfType: if (!selector->parseNth()) break; if (Element* parentElement = element->parentElement()) { if (m_mode == ResolvingStyle) parentElement->setChildrenAffectedByBackwardPositionalRules(); if (!parentElement->isFinishedParsingChildren()) return false; int count = 1 + countElementsOfTypeAfter(element, element->tagQName()); if (selector->matchNth(count)) return true; } break; case CSSSelector::PseudoTarget: if (element == element->document()->cssTarget()) return true; break; case CSSSelector::PseudoAny: { SelectorCheckingContext subContext(context); subContext.isSubSelector = true; PseudoId ignoreDynamicPseudo = NOPSEUDO; for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) { if (match(subContext, ignoreDynamicPseudo) == SelectorMatches) return true; } } break; case CSSSelector::PseudoAutofill: if (!element->isFormControlElement()) break; if (HTMLInputElement* inputElement = element->toInputElement()) return inputElement->isAutofilled(); break; case CSSSelector::PseudoAnyLink: case CSSSelector::PseudoLink: // :visited and :link matches are separated later when applying the style. Here both classes match all links... return element->isLink(); case CSSSelector::PseudoVisited: // ...except if :visited matching is disabled for ancestor/sibling matching. return element->isLink() && context.visitedMatchType == VisitedMatchEnabled; case CSSSelector::PseudoDrag: if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByDrag(); else element->setChildrenAffectedByDrag(true); } if (element->renderer() && element->renderer()->isDragging()) return true; break; case CSSSelector::PseudoFocus: return matchesFocusPseudoClass(element); case CSSSelector::PseudoHover: // If we're in quirks mode, then hover should never match anchors with no // href and *:hover should not match anything. This is important for sites like wsj.com. if (m_strictParsing || context.isSubSelector || (selector->m_match == CSSSelector::Tag && selector->tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element->isLink()) { if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByHover(); else element->setChildrenAffectedByHover(true); } if (element->hovered() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoHover)) return true; } break; case CSSSelector::PseudoActive: // If we're in quirks mode, then :active should never match anchors with no // href and *:active should not match anything. if (m_strictParsing || context.isSubSelector || (selector->m_match == CSSSelector::Tag && selector->tagQName() != anyQName() && !isHTMLAnchorElement(element)) || element->isLink()) { if (m_mode == ResolvingStyle) { if (context.elementStyle) context.elementStyle->setAffectedByActive(); else element->setChildrenAffectedByActive(true); } if (element->active() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoActive)) return true; } break; case CSSSelector::PseudoEnabled: if (element->isFormControlElement() || isHTMLOptionElement(element) || isHTMLOptGroupElement(element)) return !element->isDisabledFormControl(); break; case CSSSelector::PseudoFullPageMedia: return element->document() && element->document()->isMediaDocument(); break; case CSSSelector::PseudoDefault: return element->isDefaultButtonForForm(); case CSSSelector::PseudoDisabled: if (element->isFormControlElement() || isHTMLOptionElement(element) || isHTMLOptGroupElement(element)) return element->isDisabledFormControl(); break; case CSSSelector::PseudoReadOnly: return element->matchesReadOnlyPseudoClass(); case CSSSelector::PseudoReadWrite: return element->matchesReadWritePseudoClass(); case CSSSelector::PseudoOptional: return element->isOptionalFormControl(); case CSSSelector::PseudoRequired: return element->isRequiredFormControl(); case CSSSelector::PseudoValid: element->document()->setContainsValidityStyleRules(); return element->willValidate() && element->isValidFormControlElement(); case CSSSelector::PseudoInvalid: element->document()->setContainsValidityStyleRules(); return element->willValidate() && !element->isValidFormControlElement(); case CSSSelector::PseudoChecked: { // Even though WinIE allows checked and indeterminate to co-exist, the CSS selector spec says that // you can't be both checked and indeterminate. We will behave like WinIE behind the scenes and just // obey the CSS spec here in the test for matching the pseudo. HTMLInputElement* inputElement = element->toInputElement(); if (inputElement && inputElement->shouldAppearChecked() && !inputElement->shouldAppearIndeterminate()) return true; if (isHTMLOptionElement(element) && toHTMLOptionElement(element)->selected()) return true; break; } case CSSSelector::PseudoIndeterminate: return element->shouldAppearIndeterminate(); case CSSSelector::PseudoRoot: if (element == element->document()->documentElement()) return true; break; case CSSSelector::PseudoLang: { AtomicString value; #if ENABLE(VIDEO_TRACK) if (element->isWebVTTElement()) value = toWebVTTElement(element)->language(); else #endif value = element->computeInheritedLanguage(); const AtomicString& argument = selector->argument(); if (value.isEmpty() || !value.startsWith(argument, false)) break; if (value.length() != argument.length() && value[argument.length()] != '-') break; return true; } #if ENABLE(FULLSCREEN_API) case CSSSelector::PseudoFullScreen: // While a Document is in the fullscreen state, and the document's current fullscreen // element is an element in the document, the 'full-screen' pseudoclass applies to // that element. Also, an <iframe>, <object> or <embed> element whose child browsing // context's Document is in the fullscreen state has the 'full-screen' pseudoclass applied. if (element->isFrameElementBase() && element->containsFullScreenElement()) return true; if (!element->document()->webkitIsFullScreen()) return false; return element == element->document()->webkitCurrentFullScreenElement(); case CSSSelector::PseudoAnimatingFullScreenTransition: if (element != element->document()->webkitCurrentFullScreenElement()) return false; return element->document()->isAnimatingFullScreen(); case CSSSelector::PseudoFullScreenAncestor: return element->containsFullScreenElement(); case CSSSelector::PseudoFullScreenDocument: // While a Document is in the fullscreen state, the 'full-screen-document' pseudoclass applies // to all elements of that Document. if (!element->document()->webkitIsFullScreen()) return false; return true; #endif #if ENABLE(IFRAME_SEAMLESS) case CSSSelector::PseudoSeamlessDocument: // While a document is rendered in a seamless iframe, the 'seamless-document' pseudoclass applies // to all elements of that Document. return element->document()->shouldDisplaySeamlesslyWithParent(); #endif case CSSSelector::PseudoInRange: element->document()->setContainsValidityStyleRules(); return element->isInRange(); case CSSSelector::PseudoOutOfRange: element->document()->setContainsValidityStyleRules(); return element->isOutOfRange(); #if ENABLE(VIDEO_TRACK) case CSSSelector::PseudoFutureCue: return (element->isWebVTTElement() && !toWebVTTElement(element)->isPastNode()); case CSSSelector::PseudoPastCue: return (element->isWebVTTElement() && toWebVTTElement(element)->isPastNode()); #endif case CSSSelector::PseudoScope: { const Node* contextualReferenceNode = !context.scope || context.behaviorAtBoundary == CrossesBoundary ? element->document()->documentElement() : context.scope; if (element == contextualReferenceNode) return true; break; } case CSSSelector::PseudoHorizontal: case CSSSelector::PseudoVertical: case CSSSelector::PseudoDecrement: case CSSSelector::PseudoIncrement: case CSSSelector::PseudoStart: case CSSSelector::PseudoEnd: case CSSSelector::PseudoDoubleButton: case CSSSelector::PseudoSingleButton: case CSSSelector::PseudoNoButton: case CSSSelector::PseudoCornerPresent: return false; case CSSSelector::PseudoUnknown: case CSSSelector::PseudoNotParsed: default: ASSERT_NOT_REACHED(); break; } return false; } #if ENABLE(VIDEO_TRACK) else if (selector->m_match == CSSSelector::PseudoElement && selector->pseudoType() == CSSSelector::PseudoCue) { SelectorCheckingContext subContext(context); subContext.isSubSelector = true; PseudoId ignoreDynamicPseudo = NOPSEUDO; const CSSSelector* const & selector = context.selector; for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) { if (match(subContext, ignoreDynamicPseudo) == SelectorMatches) return true; } return false; } #endif // ### add the rest of the checks... return true; }
void InsertListCommand::doApply() { if (endingSelection().isNone()) return; if (!endingSelection().rootEditableElement()) return; VisiblePosition visibleEnd = endingSelection().visibleEnd(); VisiblePosition visibleStart = endingSelection().visibleStart(); // When a selection ends at the start of a paragraph, we rarely paint // the selection gap before that paragraph, because there often is no gap. // In a case like this, it's not obvious to the user that the selection // ends "inside" that paragraph, so it would be confusing if InsertUn{Ordered}List // operated on that paragraph. // FIXME: We paint the gap before some paragraphs that are indented with left // margin/padding, but not others. We should make the gap painting more consistent and // then use a left margin/padding rule here. if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); if (endingSelection().isRange() && modifyRange()) return; // FIXME: This will produce unexpected results for a selection that starts just before a // table and ends inside the first cell, selectionForParagraphIteration should probably // be renamed and deployed inside setEndingSelection(). Node* selectionNode = endingSelection().start().node(); const QualifiedName listTag = (m_type == OrderedList) ? olTag : ulTag; Node* listChildNode = enclosingListChild(selectionNode); bool switchListType = false; if (listChildNode) { // Remove the list chlild. HTMLElement* listNode = enclosingList(listChildNode); if (!listNode) listNode = fixOrphanedListChild(listChildNode); if (!listNode->hasTagName(listTag)) // listChildNode will be removed from the list and a list of type m_type will be created. switchListType = true; Node* nextListChild; Node* previousListChild; VisiblePosition start; VisiblePosition end; if (listChildNode->hasTagName(liTag)) { start = firstDeepEditingPositionForNode(listChildNode); end = lastDeepEditingPositionForNode(listChildNode); nextListChild = listChildNode->nextSibling(); previousListChild = listChildNode->previousSibling(); } else { // A paragraph is visually a list item minus a list marker. The paragraph will be moved. start = startOfParagraph(endingSelection().visibleStart()); end = endOfParagraph(endingSelection().visibleEnd()); nextListChild = enclosingListChild(end.next().deepEquivalent().node()); ASSERT(nextListChild != listChildNode); if (enclosingList(nextListChild) != listNode) nextListChild = 0; previousListChild = enclosingListChild(start.previous().deepEquivalent().node()); ASSERT(previousListChild != listChildNode); if (enclosingList(previousListChild) != listNode) previousListChild = 0; } // When removing a list, we must always create a placeholder to act as a point of insertion // for the list content being removed. RefPtr<Element> placeholder = createBreakElement(document()); RefPtr<Element> nodeToInsert = placeholder; // If the content of the list item will be moved into another list, put it in a list item // so that we don't create an orphaned list child. if (enclosingList(listNode)) { nodeToInsert = createListItemElement(document()); appendNode(placeholder, nodeToInsert); } if (nextListChild && previousListChild) { // We want to pull listChildNode out of listNode, and place it before nextListChild // and after previousListChild, so we split listNode and insert it between the two lists. // But to split listNode, we must first split ancestors of listChildNode between it and listNode, // if any exist. // FIXME: We appear to split at nextListChild as opposed to listChildNode so that when we remove // listChildNode below in moveParagraphs, previousListChild will be removed along with it if it is // unrendered. But we ought to remove nextListChild too, if it is unrendered. splitElement(listNode, splitTreeToNode(nextListChild, listNode)); insertNodeBefore(nodeToInsert, listNode); } else if (nextListChild || listChildNode->parentNode() != listNode) { // Just because listChildNode has no previousListChild doesn't mean there isn't any content // in listNode that comes before listChildNode, as listChildNode could have ancestors // between it and listNode. So, we split up to listNode before inserting the placeholder // where we're about to move listChildNode to. if (listChildNode->parentNode() != listNode) splitElement(listNode, splitTreeToNode(listChildNode, listNode).get()); insertNodeBefore(nodeToInsert, listNode); } else insertNodeAfter(nodeToInsert, listNode); VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0)); moveParagraphs(start, end, insertionPoint, true); } if (!listChildNode || switchListType || m_forceCreateList) { // Create list. VisiblePosition originalStart = endingSelection().visibleStart(); VisiblePosition start = startOfParagraph(originalStart); VisiblePosition end = endOfParagraph(endingSelection().visibleEnd()); // Check for adjoining lists. VisiblePosition previousPosition = start.previous(true); VisiblePosition nextPosition = end.next(true); RefPtr<HTMLElement> listItemElement = createListItemElement(document()); RefPtr<HTMLElement> placeholder = createBreakElement(document()); appendNode(placeholder, listItemElement); Element* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node()); Element* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node()); Node* startNode = start.deepEquivalent().node(); Node* previousCell = enclosingTableCell(previousPosition.deepEquivalent()); Node* nextCell = enclosingTableCell(nextPosition.deepEquivalent()); Node* currentCell = enclosingTableCell(start.deepEquivalent()); if (previousList && (!previousList->hasTagName(listTag) || startNode->isDescendantOf(previousList) || previousCell != currentCell)) previousList = 0; if (nextList && (!nextList->hasTagName(listTag) || startNode->isDescendantOf(nextList) || nextCell != currentCell)) nextList = 0; // Place list item into adjoining lists. if (previousList) appendNode(listItemElement, previousList); else if (nextList) insertNodeAt(listItemElement, Position(nextList, 0)); else { // Create the list. RefPtr<HTMLElement> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document()); m_listElement = listElement; appendNode(listItemElement, listElement); if (start == end && isBlock(start.deepEquivalent().node())) { // Inserting the list into an empty paragraph that isn't held open // by a br or a '\n', will invalidate start and end. Insert // a placeholder and then recompute start and end. RefPtr<Node> placeholder = insertBlockPlaceholder(start.deepEquivalent()); start = VisiblePosition(Position(placeholder.get(), 0)); end = start; } // Insert the list at a position visually equivalent to start of the // paragraph that is being moved into the list. // Try to avoid inserting it somewhere where it will be surrounded by // inline ancestors of start, since it is easier for editing to produce // clean markup when inline elements are pushed down as far as possible. Position insertionPos(start.deepEquivalent().upstream()); // Also avoid the containing list item. Node* listChild = enclosingListChild(insertionPos.node()); if (listChild && listChild->hasTagName(liTag)) insertionPos = positionInParentBeforeNode(listChild); insertNodeAt(listElement, insertionPos); // We inserted the list at the start of the content we're about to move // Update the start of content, so we don't try to move the list into itself. bug 19066 if (insertionPos == start.deepEquivalent()) start = startOfParagraph(originalStart); previousList = outermostEnclosingList(previousPosition.deepEquivalent().node(), enclosingList(listElement.get())); nextList = outermostEnclosingList(nextPosition.deepEquivalent().node(), enclosingList(listElement.get())); } moveParagraph(start, end, VisiblePosition(Position(placeholder.get(), 0)), true); if (m_listElement) { if (canMergeLists(previousList, m_listElement.get())) mergeIdenticalElements(previousList, m_listElement.get()); if (canMergeLists(m_listElement.get(), nextList)) mergeIdenticalElements(m_listElement.get(), nextList); } else if (canMergeLists(nextList, previousList)) mergeIdenticalElements(previousList, nextList); } }
void RenderPartObject::updateWidget() { String url; String serviceType; Vector<String> paramNames; Vector<String> paramValues; Frame* frame = m_view->frame(); setNeedsLayoutAndMinMaxRecalc(); if (element()->hasTagName(objectTag)) { HTMLObjectElement* o = static_cast<HTMLObjectElement*>(element()); if (!o->isComplete()) return; // Check for a child EMBED tag. HTMLEmbedElement* embed = 0; for (Node* child = o->firstChild(); child;) { if (child->hasTagName(embedTag)) { embed = static_cast<HTMLEmbedElement*>(child); break; } else if (child->hasTagName(objectTag)) child = child->nextSibling(); // Don't descend into nested OBJECT tags else child = child->traverseNextNode(o); // Otherwise descend (EMBEDs may be inside COMMENT tags) } // Use the attributes from the EMBED tag instead of the OBJECT tag including WIDTH and HEIGHT. HTMLElement *embedOrObject; if (embed) { embedOrObject = (HTMLElement *)embed; String attribute = embedOrObject->getAttribute(widthAttr); if (!attribute.isEmpty()) o->setAttribute(widthAttr, attribute); attribute = embedOrObject->getAttribute(heightAttr); if (!attribute.isEmpty()) o->setAttribute(heightAttr, attribute); url = embed->url; serviceType = embed->serviceType; } else embedOrObject = (HTMLElement *)o; // If there was no URL or type defined in EMBED, try the OBJECT tag. if (url.isEmpty()) url = o->url; if (serviceType.isEmpty()) serviceType = o->serviceType; HashSet<StringImpl*, CaseInsensitiveHash> uniqueParamNames; // Scan the PARAM children. // Get the URL and type from the params if we don't already have them. // Get the attributes from the params if there is no EMBED tag. Node *child = o->firstChild(); while (child && (url.isEmpty() || serviceType.isEmpty() || !embed)) { if (child->hasTagName(paramTag)) { HTMLParamElement* p = static_cast<HTMLParamElement*>(child); String name = p->name().lower(); if (url.isEmpty() && (name == "src" || name == "movie" || name == "code" || name == "url")) url = p->value(); if (serviceType.isEmpty() && name == "type") { serviceType = p->value(); int pos = serviceType.find(";"); if (pos != -1) serviceType = serviceType.left(pos); } if (!embed && !name.isEmpty()) { uniqueParamNames.add(p->name().impl()); paramNames.append(p->name()); paramValues.append(p->value()); } } child = child->nextSibling(); } // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM, // else our Java plugin will misinterpret it. [4004531] String codebase; if (!embed && serviceType.lower() == "application/x-java-applet") { codebase = "codebase"; uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already } // Turn the attributes of either the EMBED tag or OBJECT tag into arrays, but don't override PARAM values. NamedAttrMap* attributes = embedOrObject->attributes(); if (attributes) { for (unsigned i = 0; i < attributes->length(); ++i) { Attribute* it = attributes->attributeItem(i); const AtomicString& name = it->name().localName(); if (embed || !uniqueParamNames.contains(name.impl())) { paramNames.append(name.domString()); paramValues.append(it->value().domString()); } } } // If we still don't have a type, try to map from a specific CLASSID to a type. if (serviceType.isEmpty() && !o->classId.isEmpty()) mapClassIdToServiceType(o->classId, serviceType); // If no URL and type, abort. if (url.isEmpty() && serviceType.isEmpty()) return; if (!isURLAllowed(document(), url)) return; // Find out if we support fallback content. m_hasFallbackContent = false; for (Node *child = o->firstChild(); child && !m_hasFallbackContent; child = child->nextSibling()) { if ((!child->isTextNode() && !child->hasTagName(embedTag) && !child->hasTagName(paramTag)) || // Discount <embed> and <param> (child->isTextNode() && !static_cast<Text*>(child)->containsOnlyWhitespace())) m_hasFallbackContent = true; } bool success = frame->requestObject(this, url, AtomicString(o->name()), serviceType, paramNames, paramValues); if (!success && m_hasFallbackContent) o->renderFallbackContent(); } else if (element()->hasTagName(embedTag)) { HTMLEmbedElement *o = static_cast<HTMLEmbedElement*>(element()); url = o->url; serviceType = o->serviceType; if (url.isEmpty() && serviceType.isEmpty()) return; if (!isURLAllowed(document(), url)) return; // add all attributes set on the embed object NamedAttrMap* a = o->attributes(); if (a) { for (unsigned i = 0; i < a->length(); ++i) { Attribute* it = a->attributeItem(i); paramNames.append(it->name().localName().domString()); paramValues.append(it->value().domString()); } } frame->requestObject(this, url, o->getAttribute(nameAttr), serviceType, paramNames, paramValues); } }
// Result nodes are ordered in axis order. Node test (including merged // predicates) is applied. void Step::nodesInAxis(EvaluationContext& evaluationContext, Node* context, NodeSet& nodes) const { ASSERT(nodes.isEmpty()); switch (m_axis) { case ChildAxis: // In XPath model, attribute nodes do not have children. if (context->isAttributeNode()) return; for (Node* n = context->firstChild(); n; n = n->nextSibling()) { if (nodeMatches(evaluationContext, n, ChildAxis, nodeTest())) nodes.append(n); } return; case DescendantAxis: // In XPath model, attribute nodes do not have children. if (context->isAttributeNode()) return; for (Node* n = context->firstChild(); n; n = NodeTraversal::next(*n, context)) { if (nodeMatches(evaluationContext, n, DescendantAxis, nodeTest())) nodes.append(n); } return; case ParentAxis: if (context->isAttributeNode()) { Element* n = toAttr(context)->ownerElement(); if (nodeMatches(evaluationContext, n, ParentAxis, nodeTest())) nodes.append(n); } else { ContainerNode* n = context->parentNode(); if (n && nodeMatches(evaluationContext, n, ParentAxis, nodeTest())) nodes.append(n); } return; case AncestorAxis: { Node* n = context; if (context->isAttributeNode()) { n = toAttr(context)->ownerElement(); if (nodeMatches(evaluationContext, n, AncestorAxis, nodeTest())) nodes.append(n); } for (n = n->parentNode(); n; n = n->parentNode()) { if (nodeMatches(evaluationContext, n, AncestorAxis, nodeTest())) nodes.append(n); } nodes.markSorted(false); return; } case FollowingSiblingAxis: if (context->nodeType() == Node::ATTRIBUTE_NODE) return; for (Node* n = context->nextSibling(); n; n = n->nextSibling()) { if (nodeMatches(evaluationContext, n, FollowingSiblingAxis, nodeTest())) nodes.append(n); } return; case PrecedingSiblingAxis: if (context->nodeType() == Node::ATTRIBUTE_NODE) return; for (Node* n = context->previousSibling(); n; n = n->previousSibling()) { if (nodeMatches(evaluationContext, n, PrecedingSiblingAxis, nodeTest())) nodes.append(n); } nodes.markSorted(false); return; case FollowingAxis: if (context->isAttributeNode()) { for (Node* p = NodeTraversal::next(*toAttr(context)->ownerElement()); p; p = NodeTraversal::next(*p)) { if (nodeMatches(evaluationContext, p, FollowingAxis, nodeTest())) nodes.append(p); } } else { for (Node* p = context; !isRootDomNode(p); p = p->parentNode()) { for (Node* n = p->nextSibling(); n; n = n->nextSibling()) { if (nodeMatches(evaluationContext, n, FollowingAxis, nodeTest())) nodes.append(n); for (Node* c = n->firstChild(); c; c = NodeTraversal::next(*c, n)) { if (nodeMatches(evaluationContext, c, FollowingAxis, nodeTest())) nodes.append(c); } } } } return; case PrecedingAxis: { if (context->isAttributeNode()) context = toAttr(context)->ownerElement(); Node* n = context; while (ContainerNode* parent = n->parentNode()) { for (n = NodeTraversal::previous(*n); n != parent; n = NodeTraversal::previous(*n)) { if (nodeMatches(evaluationContext, n, PrecedingAxis, nodeTest())) nodes.append(n); } n = parent; } nodes.markSorted(false); return; } case AttributeAxis: { if (!context->isElementNode()) return; Element* contextElement = toElement(context); // Avoid lazily creating attribute nodes for attributes that we do not // need anyway. if (nodeTest().kind() == NodeTest::NameTest && nodeTest().data() != starAtom) { RefPtrWillBeRawPtr<Node> n = contextElement->getAttributeNodeNS(nodeTest().namespaceURI(), nodeTest().data()); // In XPath land, namespace nodes are not accessible on the attribute axis. if (n && n->namespaceURI() != XMLNSNames::xmlnsNamespaceURI) { // Still need to check merged predicates. if (nodeMatches(evaluationContext, n.get(), AttributeAxis, nodeTest())) nodes.append(n.release()); } return; } AttributeCollection attributes = contextElement->attributes(); AttributeCollection::iterator end = attributes.end(); for (AttributeCollection::iterator it = attributes.begin(); it != end; ++it) { RefPtrWillBeRawPtr<Attr> attr = contextElement->ensureAttr(it->name()); if (nodeMatches(evaluationContext, attr.get(), AttributeAxis, nodeTest())) nodes.append(attr.release()); } return; } case NamespaceAxis: // XPath namespace nodes are not implemented. return; case SelfAxis: if (nodeMatches(evaluationContext, context, SelfAxis, nodeTest())) nodes.append(context); return; case DescendantOrSelfAxis: if (nodeMatches(evaluationContext, context, DescendantOrSelfAxis, nodeTest())) nodes.append(context); // In XPath model, attribute nodes do not have children. if (context->isAttributeNode()) return; for (Node* n = context->firstChild(); n; n = NodeTraversal::next(*n, context)) { if (nodeMatches(evaluationContext, n, DescendantOrSelfAxis, nodeTest())) nodes.append(n); } return; case AncestorOrSelfAxis: { if (nodeMatches(evaluationContext, context, AncestorOrSelfAxis, nodeTest())) nodes.append(context); Node* n = context; if (context->isAttributeNode()) { n = toAttr(context)->ownerElement(); if (nodeMatches(evaluationContext, n, AncestorOrSelfAxis, nodeTest())) nodes.append(n); } for (n = n->parentNode(); n; n = n->parentNode()) { if (nodeMatches(evaluationContext, n, AncestorOrSelfAxis, nodeTest())) nodes.append(n); } nodes.markSorted(false); return; } } ASSERT_NOT_REACHED(); }
void InsertParagraphSeparatorCommand::doApply() { bool splitText = false; if (endingSelection().isNone()) return; Position pos = endingSelection().start(); EAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { calculateStyleBeforeInsertion(pos); deleteSelection(false, true); pos = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The rangeCompliantEquivalent conversion needs to be moved into enclosingBlock. Node* startBlock = enclosingBlock(rangeCompliantEquivalent(pos).node()); Position canonicalPos = VisiblePosition(pos).deepEquivalent(); if (!startBlock || !startBlock->parentNode() || isTableCell(startBlock) || startBlock->hasTagName(formTag) || canonicalPos.node()->renderer() && canonicalPos.node()->renderer()->isTable() || canonicalPos.node()->hasTagName(hrTag)) { applyCommandToComposite(InsertLineBreakCommand::create(document())); return; } // Use the leftmost candidate. pos = pos.upstream(); if (!pos.isCandidate()) pos = pos.downstream(); // Adjust the insertion position after the delete pos = positionAvoidingSpecialElementBoundary(pos); VisiblePosition visiblePos(pos, affinity); calculateStyleBeforeInsertion(pos); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item if (breakOutOfEmptyListItem()) return; //--------------------------------------------------------------------- // Prepare for more general cases. // FIXME: We shouldn't peel off the node here because then we lose track of // the fact that it's the node that belongs to an editing position and // not a rangeCompliantEquivalent. Node *startNode = pos.node(); bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); bool nestNewBlock = false; // Create block to be inserted. RefPtr<Node> blockToInsert; if (startBlock == startBlock->rootEditableElement()) { blockToInsert = static_pointer_cast<Node>(createDefaultParagraphElement(document())); nestNewBlock = true; } else if (shouldUseDefaultParagraphElement(startBlock)) blockToInsert = static_pointer_cast<Node>(createDefaultParagraphElement(document())); else blockToInsert = startBlock->cloneNode(false); //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, // including when the block is empty. if (isLastInBlock) { if (nestNewBlock) { if (isFirstInBlock && !lineBreakExistsAtPosition(visiblePos)) { // The block is empty. Create an empty block to // represent the paragraph that we're leaving. RefPtr<Node> extraBlock = createDefaultParagraphElement(document()); appendNode(extraBlock.get(), startBlock); appendBlockPlaceholder(extraBlock.get()); } appendNode(blockToInsert.get(), startBlock); } else insertNodeAfter(blockToInsert.get(), startBlock); appendBlockPlaceholder(blockToInsert.get()); setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM)); applyStyleAfterInsertion(startBlock); return; } //--------------------------------------------------------------------- // Handle case when position is in the first visible position in its block, and // similar case where previous position is in another, presumeably nested, block. if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) { Node *refNode; if (isFirstInBlock && !nestNewBlock) refNode = startBlock; else if (pos.node() == startBlock && nestNewBlock) { refNode = startBlock->childNode(pos.offset()); ASSERT(refNode); // must be true or we'd be in the end of block case } else refNode = pos.node(); // find ending selection position easily before inserting the paragraph pos = pos.downstream(); insertNodeBefore(blockToInsert.get(), refNode); appendBlockPlaceholder(blockToInsert.get()); setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM)); applyStyleAfterInsertion(startBlock); setEndingSelection(Selection(pos, DOWNSTREAM)); return; } //--------------------------------------------------------------------- // Handle the (more complicated) general case, // All of the content in the current block after visiblePos is // about to be wrapped in a new paragraph element. Add a br before // it if visiblePos is at the start of a paragraph so that the // content will move down a line. if (isStartOfParagraph(visiblePos)) { RefPtr<Element> br = createBreakElement(document()); insertNodeAt(br.get(), pos); pos = positionAfterNode(br.get()); } // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. pos = pos.downstream(); startNode = pos.node(); // Build up list of ancestors in between the start node and the start block. Vector<Node*> ancestors; if (startNode != startBlock) for (Node* n = startNode->parentNode(); n && n != startBlock; n = n->parentNode()) ancestors.append(n); // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but pos.downstream() does not give it Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull()) { Text* textNode = static_cast<Text*>(leadingWhitespace.node()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. if (startNode->isTextNode()) { Text *textNode = static_cast<Text *>(startNode); bool atEnd = (unsigned)pos.offset() >= textNode->length(); if (pos.offset() > 0 && !atEnd) { splitTextNode(textNode, pos.offset()); pos = Position(startNode, 0); visiblePos = VisiblePosition(pos); splitText = true; } } // Put the added block in the tree. if (nestNewBlock) appendNode(blockToInsert.get(), startBlock); else insertNodeAfter(blockToInsert.get(), startBlock); updateLayout(); // Make clones of ancestors in between the start node and the start block. RefPtr<Node> parent = blockToInsert; for (size_t i = ancestors.size(); i != 0; --i) { RefPtr<Node> child = ancestors[i - 1]->cloneNode(false); // shallow clone appendNode(child.get(), parent.get()); parent = child.release(); } // If the paragraph separator was inserted at the end of a paragraph, an empty line must be // created. All of the nodes, starting at visiblePos, are about to be added to the new paragraph // element. If the first node to be inserted won't be one that will hold an empty line open, add a br. if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtPosition(visiblePos)) appendNode(createBreakElement(document()).get(), blockToInsert.get()); // Move the start node and the siblings of the start node. if (startNode != startBlock) { Node *n = startNode; if (pos.offset() >= caretMaxOffset(startNode)) n = startNode->nextSibling(); while (n && n != blockToInsert) { Node *next = n->nextSibling(); removeNode(n); appendNode(n, parent.get()); n = next; } } // Move everything after the start node. if (!ancestors.isEmpty()) { Node* leftParent = ancestors.first(); while (leftParent && leftParent != startBlock) { parent = parent->parentNode(); Node* n = leftParent->nextSibling(); while (n && n != blockToInsert) { Node* next = n->nextSibling(); removeNode(n); appendNode(n, parent.get()); n = next; } leftParent = leftParent->parentNode(); } } // Handle whitespace that occurs after the split if (splitText) { updateLayout(); pos = Position(startNode, 0); if (!pos.isRenderedCharacter()) { // Clear out all whitespace and insert one non-breaking space ASSERT(startNode); ASSERT(startNode->isTextNode()); ASSERT(!startNode->renderer() || startNode->renderer()->style()->collapseWhiteSpace()); deleteInsignificantTextDownstream(pos); insertTextIntoNode(static_cast<Text*>(startNode), 0, nonBreakingSpaceString()); } } setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM)); applyStyleAfterInsertion(startBlock); }
PassRefPtr<SimpleFontData> CSSFontFaceSource::getFontData(const FontDescription& fontDescription) { // If the font hasn't loaded or an error occurred, then we've got nothing. if (!isValid()) return 0; if (isLocal()) { // We're local. Just return a SimpleFontData from the normal cache. // We don't want to check alternate font family names here, so pass true as the checkingAlternateName parameter. RefPtr<SimpleFontData> fontData = FontCache::fontCache()->getFontData(fontDescription, m_string, true); m_histograms.recordLocalFont(fontData); return fontData; } // See if we have a mapping in our FontData cache. AtomicString emptyFontFamily = ""; FontCacheKey key = fontDescription.cacheKey(emptyFontFamily); RefPtr<SimpleFontData>& fontData = m_fontDataTable.add(key.hash(), 0).iterator->value; if (fontData) return fontData; // No release, because fontData is a reference to a RefPtr that is held in the m_fontDataTable. // If we are still loading, then we let the system pick a font. if (isLoaded()) { if (m_font) { #if ENABLE(SVG_FONTS) if (m_hasExternalSVGFont) { // For SVG fonts parse the external SVG document, and extract the <font> element. if (!m_font->ensureSVGFontData()) return 0; if (!m_externalSVGFontElement) { String fragmentIdentifier; size_t start = m_string.find('#'); if (start != kNotFound) fragmentIdentifier = m_string.string().substring(start + 1); m_externalSVGFontElement = m_font->getSVGFontById(fragmentIdentifier); } if (!m_externalSVGFontElement) return 0; SVGFontFaceElement* fontFaceElement = 0; // Select first <font-face> child for (Node* fontChild = m_externalSVGFontElement->firstChild(); fontChild; fontChild = fontChild->nextSibling()) { if (fontChild->hasTagName(SVGNames::font_faceTag)) { fontFaceElement = toSVGFontFaceElement(fontChild); break; } } if (fontFaceElement) { if (!m_svgFontFaceElement) { // We're created using a CSS @font-face rule, that means we're not associated with a SVGFontFaceElement. // Use the imported <font-face> tag as referencing font-face element for these cases. m_svgFontFaceElement = fontFaceElement; } fontData = SimpleFontData::create( SVGFontData::create(fontFaceElement), fontDescription.effectiveFontSize(), fontDescription.isSyntheticBold(), fontDescription.isSyntheticItalic()); } } else #endif { // Create new FontPlatformData from our CGFontRef, point size and ATSFontRef. if (!m_font->ensureCustomFontData()) return 0; fontData = SimpleFontData::create( m_font->platformDataFromCustomData(fontDescription.effectiveFontSize(), fontDescription.isSyntheticBold(), fontDescription.isSyntheticItalic(), fontDescription.orientation(), fontDescription.widthVariant()), CustomFontData::create(false)); } } else { #if ENABLE(SVG_FONTS) // In-Document SVG Fonts if (m_svgFontFaceElement) { fontData = SimpleFontData::create( SVGFontData::create(m_svgFontFaceElement.get()), fontDescription.effectiveFontSize(), fontDescription.isSyntheticBold(), fontDescription.isSyntheticItalic()); } #endif } } else { // This temporary font is not retained and should not be returned. FontCachePurgePreventer fontCachePurgePreventer; SimpleFontData* temporaryFont = FontCache::fontCache()->getNonRetainedLastResortFallbackFont(fontDescription); RefPtr<CSSCustomFontData> cssFontData = CSSCustomFontData::create(true); cssFontData->setCSSFontFaceSource(this); fontData = SimpleFontData::create(temporaryFont->platformData(), cssFontData); } return fontData; // No release, because fontData is a reference to a RefPtr that is held in the m_fontDataTable. }
static inline bool hasOneChild(ContainerNode* node) { Node* firstChild = node->firstChild(); return firstChild && !firstChild->nextSibling(); }
void ContentDistributor::populate(Node* node, ContentDistribution& pool) { if (!isActiveInsertionPoint(node)) { pool.append(node); return; } InsertionPoint* insertionPoint = toInsertionPoint(node); if (insertionPoint->hasDistribution()) { for (size_t i = 0; i < insertionPoint->size(); ++i) populate(insertionPoint->at(i), pool); } else { for (Node* fallbackNode = insertionPoint->firstChild(); fallbackNode; fallbackNode = fallbackNode->nextSibling()) pool.append(fallbackNode); } }
PassRefPtr<DocumentFragment> TextTrackCue::getCueAsHTML() { RefPtr<DocumentFragment> clonedFragment; Document* document; if (!m_documentFragment) { m_hasInnerTimestamps = false; m_documentFragment = WebVTTParser::create(0, m_scriptExecutionContext)->createDocumentFragmentFromCueText(m_content); if (!m_documentFragment) return 0; for (Node *child = m_documentFragment->firstChild(); !m_hasInnerTimestamps && child; child = child->nextSibling()) { if (child->nodeName() == "timestamp") m_hasInnerTimestamps = true; } } document = static_cast<Document*>(m_scriptExecutionContext); clonedFragment = DocumentFragment::create(document); m_documentFragment->cloneChildNodes(clonedFragment.get()); return clonedFragment.release(); }
SimpleFontData* CSSFontFaceSource::getFontData(const FontDescription& fontDescription, bool syntheticBold, bool syntheticItalic, CSSFontSelector* fontSelector) { // If the font hasn't loaded or an error occurred, then we've got nothing. if (!isValid()) return 0; #if ENABLE(SVG_FONTS) if (!m_font && !m_svgFontFaceElement) { #else if (!m_font) { #endif SimpleFontData* fontData = fontCache()->getCachedFontData(fontDescription, m_string); // We're local. Just return a SimpleFontData from the normal cache. return fontData; } // See if we have a mapping in our FontData cache. unsigned hashKey = fontDescription.computedPixelSize() << 3 | (fontDescription.orientation() == Vertical ? 4 : 0) | (syntheticBold ? 2 : 0) | (syntheticItalic ? 1 : 0); if (SimpleFontData* cachedData = m_fontDataTable.get(hashKey)) return cachedData; OwnPtr<SimpleFontData> fontData; // If we are still loading, then we let the system pick a font. if (isLoaded()) { if (m_font) { #if ENABLE(SVG_FONTS) if (m_font->isSVGFont()) { // For SVG fonts parse the external SVG document, and extract the <font> element. if (!m_font->ensureSVGFontData()) return 0; if (!m_externalSVGFontElement) m_externalSVGFontElement = m_font->getSVGFontById(SVGURIReference::getTarget(m_string)); if (!m_externalSVGFontElement) return 0; SVGFontFaceElement* fontFaceElement = 0; // Select first <font-face> child for (Node* fontChild = m_externalSVGFontElement->firstChild(); fontChild; fontChild = fontChild->nextSibling()) { if (fontChild->hasTagName(SVGNames::font_faceTag)) { fontFaceElement = static_cast<SVGFontFaceElement*>(fontChild); break; } } if (fontFaceElement) { if (!m_svgFontFaceElement) { // We're created using a CSS @font-face rule, that means we're not associated with a SVGFontFaceElement. // Use the imported <font-face> tag as referencing font-face element for these cases. m_svgFontFaceElement = fontFaceElement; } fontData.set(new SimpleFontData(adoptPtr(new SVGFontData(fontFaceElement)), fontDescription.computedPixelSize(), syntheticBold, syntheticItalic)); } } else #endif { // Create new FontPlatformData from our CGFontRef, point size and ATSFontRef. if (!m_font->ensureCustomFontData()) return 0; fontData.set(new SimpleFontData(m_font->platformDataFromCustomData(fontDescription.computedPixelSize(), syntheticBold, syntheticItalic, fontDescription.orientation(), fontDescription.renderingMode()), true, false)); } } else { #if ENABLE(SVG_FONTS) // In-Document SVG Fonts if (m_svgFontFaceElement) fontData.set(new SimpleFontData(adoptPtr(new SVGFontData(m_svgFontFaceElement)), fontDescription.computedPixelSize(), syntheticBold, syntheticItalic)); #endif } } else { // Kick off the load now. if (CachedResourceLoader* cachedResourceLoader = fontSelector->cachedResourceLoader()) m_font->beginLoadIfNeeded(cachedResourceLoader); // FIXME: m_string is a URL so it makes no sense to pass it as a family name. SimpleFontData* tempData = fontCache()->getCachedFontData(fontDescription, m_string); if (!tempData) tempData = fontCache()->getLastResortFallbackFont(fontDescription); fontData.set(new SimpleFontData(tempData->platformData(), true, true)); } SimpleFontData* fontDataRawPtr = fontData.leakPtr(); m_fontDataTable.set(hashKey, fontDataRawPtr); return fontDataRawPtr; } #if ENABLE(SVG_FONTS) bool CSSFontFaceSource::isSVGFontFaceSource() const { return m_svgFontFaceElement || (m_font && m_font->isSVGFont()); } #endif }
void DistributionPool::distributeTo(InsertionPoint* insertionPoint, ElementShadow* elementShadow) { DistributedNodes distributedNodes; for (size_t i = 0; i < m_nodes.size(); ++i) { if (m_distributed[i]) continue; if (isHTMLContentElement(*insertionPoint) && !toHTMLContentElement(insertionPoint)->canSelectNode(m_nodes, i)) continue; Node* node = m_nodes[i]; distributedNodes.append(node); elementShadow->didDistributeNode(node, insertionPoint); m_distributed[i] = true; } // Distributes fallback elements if (insertionPoint->isContentInsertionPoint() && distributedNodes.isEmpty()) { for (Node* fallbackNode = insertionPoint->firstChild(); fallbackNode; fallbackNode = fallbackNode->nextSibling()) { distributedNodes.append(fallbackNode); elementShadow->didDistributeNode(fallbackNode, insertionPoint); } } insertionPoint->setDistributedNodes(distributedNodes); }
bool AXTable::isDataTable() const { if (!m_renderer) return false; // Do not consider it a data table is it has an ARIA role. if (hasARIARole()) return false; // When a section of the document is contentEditable, all tables should be // treated as data tables, otherwise users may not be able to work with rich // text editors that allow creating and editing tables. if (node() && node()->rendererIsEditable()) return true; // This employs a heuristic to determine if this table should appear. // Only "data" tables should be exposed as tables. // Unfortunately, there is no good way to determine the difference // between a "layout" table and a "data" table. RenderTable* table = toRenderTable(m_renderer); Node* tableNode = table->node(); if (!tableNode || !isHTMLTableElement(tableNode)) return false; // if there is a caption element, summary, THEAD, or TFOOT section, it's most certainly a data table HTMLTableElement* tableElement = toHTMLTableElement(tableNode); if (!tableElement->summary().isEmpty() || tableElement->tHead() || tableElement->tFoot() || tableElement->caption()) return true; // if someone used "rules" attribute than the table should appear if (!tableElement->rules().isEmpty()) return true; // if there's a colgroup or col element, it's probably a data table. for (Node* child = tableElement->firstChild(); child; child = child->nextSibling()) { if (child->hasTagName(colTag) || child->hasTagName(colgroupTag)) return true; } // go through the cell's and check for tell-tale signs of "data" table status // cells have borders, or use attributes like headers, abbr, scope or axis table->recalcSectionsIfNeeded(); RenderTableSection* firstBody = table->firstBody(); if (!firstBody) return false; int numCols = firstBody->numColumns(); int numRows = firstBody->numRows(); // If there's only one cell, it's not a good AXTable candidate. if (numRows == 1 && numCols == 1) return false; // If there are at least 20 rows, we'll call it a data table. if (numRows >= 20) return true; // Store the background color of the table to check against cell's background colors. RenderStyle* tableStyle = table->style(); if (!tableStyle) return false; Color tableBGColor = tableStyle->visitedDependentColor(CSSPropertyBackgroundColor); // check enough of the cells to find if the table matches our criteria // Criteria: // 1) must have at least one valid cell (and) // 2) at least half of cells have borders (or) // 3) at least half of cells have different bg colors than the table, and there is cell spacing unsigned validCellCount = 0; unsigned borderedCellCount = 0; unsigned backgroundDifferenceCellCount = 0; unsigned cellsWithTopBorder = 0; unsigned cellsWithBottomBorder = 0; unsigned cellsWithLeftBorder = 0; unsigned cellsWithRightBorder = 0; Color alternatingRowColors[5]; int alternatingRowColorCount = 0; int headersInFirstColumnCount = 0; for (int row = 0; row < numRows; ++row) { int headersInFirstRowCount = 0; for (int col = 0; col < numCols; ++col) { RenderTableCell* cell = firstBody->primaryCellAt(row, col); if (!cell) continue; Node* cellNode = cell->node(); if (!cellNode) continue; if (cell->width() < 1 || cell->height() < 1) continue; validCellCount++; bool isTHCell = cellNode->hasTagName(thTag); // If the first row is comprised of all <th> tags, assume it is a data table. if (!row && isTHCell) headersInFirstRowCount++; // If the first column is comprised of all <th> tags, assume it is a data table. if (!col && isTHCell) headersInFirstColumnCount++; // in this case, the developer explicitly assigned a "data" table attribute if (cellNode->hasTagName(tdTag) || cellNode->hasTagName(thTag)) { HTMLTableCellElement* cellElement = toHTMLTableCellElement(cellNode); if (!cellElement->headers().isEmpty() || !cellElement->abbr().isEmpty() || !cellElement->axis().isEmpty() || !cellElement->scope().isEmpty()) return true; } RenderStyle* renderStyle = cell->style(); if (!renderStyle) continue; // If the empty-cells style is set, we'll call it a data table. if (renderStyle->emptyCells() == HIDE) return true; // If a cell has matching bordered sides, call it a (fully) bordered cell. if ((cell->borderTop() > 0 && cell->borderBottom() > 0) || (cell->borderLeft() > 0 && cell->borderRight() > 0)) borderedCellCount++; // Also keep track of each individual border, so we can catch tables where most // cells have a bottom border, for example. if (cell->borderTop() > 0) cellsWithTopBorder++; if (cell->borderBottom() > 0) cellsWithBottomBorder++; if (cell->borderLeft() > 0) cellsWithLeftBorder++; if (cell->borderRight() > 0) cellsWithRightBorder++; // If the cell has a different color from the table and there is cell spacing, // then it is probably a data table cell (spacing and colors take the place of borders). Color cellColor = renderStyle->visitedDependentColor(CSSPropertyBackgroundColor); if (table->hBorderSpacing() > 0 && table->vBorderSpacing() > 0 && tableBGColor != cellColor && cellColor.alpha() != 1) backgroundDifferenceCellCount++; // If we've found 10 "good" cells, we don't need to keep searching. if (borderedCellCount >= 10 || backgroundDifferenceCellCount >= 10) return true; // For the first 5 rows, cache the background color so we can check if this table has zebra-striped rows. if (row < 5 && row == alternatingRowColorCount) { RenderObject* renderRow = cell->parent(); if (!renderRow || !renderRow->isBoxModelObject() || !toRenderBoxModelObject(renderRow)->isTableRow()) continue; RenderStyle* rowRenderStyle = renderRow->style(); if (!rowRenderStyle) continue; Color rowColor = rowRenderStyle->visitedDependentColor(CSSPropertyBackgroundColor); alternatingRowColors[alternatingRowColorCount] = rowColor; alternatingRowColorCount++; } } if (!row && headersInFirstRowCount == numCols && numCols > 1) return true; } if (headersInFirstColumnCount == numRows && numRows > 1) return true; // if there is less than two valid cells, it's not a data table if (validCellCount <= 1) return false; // half of the cells had borders, it's a data table unsigned neededCellCount = validCellCount / 2; if (borderedCellCount >= neededCellCount || cellsWithTopBorder >= neededCellCount || cellsWithBottomBorder >= neededCellCount || cellsWithLeftBorder >= neededCellCount || cellsWithRightBorder >= neededCellCount) return true; // half had different background colors, it's a data table if (backgroundDifferenceCellCount >= neededCellCount) return true; // Check if there is an alternating row background color indicating a zebra striped style pattern. if (alternatingRowColorCount > 2) { Color firstColor = alternatingRowColors[0]; for (int k = 1; k < alternatingRowColorCount; k++) { // If an odd row was the same color as the first row, its not alternating. if (k % 2 == 1 && alternatingRowColors[k] == firstColor) return false; // If an even row is not the same as the first row, its not alternating. if (!(k % 2) && alternatingRowColors[k] != firstColor) return false; } return true; } return false; }
// FIXME: This function should not deal with url or serviceType! void HTMLObjectElement::parametersForPlugin(Vector<String>& paramNames, Vector<String>& paramValues, String& url, String& serviceType) { HashSet<StringImpl*, CaseFoldingHash> uniqueParamNames; String urlParameter; // Scan the PARAM children and store their name/value pairs. // Get the URL and type from the params if we don't already have them. for (Node* child = firstChild(); child; child = child->nextSibling()) { if (!child->hasTagName(paramTag)) continue; HTMLParamElement* p = static_cast<HTMLParamElement*>(child); String name = p->name(); if (name.isEmpty()) continue; uniqueParamNames.add(name.impl()); paramNames.append(p->name()); paramValues.append(p->value()); // FIXME: url adjustment does not belong in this function. if (url.isEmpty() && urlParameter.isEmpty() && (equalIgnoringCase(name, "src") || equalIgnoringCase(name, "movie") || equalIgnoringCase(name, "code") || equalIgnoringCase(name, "url"))) urlParameter = stripLeadingAndTrailingHTMLSpaces(p->value()); // FIXME: serviceType calculation does not belong in this function. if (serviceType.isEmpty() && equalIgnoringCase(name, "type")) { serviceType = p->value(); size_t pos = serviceType.find(";"); if (pos != notFound) serviceType = serviceType.left(pos); } } // When OBJECT is used for an applet via Sun's Java plugin, the CODEBASE attribute in the tag // points to the Java plugin itself (an ActiveX component) while the actual applet CODEBASE is // in a PARAM tag. See <http://java.sun.com/products/plugin/1.2/docs/tags.html>. This means // we have to explicitly suppress the tag's CODEBASE attribute if there is none in a PARAM, // else our Java plugin will misinterpret it. [4004531] String codebase; if (MIMETypeRegistry::isJavaAppletMIMEType(serviceType)) { codebase = "codebase"; uniqueParamNames.add(codebase.impl()); // pretend we found it in a PARAM already } // Turn the attributes of the <object> element into arrays, but don't override <param> values. NamedNodeMap* attributes = this->attributes(true); if (attributes) { for (unsigned i = 0; i < attributes->length(); ++i) { Attribute* it = attributes->attributeItem(i); const AtomicString& name = it->name().localName(); if (!uniqueParamNames.contains(name.impl())) { paramNames.append(name.string()); paramValues.append(it->value().string()); } } } mapDataParamToSrc(¶mNames, ¶mValues); // HTML5 says that an object resource's URL is specified by the object's data // attribute, not by a param element. However, for compatibility, allow the // resource's URL to be given by a param named "src", "movie", "code" or "url" // if we know that resource points to a plug-in. if (url.isEmpty() && !urlParameter.isEmpty()) { SubframeLoader* loader = document()->frame()->loader()->subframeLoader(); if (loader->resourceWillUsePlugin(urlParameter, serviceType, shouldPreferPlugInsForImages())) url = urlParameter; } }
bool RenderSVGResourceClipper::drawContentIntoMaskImage(ClipperData* clipperData, const FloatRect& objectBoundingBox) { ASSERT(frame()); ASSERT(clipperData); ASSERT(clipperData->clipMaskImage); GraphicsContext* maskContext = clipperData->clipMaskImage->context(); ASSERT(maskContext); AffineTransform maskContentTransformation; SVGClipPathElement* clipPath = static_cast<SVGClipPathElement*>(node()); if (clipPath->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { maskContentTransformation.translate(objectBoundingBox.x(), objectBoundingBox.y()); maskContentTransformation.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); maskContext->concatCTM(maskContentTransformation); } // Switch to a paint behavior where all children of this <clipPath> will be rendered using special constraints: // - fill-opacity/stroke-opacity/opacity set to 1 // - masker/filter not applied when rendering the children // - fill is set to the initial fill paint server (solid, black) // - stroke is set to the initial stroke paint server (none) PaintBehavior oldBehavior = frame()->view()->paintBehavior(); frame()->view()->setPaintBehavior(oldBehavior | PaintBehaviorRenderingSVGMask); // Draw all clipPath children into a global mask. for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { RenderObject* renderer = childNode->renderer(); if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyled() || !renderer) continue; if (renderer->needsLayout()) { frame()->view()->setPaintBehavior(oldBehavior); return false; } RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; WindRule newClipRule = style->svgStyle()->clipRule(); bool isUseElement = renderer->isSVGShadowTreeRootContainer(); if (isUseElement) { SVGUseElement* useElement = static_cast<SVGUseElement*>(childNode); renderer = useElement->rendererClipChild(); if (!renderer) continue; if (!useElement->hasAttribute(SVGNames::clip_ruleAttr)) newClipRule = renderer->style()->svgStyle()->clipRule(); } // Only shapes, paths and texts are allowed for clipping. if (!renderer->isSVGShape() && !renderer->isSVGText()) continue; maskContext->setFillRule(newClipRule); // In the case of a <use> element, we obtained its renderere above, to retrieve its clipRule. // We have to pass the <use> renderer itself to renderSubtreeToImageBuffer() to apply it's x/y/transform/etc. values when rendering. // So if isUseElement is true, refetch the childNode->renderer(), as renderer got overriden above. SVGImageBufferTools::renderSubtreeToImageBuffer(clipperData->clipMaskImage.get(), isUseElement ? childNode->renderer() : renderer, maskContentTransformation); } frame()->view()->setPaintBehavior(oldBehavior); return true; }
static void checkForSiblingStyleChanges(Element* e, RenderStyle* style, bool finishedParsingCallback, Node* beforeChange, Node* afterChange, int childCountDelta) { if (!style || (e->changed() && style->childrenAffectedByPositionalRules())) return; // :first-child. In the parser callback case, we don't have to check anything, since we were right the first time. // In the DOM case, we only need to do something if |afterChange| is not 0. // |afterChange| is 0 in the parser case, so it works out that we'll skip this block. if (style->childrenAffectedByFirstChildRules() && afterChange) { // Find our new first child. Node* newFirstChild = 0; for (newFirstChild = e->firstChild(); newFirstChild && !newFirstChild->isElementNode(); newFirstChild = newFirstChild->nextSibling()) {}; // Find the first element node following |afterChange| Node* firstElementAfterInsertion = 0; for (firstElementAfterInsertion = afterChange; firstElementAfterInsertion && !firstElementAfterInsertion->isElementNode(); firstElementAfterInsertion = firstElementAfterInsertion->nextSibling()) {}; // This is the insert/append case. if (newFirstChild != firstElementAfterInsertion && firstElementAfterInsertion && firstElementAfterInsertion->attached() && firstElementAfterInsertion->renderStyle() && firstElementAfterInsertion->renderStyle()->firstChildState()) firstElementAfterInsertion->setChanged(); // We also have to handle node removal. if (childCountDelta < 0 && newFirstChild == firstElementAfterInsertion && newFirstChild && newFirstChild->renderStyle() && !newFirstChild->renderStyle()->firstChildState()) newFirstChild->setChanged(); } // :last-child. In the parser callback case, we don't have to check anything, since we were right the first time. // In the DOM case, we only need to do something if |afterChange| is not 0. if (style->childrenAffectedByLastChildRules() && beforeChange) { // Find our new last child. Node* newLastChild = 0; for (newLastChild = e->lastChild(); newLastChild && !newLastChild->isElementNode(); newLastChild = newLastChild->previousSibling()) {}; // Find the last element node going backwards from |beforeChange| Node* lastElementBeforeInsertion = 0; for (lastElementBeforeInsertion = beforeChange; lastElementBeforeInsertion && !lastElementBeforeInsertion->isElementNode(); lastElementBeforeInsertion = lastElementBeforeInsertion->previousSibling()) {}; if (newLastChild != lastElementBeforeInsertion && lastElementBeforeInsertion && lastElementBeforeInsertion->attached() && lastElementBeforeInsertion->renderStyle() && lastElementBeforeInsertion->renderStyle()->lastChildState()) lastElementBeforeInsertion->setChanged(); // We also have to handle node removal. The parser callback case is similar to node removal as well in that we need to change the last child // to match now. if ((childCountDelta < 0 || finishedParsingCallback) && newLastChild == lastElementBeforeInsertion && newLastChild && newLastChild->renderStyle() && !newLastChild->renderStyle()->lastChildState()) newLastChild->setChanged(); } // The + selector. We need to invalidate the first element following the insertion point. It is the only possible element // that could be affected by this DOM change. if (style->childrenAffectedByDirectAdjacentRules() && afterChange) { Node* firstElementAfterInsertion = 0; for (firstElementAfterInsertion = afterChange; firstElementAfterInsertion && !firstElementAfterInsertion->isElementNode(); firstElementAfterInsertion = firstElementAfterInsertion->nextSibling()) {}; if (firstElementAfterInsertion && firstElementAfterInsertion->attached()) firstElementAfterInsertion->setChanged(); } // Forward positional selectors include the ~ selector, nth-child, nth-of-type, first-of-type and only-of-type. // Backward positional selectors include nth-last-child, nth-last-of-type, last-of-type and only-of-type. // We have to invalidate everything following the insertion point in the forward case, and everything before the insertion point in the // backward case. // |afterChange| is 0 in the parser callback case, so we won't do any work for the forward case if we don't have to. // For performance reasons we just mark the parent node as changed, since we don't want to make childrenChanged O(n^2) by crawling all our kids // here. recalcStyle will then force a walk of the children when it sees that this has happened. if ((style->childrenAffectedByForwardPositionalRules() && afterChange) || (style->childrenAffectedByBackwardPositionalRules() && beforeChange)) e->setChanged(); // :empty selector. if (style->affectedByEmpty() && (!style->emptyState() || e->hasChildNodes())) e->setChanged(); }