void RenderView::invalidatePaintForSelection() const { HashSet<RenderBlock*> processedBlocks; // For querying RenderLayer::compositingState() // FIXME: this may be wrong. crbug.com/407416 DisableCompositingQueryAsserts disabler; RenderObject* end = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); for (RenderObject* o = m_selectionStart; o && o != end; o = o->nextInPreOrder()) { if (!o->canBeSelectionLeaf() && o != m_selectionStart && o != m_selectionEnd) continue; if (o->selectionState() == SelectionNone) continue; RenderSelectionInfo(o, true).invalidatePaint(); // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. for (RenderBlock* block = o->containingBlock(); block && !block->isRenderView(); block = block->containingBlock()) { if (!processedBlocks.add(block).isNewEntry) break; RenderSelectionInfo(block, true).invalidatePaint(); } } }
IntRect RenderView::selectionBounds() const { typedef WillBeHeapHashMap<RawPtrWillBeMember<RenderObject>, OwnPtrWillBeMember<RenderSelectionInfo> > SelectionMap; SelectionMap selectedObjects; RenderObject* os = m_selectionStart; RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); while (os && os != stop) { if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. selectedObjects.set(os, adoptPtrWillBeNoop(new RenderSelectionInfo(os))); RenderBlock* cb = os->containingBlock(); while (cb && !cb->isRenderView()) { OwnPtrWillBeMember<RenderSelectionInfo>& blockInfo = selectedObjects.add(cb, nullptr).storedValue->value; if (blockInfo) break; blockInfo = adoptPtrWillBeNoop(new RenderSelectionInfo(cb)); cb = cb->containingBlock(); } } os = os->nextInPreOrder(); } // Now create a single bounding box rect that encloses the whole selection. LayoutRect selRect; SelectionMap::iterator end = selectedObjects.end(); for (SelectionMap::iterator i = selectedObjects.begin(); i != end; ++i) selRect.unite(i->value->absoluteSelectionRect()); return pixelSnappedIntRect(selRect); }
void findGoodTouchTargets(const IntRect& touchBox, LocalFrame* mainFrame, Vector<IntRect>& goodTargets, WillBeHeapVector<RawPtrWillBeMember<Node> >& highlightNodes) { goodTargets.clear(); int touchPointPadding = ceil(std::max(touchBox.width(), touchBox.height()) * 0.5); IntPoint touchPoint = touchBox.center(); IntPoint contentsPoint = mainFrame->view()->windowToContents(touchPoint); HitTestResult result = mainFrame->eventHandler().hitTestResultAtPoint(contentsPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent, IntSize(touchPointPadding, touchPointPadding)); const WillBeHeapListHashSet<RefPtrWillBeMember<Node> >& hitResults = result.rectBasedTestResult(); // Blacklist nodes that are container of disambiguated nodes. // It is not uncommon to have a clickable <div> that contains other clickable objects. // This heuristic avoids excessive disambiguation in that case. WillBeHeapHashSet<RawPtrWillBeMember<Node> > blackList; for (WillBeHeapListHashSet<RefPtrWillBeMember<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { // Ignore any Nodes that can't be clicked on. RenderObject* renderer = it->get()->renderer(); if (!renderer || !it->get()->willRespondToMouseClickEvents()) continue; // Blacklist all of the Node's containers. for (RenderBlock* container = renderer->containingBlock(); container; container = container->containingBlock()) { Node* containerNode = container->node(); if (!containerNode) continue; if (!blackList.add(containerNode).isNewEntry) break; } } WillBeHeapHashMap<RawPtrWillBeMember<Node>, TouchTargetData> touchTargets; float bestScore = 0; for (WillBeHeapListHashSet<RefPtrWillBeMember<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { for (Node* node = it->get(); node; node = node->parentNode()) { if (blackList.contains(node)) continue; if (node->isDocumentNode() || isHTMLHtmlElement(*node) || isHTMLBodyElement(*node)) break; if (node->willRespondToMouseClickEvents()) { TouchTargetData& targetData = touchTargets.add(node, TouchTargetData()).storedValue->value; targetData.windowBoundingBox = boundingBoxForEventNodes(node); targetData.score = scoreTouchTarget(touchPoint, touchPointPadding, targetData.windowBoundingBox); bestScore = std::max(bestScore, targetData.score); break; } } } for (WillBeHeapHashMap<RawPtrWillBeMember<Node>, TouchTargetData>::iterator it = touchTargets.begin(); it != touchTargets.end(); ++it) { // Currently the scoring function uses the overlap area with the fat point as the score. // We ignore the candidates that has less than 1/2 overlap (we consider not really ambiguous enough) than the best candidate to avoid excessive popups. if (it->value.score < bestScore * 0.5) continue; goodTargets.append(it->value.windowBoundingBox); highlightNodes.append(it->key); } }
RenderBlock* CaretBase::caretRenderer(Node* node) { if (!node) return 0; RenderObject* renderer = node->renderer(); if (!renderer) return 0; // if caretNode is a block and caret is inside it then caret should be painted by that block bool paintedByBlock = renderer->isRenderBlock() && caretRendersInsideNode(node); return paintedByBlock ? toRenderBlock(renderer) : renderer->containingBlock(); }
VisiblePosition RenderInline::positionForCoordinates(int x, int y) { // Translate the coords from the pre-anonymous block to the post-anonymous block. RenderBlock* cb = containingBlock(); int parentBlockX = cb->xPos() + x; int parentBlockY = cb->yPos() + y; for (RenderObject* c = continuation(); c; c = c->continuation()) { RenderObject* contBlock = c; if (c->isInline()) contBlock = c->containingBlock(); if (c->isInline() || c->firstChild()) return c->positionForCoordinates(parentBlockX - contBlock->xPos(), parentBlockY - contBlock->yPos()); } return RenderFlow::positionForCoordinates(x, y); }
int VisiblePosition::lineDirectionPointForBlockDirectionNavigation() const { RenderObject* renderer; LayoutRect localRect = localCaretRect(renderer); if (localRect.isEmpty() || !renderer) return 0; // This ignores transforms on purpose, for now. Vertical navigation is done // without consulting transforms, so that 'up' in transformed text is 'up' // relative to the text, not absolute 'up'. FloatPoint caretPoint = renderer->localToAbsolute(localRect.location()); RenderObject* containingBlock = renderer->containingBlock(); if (!containingBlock) containingBlock = renderer; // Just use ourselves to determine the writing mode if we have no containing block. return containingBlock->isHorizontalWritingMode() ? caretPoint.x() : caretPoint.y(); }
QWebHitTestResultPrivate::QWebHitTestResultPrivate(const WebCore::HitTestResult &hitTest) : isContentEditable(false) , isContentSelected(false) , isScrollBar(false) { if (!hitTest.innerNode()) return; pos = hitTest.point(); boundingRect = hitTest.boundingBox(); title = hitTest.title(); linkText = hitTest.textContent(); linkUrl = hitTest.absoluteLinkURL(); linkTitle = hitTest.titleDisplayString(); alternateText = hitTest.altDisplayString(); imageUrl = hitTest.absoluteImageURL(); innerNode = hitTest.innerNode(); innerNonSharedNode = hitTest.innerNonSharedNode(); WebCore::Image *img = hitTest.image(); if (img) { QPixmap *pix = img->nativeImageForCurrentFrame(); if (pix) pixmap = *pix; } WebCore::Frame *wframe = hitTest.targetFrame(); if (wframe) linkTargetFrame = QWebFramePrivate::kit(wframe); isContentEditable = hitTest.isContentEditable(); isContentSelected = hitTest.isSelected(); isScrollBar = hitTest.scrollbar(); if (innerNonSharedNode && innerNonSharedNode->document() && innerNonSharedNode->document()->frame()) frame = QWebFramePrivate::kit(innerNonSharedNode->document()->frame()); if (Node *block = WebCore::enclosingBlock(innerNode.get())) { RenderObject *renderBlock = block->renderer(); while (renderBlock && renderBlock->isListItem()) renderBlock = renderBlock->containingBlock(); if (renderBlock) enclosingBlock = renderBlock->absoluteClippedOverflowRect(); } }
IntRect RenderView::selectionBounds(bool clipToVisibleContent) const { document()->updateStyleIfNeeded(); typedef HashMap<RenderObject*, RenderSelectionInfo*> SelectionMap; SelectionMap selectedObjects; RenderObject* os = m_selectionStart; RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); while (os && os != stop) { if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. selectedObjects.set(os, new RenderSelectionInfo(os, clipToVisibleContent)); RenderBlock* cb = os->containingBlock(); while (cb && !cb->isRenderView()) { RenderSelectionInfo* blockInfo = selectedObjects.get(cb); if (blockInfo) break; selectedObjects.set(cb, new RenderSelectionInfo(cb, clipToVisibleContent)); cb = cb->containingBlock(); } } os = os->nextInPreOrder(); } // Now create a single bounding box rect that encloses the whole selection. IntRect selRect; SelectionMap::iterator end = selectedObjects.end(); for (SelectionMap::iterator i = selectedObjects.begin(); i != end; ++i) { RenderSelectionInfo* info = i->second; // RenderSelectionInfo::rect() is in the coordinates of the repaintContainer, so map to page coordinates. IntRect currRect = info->rect(); if (RenderBoxModelObject* repaintContainer = info->repaintContainer()) { FloatQuad absQuad = repaintContainer->localToAbsoluteQuad(FloatRect(currRect)); currRect = absQuad.enclosingBoundingBox(); } selRect.unite(currRect); delete info; } return selRect; }
void RenderView::invalidatePaintForSelection() const { HashSet<RenderBlock*> processedBlocks; RenderObject* end = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); for (RenderObject* o = m_selectionStart; o && o != end; o = o->nextInPreOrder()) { if (!o->canBeSelectionLeaf() && o != m_selectionStart && o != m_selectionEnd) continue; if (o->selectionState() == SelectionNone) continue; o->setShouldInvalidateSelection(); // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. for (RenderBlock* block = o->containingBlock(); block && !block->isRenderView(); block = block->containingBlock()) { if (!processedBlocks.add(block).isNewEntry) break; block->setShouldInvalidateSelection(); } } }
IntRect RenderView::selectionBounds(bool clipToVisibleContent) const { typedef HashMap<RawPtr<RenderObject>, OwnPtr<RenderSelectionInfo> > SelectionMap; SelectionMap selectedObjects; RenderObject* os = m_selectionStart; RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); while (os && os != stop) { if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. selectedObjects.set(os, adoptPtr(new RenderSelectionInfo(os, clipToVisibleContent))); RenderBlock* cb = os->containingBlock(); while (cb && !cb->isRenderView()) { OwnPtr<RenderSelectionInfo>& blockInfo = selectedObjects.add(cb, nullptr).storedValue->value; if (blockInfo) break; blockInfo = adoptPtr(new RenderSelectionInfo(cb, clipToVisibleContent)); cb = cb->containingBlock(); } } os = os->nextInPreOrder(); } // Now create a single bounding box rect that encloses the whole selection. LayoutRect selRect; SelectionMap::iterator end = selectedObjects.end(); for (SelectionMap::iterator i = selectedObjects.begin(); i != end; ++i) { RenderSelectionInfo* info = i->value.get(); // RenderSelectionInfo::rect() is in the coordinates of the paintInvalidationContainer, so map to page coordinates. LayoutRect currRect = info->rect(); if (const RenderLayerModelObject* paintInvalidationContainer = info->paintInvalidationContainer()) { FloatQuad absQuad = paintInvalidationContainer->localToAbsoluteQuad(FloatRect(currRect)); currRect = absQuad.enclosingBoundingBox(); } selRect.unite(currRect); } return pixelSnappedIntRect(selRect); }
void RenderView::repaintSelection() const { document().updateStyleIfNeeded(); HashSet<RenderBlock*> processedBlocks; RenderObject* end = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); for (RenderObject* o = m_selectionStart; o && o != end; o = o->nextInPreOrder()) { if (!o->canBeSelectionLeaf() && o != m_selectionStart && o != m_selectionEnd) continue; if (o->selectionState() == SelectionNone) continue; RenderSelectionInfo(o, true).repaint(); // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. for (RenderBlock* block = o->containingBlock(); block && !block->isRenderView(); block = block->containingBlock()) { if (!processedBlocks.add(block).isNewEntry) break; RenderSelectionInfo(block, true).repaint(); } } }
IntRect RenderView::selectionBounds(bool clipToVisibleContent) const { document()->updateRendering(); typedef HashMap<RenderObject*, SelectionInfo*> SelectionMap; SelectionMap selectedObjects; RenderObject* os = m_selectionStart; RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); while (os && os != stop) { if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. selectedObjects.set(os, new SelectionInfo(os, clipToVisibleContent)); RenderBlock* cb = os->containingBlock(); while (cb && !cb->isRenderView()) { SelectionInfo* blockInfo = selectedObjects.get(cb); if (blockInfo) break; selectedObjects.set(cb, new SelectionInfo(cb, clipToVisibleContent)); cb = cb->containingBlock(); } } os = os->nextInPreOrder(); } // Now create a single bounding box rect that encloses the whole selection. IntRect selRect; SelectionMap::iterator end = selectedObjects.end(); for (SelectionMap::iterator i = selectedObjects.begin(); i != end; ++i) { SelectionInfo* info = i->second; selRect.unite(info->rect()); delete info; } return selRect; }
VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) { Position p = visiblePosition.deepEquivalent(); Node *node = p.node(); Node* highestRoot = highestEditableRoot(p); if (!node) return VisiblePosition(); node->document()->updateLayoutIgnorePendingStylesheets(); RenderObject *renderer = node->renderer(); if (!renderer) return VisiblePosition(); RenderBlock *containingBlock = 0; RootInlineBox *root = 0; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); if (box) { root = box->root()->nextRootBox(); if (root) containingBlock = renderer->containingBlock(); } if (!root) { // This containing editable block does not have a next line. // Need to move forward to next containing editable block in this root editable // block and find the first root line box in that block. Node* startBlock = enclosingNodeWithNonInlineRenderer(node); Node* n = nextLeafWithSameEditability(node, p.deprecatedEditingOffset()); while (n && startBlock == enclosingNodeWithNonInlineRenderer(n)) n = nextLeafWithSameEditability(n); while (n) { if (highestEditableRoot(Position(n, 0)) != highestRoot) break; Position pos(n, caretMinOffset(n)); if (pos.isCandidate()) { ASSERT(n->renderer()); pos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset); if (box) { // next root line box found root = box->root(); containingBlock = n->renderer()->containingBlock(); break; } return VisiblePosition(pos, DOWNSTREAM); } n = nextLeafWithSameEditability(n); } } if (root) { // FIXME: Can be wrong for multi-column layout and with transforms. FloatPoint absPos = containingBlock->localToAbsolute(FloatPoint()); if (containingBlock->hasOverflowClip()) absPos -= containingBlock->layer()->scrolledContentOffset(); RenderObject* renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (node && editingIgnoresContent(node)) return Position(node->parent(), node->nodeIndex()); return renderer->positionForPoint(IntPoint(x - absPos.x(), root->lineTop())); } // Could not find a next line. This means we must already be on the last line. // Move to the end of the content in this block, which effectively moves us // to the end of the line we're on. Element* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement(); return VisiblePosition(rootElement, rootElement ? rootElement->childNodeCount() : 0, DOWNSTREAM); }
void RenderNamedFlowThread::getRanges(Vector<RefPtr<Range> >& rangeObjects, const RenderRegion* region) const { LayoutUnit logicalTopForRegion; LayoutUnit logicalBottomForRegion; // extend the first region top to contain everything up to its logical height if (region->isFirstRegion()) logicalTopForRegion = LayoutUnit::min(); else logicalTopForRegion = region->logicalTopForFlowThreadContent(); // extend the last region to contain everything above its y() if (region->isLastRegion()) logicalBottomForRegion = LayoutUnit::max(); else logicalBottomForRegion = region->logicalBottomForFlowThreadContent(); Vector<Node*> nodes; // eliminate the contentNodes that are descendants of other contentNodes for (NamedFlowContentNodes::const_iterator it = contentNodes().begin(); it != contentNodes().end(); ++it) { Node* node = *it; if (!isContainedInNodes(nodes, node)) nodes.append(node); } for (size_t i = 0; i < nodes.size(); i++) { Node* contentNode = nodes.at(i); if (!contentNode->renderer()) continue; RefPtr<Range> range = Range::create(contentNode->document()); bool foundStartPosition = false; bool startsAboveRegion = true; bool endsBelowRegion = true; bool skipOverOutsideNodes = false; Node* lastEndNode = 0; for (Node* node = contentNode; node; node = nextNodeInsideContentNode(node, contentNode)) { RenderObject* renderer = node->renderer(); if (!renderer) continue; LayoutRect boundingBox; if (renderer->isRenderInline()) boundingBox = toRenderInline(renderer)->linesBoundingBox(); else if (renderer->isText()) boundingBox = toRenderText(renderer)->linesBoundingBox(); else { boundingBox = toRenderBox(renderer)->frameRect(); if (toRenderBox(renderer)->isRelPositioned()) boundingBox.move(toRenderBox(renderer)->relativePositionLogicalOffset()); } LayoutUnit offsetTop = renderer->containingBlock()->offsetFromLogicalTopOfFirstPage(); const LayoutPoint logicalOffsetFromTop(isHorizontalWritingMode() ? LayoutUnit() : offsetTop, isHorizontalWritingMode() ? offsetTop : LayoutUnit()); boundingBox.moveBy(logicalOffsetFromTop); LayoutUnit logicalTopForRenderer = region->logicalTopOfFlowThreadContentRect(boundingBox); LayoutUnit logicalBottomForRenderer = region->logicalBottomOfFlowThreadContentRect(boundingBox); // if the bounding box of the current element doesn't intersect the region box // close the current range only if the start element began inside the region, // otherwise just move the start position after this node and keep skipping them until we found a proper start position. if (!boxIntersectsRegion(logicalTopForRenderer, logicalBottomForRenderer, logicalTopForRegion, logicalBottomForRegion)) { if (foundStartPosition) { if (!startsAboveRegion) { if (range->intersectsNode(node, IGNORE_EXCEPTION)) range->setEndBefore(node, IGNORE_EXCEPTION); rangeObjects.append(range->cloneRange(IGNORE_EXCEPTION)); range = Range::create(contentNode->document()); startsAboveRegion = true; } else skipOverOutsideNodes = true; } if (skipOverOutsideNodes) range->setStartAfter(node, IGNORE_EXCEPTION); foundStartPosition = false; continue; } // start position if (logicalTopForRenderer < logicalTopForRegion && startsAboveRegion) { if (renderer->isText()) { // Text crosses region top // for Text elements, just find the last textbox that is contained inside the region and use its start() offset as start position RenderText* textRenderer = toRenderText(renderer); for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (offsetTop + box->logicalBottom() < logicalTopForRegion) continue; range->setStart(Position(toText(node), box->start())); startsAboveRegion = false; break; } } else { // node crosses region top // for all elements, except Text, just set the start position to be before their children startsAboveRegion = true; range->setStart(Position(node, Position::PositionIsBeforeChildren)); } } else { // node starts inside region // for elements that start inside the region, set the start position to be before them. If we found one, we will just skip the others until // the range is closed. if (startsAboveRegion) { startsAboveRegion = false; range->setStartBefore(node, IGNORE_EXCEPTION); } } skipOverOutsideNodes = false; foundStartPosition = true; // end position if (logicalBottomForRegion < logicalBottomForRenderer && (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode)))) { // for Text elements, just find just find the last textbox that is contained inside the region and use its start()+len() offset as end position if (renderer->isText()) { // Text crosses region bottom RenderText* textRenderer = toRenderText(renderer); InlineTextBox* lastBox = 0; for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if ((offsetTop + box->logicalTop()) < logicalBottomForRegion) { lastBox = box; continue; } ASSERT(lastBox); if (lastBox) range->setEnd(Position(toText(node), lastBox->start() + lastBox->len())); break; } endsBelowRegion = false; lastEndNode = node; } else { // node crosses region bottom // for all elements, except Text, just set the start position to be after their children range->setEnd(Position(node, Position::PositionIsAfterChildren)); endsBelowRegion = true; lastEndNode = node; } } else { // node ends inside region // for elements that ends inside the region, set the end position to be after them // allow this end position to be changed only by other elements that are not descendants of the current end node if (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode))) { range->setEndAfter(node, IGNORE_EXCEPTION); endsBelowRegion = false; lastEndNode = node; } } } if (foundStartPosition || skipOverOutsideNodes) rangeObjects.append(range); } }
void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* end, int endPos, SelectionRepaintMode blockRepaintMode) { // Make sure both our start and end objects are defined. // Check www.msnbc.com and try clicking around to find the case where this happened. if ((start && !end) || (end && !start)) return; // Just return if the selection hasn't changed. if (m_selectionStart == start && m_selectionStartPos == startPos && m_selectionEnd == end && m_selectionEndPos == endPos) return; // Record the old selected objects. These will be used later // when we compare against the new selected objects. int oldStartPos = m_selectionStartPos; int oldEndPos = m_selectionEndPos; // Objects each have a single selection rect to examine. typedef HashMap<RenderObject*, OwnPtr<RenderSelectionInfo> > SelectedObjectMap; SelectedObjectMap oldSelectedObjects; SelectedObjectMap newSelectedObjects; // Blocks contain selected objects and fill gaps between them, either on the left, right, or in between lines and blocks. // In order to get the repaint rect right, we have to examine left, middle, and right rects individually, since otherwise // the union of those rects might remain the same even when changes have occurred. typedef HashMap<RenderBlock*, OwnPtr<RenderBlockSelectionInfo> > SelectedBlockMap; SelectedBlockMap oldSelectedBlocks; SelectedBlockMap newSelectedBlocks; RenderObject* os = m_selectionStart; RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); while (os && os != stop) { if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. oldSelectedObjects.set(os, adoptPtr(new RenderSelectionInfo(os, true))); if (blockRepaintMode == RepaintNewXOROld) { RenderBlock* cb = os->containingBlock(); while (cb && !cb->isRenderView()) { OwnPtr<RenderBlockSelectionInfo>& blockInfo = oldSelectedBlocks.add(cb, nullptr).iterator->second; if (blockInfo) break; blockInfo = adoptPtr(new RenderBlockSelectionInfo(cb)); cb = cb->containingBlock(); } } } os = os->nextInPreOrder(); } // Now clear the selection. SelectedObjectMap::iterator oldObjectsEnd = oldSelectedObjects.end(); for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) i->first->setSelectionStateIfNeeded(SelectionNone); // set selection start and end m_selectionStart = start; m_selectionStartPos = startPos; m_selectionEnd = end; m_selectionEndPos = endPos; // Update the selection status of all objects between m_selectionStart and m_selectionEnd if (start && start == end) start->setSelectionStateIfNeeded(SelectionBoth); else { if (start) start->setSelectionStateIfNeeded(SelectionStart); if (end) end->setSelectionStateIfNeeded(SelectionEnd); } RenderObject* o = start; stop = rendererAfterPosition(end, endPos); while (o && o != stop) { if (o != start && o != end && o->canBeSelectionLeaf()) o->setSelectionStateIfNeeded(SelectionInside); o = o->nextInPreOrder(); } if (blockRepaintMode != RepaintNothing) m_layer->clearBlockSelectionGapsBounds(); // Now that the selection state has been updated for the new objects, walk them again and // put them in the new objects list. o = start; while (o && o != stop) { if ((o->canBeSelectionLeaf() || o == start || o == end) && o->selectionState() != SelectionNone) { newSelectedObjects.set(o, adoptPtr(new RenderSelectionInfo(o, true))); RenderBlock* cb = o->containingBlock(); while (cb && !cb->isRenderView()) { OwnPtr<RenderBlockSelectionInfo>& blockInfo = newSelectedBlocks.add(cb, nullptr).iterator->second; if (blockInfo) break; blockInfo = adoptPtr(new RenderBlockSelectionInfo(cb)); cb = cb->containingBlock(); } } o = o->nextInPreOrder(); } if (!m_frameView || blockRepaintMode == RepaintNothing) return; m_frameView->beginDeferredRepaints(); // Have any of the old selected objects changed compared to the new selection? for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) { RenderObject* obj = i->first; RenderSelectionInfo* newInfo = newSelectedObjects.get(obj); RenderSelectionInfo* oldInfo = i->second.get(); if (!newInfo || oldInfo->rect() != newInfo->rect() || oldInfo->state() != newInfo->state() || (m_selectionStart == obj && oldStartPos != m_selectionStartPos) || (m_selectionEnd == obj && oldEndPos != m_selectionEndPos)) { oldInfo->repaint(); if (newInfo) { newInfo->repaint(); newSelectedObjects.remove(obj); } } } // Any new objects that remain were not found in the old objects dict, and so they need to be updated. SelectedObjectMap::iterator newObjectsEnd = newSelectedObjects.end(); for (SelectedObjectMap::iterator i = newSelectedObjects.begin(); i != newObjectsEnd; ++i) i->second->repaint(); // Have any of the old blocks changed? SelectedBlockMap::iterator oldBlocksEnd = oldSelectedBlocks.end(); for (SelectedBlockMap::iterator i = oldSelectedBlocks.begin(); i != oldBlocksEnd; ++i) { RenderBlock* block = i->first; RenderBlockSelectionInfo* newInfo = newSelectedBlocks.get(block); RenderBlockSelectionInfo* oldInfo = i->second.get(); if (!newInfo || oldInfo->rects() != newInfo->rects() || oldInfo->state() != newInfo->state()) { oldInfo->repaint(); if (newInfo) { newInfo->repaint(); newSelectedBlocks.remove(block); } } } // Any new blocks that remain were not found in the old blocks dict, and so they need to be updated. SelectedBlockMap::iterator newBlocksEnd = newSelectedBlocks.end(); for (SelectedBlockMap::iterator i = newSelectedBlocks.begin(); i != newBlocksEnd; ++i) i->second->repaint(); m_frameView->endDeferredRepaints(); }
static void writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior) { ts << o.renderName(); if (behavior & RenderAsTextShowAddresses) ts << " " << static_cast<const void*>(&o); if (o.style() && o.style()->zIndex()) ts << " zI: " << o.style()->zIndex(); if (o.node()) { String tagName = getTagName(o.node()); if (!tagName.isEmpty()) { ts << " {" << tagName << "}"; // flag empty or unstyled AppleStyleSpan because we never // want to leave them in the DOM if (isEmptyOrUnstyledAppleStyleSpan(o.node())) ts << " *empty or unstyled AppleStyleSpan*"; } } bool adjustForTableCells = o.containingBlock()->isTableCell(); IntRect r; if (o.isText()) { // FIXME: Would be better to dump the bounding box x and y rather than the first run's x and y, but that would involve updating // many test results. const RenderText& text = *toRenderText(&o); IntRect linesBox = text.linesBoundingBox(); r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height()); if (adjustForTableCells && !text.firstTextBox()) adjustForTableCells = false; } else if (o.isRenderInline()) { // FIXME: Would be better not to just dump 0, 0 as the x and y here. const RenderInline& inlineFlow = *toRenderInline(&o); r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height()); adjustForTableCells = false; } else if (o.isTableCell()) { // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are // captured by the results. const RenderTableCell& cell = *toRenderTableCell(&o); r = IntRect(cell.x(), cell.y() + cell.intrinsicPaddingTop(), cell.width(), cell.height() - cell.intrinsicPaddingTop() - cell.intrinsicPaddingBottom()); } else if (o.isBox()) r = toRenderBox(&o)->frameRect(); // FIXME: Temporary in order to ensure compatibility with existing layout test results. if (adjustForTableCells) r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingTop()); ts << " " << r; if (!(o.isText() && !o.isBR())) { if (o.isFileUploadControl()) { ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue()); } if (o.parent() && (o.parent()->style()->color() != o.style()->color())) ts << " [color=" << o.style()->color().name() << "]"; if (o.parent() && (o.parent()->style()->backgroundColor() != o.style()->backgroundColor()) && o.style()->backgroundColor().isValid() && o.style()->backgroundColor().rgb()) // Do not dump invalid or transparent backgrounds, since that is the default. ts << " [bgcolor=" << o.style()->backgroundColor().name() << "]"; if (o.parent() && (o.parent()->style()->textFillColor() != o.style()->textFillColor()) && o.style()->textFillColor().isValid() && o.style()->textFillColor() != o.style()->color() && o.style()->textFillColor().rgb()) ts << " [textFillColor=" << o.style()->textFillColor().name() << "]"; if (o.parent() && (o.parent()->style()->textStrokeColor() != o.style()->textStrokeColor()) && o.style()->textStrokeColor().isValid() && o.style()->textStrokeColor() != o.style()->color() && o.style()->textStrokeColor().rgb()) ts << " [textStrokeColor=" << o.style()->textStrokeColor().name() << "]"; if (o.parent() && (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth()) && o.style()->textStrokeWidth() > 0) ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]"; if (!o.isBoxModelObject()) return; const RenderBoxModelObject& box = *toRenderBoxModelObject(&o); if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) { ts << " [border:"; BorderValue prevBorder; if (o.style()->borderTop() != prevBorder) { prevBorder = o.style()->borderTop(); if (!box.borderTop()) ts << " none"; else { ts << " (" << box.borderTop() << "px "; printBorderStyle(ts, o.style()->borderTopStyle()); Color col = o.style()->borderTopColor(); if (!col.isValid()) col = o.style()->color(); ts << col.name() << ")"; } } if (o.style()->borderRight() != prevBorder) { prevBorder = o.style()->borderRight(); if (!box.borderRight()) ts << " none"; else { ts << " (" << box.borderRight() << "px "; printBorderStyle(ts, o.style()->borderRightStyle()); Color col = o.style()->borderRightColor(); if (!col.isValid()) col = o.style()->color(); ts << col.name() << ")"; } } if (o.style()->borderBottom() != prevBorder) { prevBorder = box.style()->borderBottom(); if (!box.borderBottom()) ts << " none"; else { ts << " (" << box.borderBottom() << "px "; printBorderStyle(ts, o.style()->borderBottomStyle()); Color col = o.style()->borderBottomColor(); if (!col.isValid()) col = o.style()->color(); ts << col.name() << ")"; } } if (o.style()->borderLeft() != prevBorder) { prevBorder = o.style()->borderLeft(); if (!box.borderLeft()) ts << " none"; else { ts << " (" << box.borderLeft() << "px "; printBorderStyle(ts, o.style()->borderLeftStyle()); Color col = o.style()->borderLeftColor(); if (!col.isValid()) col = o.style()->color(); ts << col.name() << ")"; } } ts << "]"; } } if (o.isTableCell()) { const RenderTableCell& c = *toRenderTableCell(&o); ts << " [r=" << c.row() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]"; } if (o.isListMarker()) { String text = toRenderListMarker(&o)->text(); if (!text.isEmpty()) { if (text.length() != 1) text = quoteAndEscapeNonPrintables(text); else { switch (text[0]) { case bullet: text = "bullet"; break; case blackSquare: text = "black square"; break; case whiteBullet: text = "white bullet"; break; default: text = quoteAndEscapeNonPrintables(text); } } ts << ": " << text; } } #if PLATFORM(QT) // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget // is invisible the QWidget should be invisible too. if (o.isRenderPart()) { const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o)); if (part->widget() && part->widget()->platformWidget()) { QWidget* wid = part->widget()->platformWidget(); ts << " [QT: "; ts << "geometry: {" << wid->geometry() << "} "; ts << "isHidden: " << wid->isHidden() << " "; ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " "; ts << "isParentVisible: " << part->widget()->isParentVisible() << " "; ts << "mask: {" << wid->mask().boundingRect() << "} ] "; } } #endif }
void RenderTreeAsText::writeRenderObject(TextStream& ts, const RenderObject& o, RenderAsTextBehavior behavior) { ts << o.renderName(); if (behavior & RenderAsTextShowAddresses) ts << " " << static_cast<const void*>(&o); if (o.style() && o.style()->zIndex()) ts << " zI: " << o.style()->zIndex(); if (o.node()) { String tagName = getTagName(o.node()); if (!tagName.isEmpty()) { ts << " {" << tagName << "}"; // flag empty or unstyled AppleStyleSpan because we never // want to leave them in the DOM if (isEmptyOrUnstyledAppleStyleSpan(o.node())) ts << " *empty or unstyled AppleStyleSpan*"; } } RenderBlock* cb = o.containingBlock(); bool adjustForTableCells = cb ? cb->isTableCell() : false; LayoutRect r; if (o.isText()) { // FIXME: Would be better to dump the bounding box x and y rather than the first run's x and y, but that would involve updating // many test results. const RenderText& text = *toRenderText(&o); IntRect linesBox = text.linesBoundingBox(); r = IntRect(text.firstRunX(), text.firstRunY(), linesBox.width(), linesBox.height()); if (adjustForTableCells && !text.firstTextBox()) adjustForTableCells = false; } else if (o.isRenderInline()) { // FIXME: Would be better not to just dump 0, 0 as the x and y here. const RenderInline& inlineFlow = *toRenderInline(&o); r = IntRect(0, 0, inlineFlow.linesBoundingBox().width(), inlineFlow.linesBoundingBox().height()); adjustForTableCells = false; } else if (o.isTableCell()) { // FIXME: Deliberately dump the "inner" box of table cells, since that is what current results reflect. We'd like // to clean up the results to dump both the outer box and the intrinsic padding so that both bits of information are // captured by the results. const RenderTableCell& cell = *toRenderTableCell(&o); r = LayoutRect(cell.x(), cell.y() + cell.intrinsicPaddingBefore(), cell.width(), cell.height() - cell.intrinsicPaddingBefore() - cell.intrinsicPaddingAfter()); } else if (o.isBox()) r = toRenderBox(&o)->frameRect(); // FIXME: Temporary in order to ensure compatibility with existing layout test results. if (adjustForTableCells) r.move(0, -toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore()); // FIXME: Convert layout test results to report sub-pixel values, in the meantime using enclosingIntRect // for consistency with old results. ts << " " << enclosingIntRect(r); if (!(o.isText() && !o.isBR())) { if (o.isFileUploadControl()) ts << " " << quoteAndEscapeNonPrintables(toRenderFileUploadControl(&o)->fileTextValue()); if (o.parent()) { Color color = o.style()->visitedDependentColor(CSSPropertyColor); if (o.parent()->style()->visitedDependentColor(CSSPropertyColor) != color) ts << " [color=" << color.nameForRenderTreeAsText() << "]"; // Do not dump invalid or transparent backgrounds, since that is the default. Color backgroundColor = o.style()->visitedDependentColor(CSSPropertyBackgroundColor); if (o.parent()->style()->visitedDependentColor(CSSPropertyBackgroundColor) != backgroundColor && backgroundColor.isValid() && backgroundColor.rgb()) ts << " [bgcolor=" << backgroundColor.nameForRenderTreeAsText() << "]"; Color textFillColor = o.style()->visitedDependentColor(CSSPropertyWebkitTextFillColor); if (o.parent()->style()->visitedDependentColor(CSSPropertyWebkitTextFillColor) != textFillColor && textFillColor.isValid() && textFillColor != color && textFillColor.rgb()) ts << " [textFillColor=" << textFillColor.nameForRenderTreeAsText() << "]"; Color textStrokeColor = o.style()->visitedDependentColor(CSSPropertyWebkitTextStrokeColor); if (o.parent()->style()->visitedDependentColor(CSSPropertyWebkitTextStrokeColor) != textStrokeColor && textStrokeColor.isValid() && textStrokeColor != color && textStrokeColor.rgb()) ts << " [textStrokeColor=" << textStrokeColor.nameForRenderTreeAsText() << "]"; if (o.parent()->style()->textStrokeWidth() != o.style()->textStrokeWidth() && o.style()->textStrokeWidth() > 0) ts << " [textStrokeWidth=" << o.style()->textStrokeWidth() << "]"; } if (!o.isBoxModelObject()) return; const RenderBoxModelObject& box = *toRenderBoxModelObject(&o); if (box.borderTop() || box.borderRight() || box.borderBottom() || box.borderLeft()) { ts << " [border:"; BorderValue prevBorder = o.style()->borderTop(); if (!box.borderTop()) ts << " none"; else { ts << " (" << box.borderTop() << "px "; printBorderStyle(ts, o.style()->borderTopStyle()); Color col = o.style()->borderTopColor(); if (!col.isValid()) col = o.style()->color(); ts << col.nameForRenderTreeAsText() << ")"; } if (o.style()->borderRight() != prevBorder) { prevBorder = o.style()->borderRight(); if (!box.borderRight()) ts << " none"; else { ts << " (" << box.borderRight() << "px "; printBorderStyle(ts, o.style()->borderRightStyle()); Color col = o.style()->borderRightColor(); if (!col.isValid()) col = o.style()->color(); ts << col.nameForRenderTreeAsText() << ")"; } } if (o.style()->borderBottom() != prevBorder) { prevBorder = box.style()->borderBottom(); if (!box.borderBottom()) ts << " none"; else { ts << " (" << box.borderBottom() << "px "; printBorderStyle(ts, o.style()->borderBottomStyle()); Color col = o.style()->borderBottomColor(); if (!col.isValid()) col = o.style()->color(); ts << col.nameForRenderTreeAsText() << ")"; } } if (o.style()->borderLeft() != prevBorder) { prevBorder = o.style()->borderLeft(); if (!box.borderLeft()) ts << " none"; else { ts << " (" << box.borderLeft() << "px "; printBorderStyle(ts, o.style()->borderLeftStyle()); Color col = o.style()->borderLeftColor(); if (!col.isValid()) col = o.style()->color(); ts << col.nameForRenderTreeAsText() << ")"; } } ts << "]"; } #if ENABLE(MATHML) // We want to show any layout padding, both CSS padding and intrinsic padding, so we can't just check o.style()->hasPadding(). if (o.isRenderMathMLBlock() && (box.paddingTop() || box.paddingRight() || box.paddingBottom() || box.paddingLeft())) { ts << " ["; LayoutUnit cssTop = box.computedCSSPaddingTop(); LayoutUnit cssRight = box.computedCSSPaddingRight(); LayoutUnit cssBottom = box.computedCSSPaddingBottom(); LayoutUnit cssLeft = box.computedCSSPaddingLeft(); if (box.paddingTop() != cssTop || box.paddingRight() != cssRight || box.paddingBottom() != cssBottom || box.paddingLeft() != cssLeft) { ts << "intrinsic "; if (cssTop || cssRight || cssBottom || cssLeft) ts << "+ CSS "; } ts << "padding: " << roundToInt(box.paddingTop()) << " " << roundToInt(box.paddingRight()) << " " << roundToInt(box.paddingBottom()) << " " << roundToInt(box.paddingLeft()) << "]"; } #endif } if (o.isTableCell()) { const RenderTableCell& c = *toRenderTableCell(&o); ts << " [r=" << c.rowIndex() << " c=" << c.col() << " rs=" << c.rowSpan() << " cs=" << c.colSpan() << "]"; } #if ENABLE(DETAILS_ELEMENT) if (o.isDetailsMarker()) { ts << ": "; switch (toRenderDetailsMarker(&o)->orientation()) { case RenderDetailsMarker::Left: ts << "left"; break; case RenderDetailsMarker::Right: ts << "right"; break; case RenderDetailsMarker::Up: ts << "up"; break; case RenderDetailsMarker::Down: ts << "down"; break; } } #endif if (o.isListMarker()) { String text = toRenderListMarker(&o)->text(); if (!text.isEmpty()) { if (text.length() != 1) text = quoteAndEscapeNonPrintables(text); else { switch (text[0]) { case bullet: text = "bullet"; break; case blackSquare: text = "black square"; break; case whiteBullet: text = "white bullet"; break; default: text = quoteAndEscapeNonPrintables(text); } } ts << ": " << text; } } if (behavior & RenderAsTextShowIDAndClass) { if (Node* node = o.node()) { if (node->hasID()) ts << " id=\"" + static_cast<Element*>(node)->getIdAttribute() + "\""; if (node->hasClass()) { ts << " class=\""; StyledElement* styledElement = static_cast<StyledElement*>(node); for (size_t i = 0; i < styledElement->classNames().size(); ++i) { if (i > 0) ts << " "; ts << styledElement->classNames()[i]; } ts << "\""; } } } if (behavior & RenderAsTextShowLayoutState) { bool needsLayout = o.selfNeedsLayout() || o.needsPositionedMovementLayout() || o.posChildNeedsLayout() || o.normalChildNeedsLayout(); if (needsLayout) ts << " (needs layout:"; bool havePrevious = false; if (o.selfNeedsLayout()) { ts << " self"; havePrevious = true; } if (o.needsPositionedMovementLayout()) { if (havePrevious) ts << ","; havePrevious = true; ts << " positioned movement"; } if (o.normalChildNeedsLayout()) { if (havePrevious) ts << ","; havePrevious = true; ts << " child"; } if (o.posChildNeedsLayout()) { if (havePrevious) ts << ","; ts << " positioned child"; } if (needsLayout) ts << ")"; } #if PLATFORM(QT) // Print attributes of embedded QWidgets. E.g. when the WebCore::Widget // is invisible the QWidget should be invisible too. if (o.isRenderPart()) { const RenderPart* part = toRenderPart(const_cast<RenderObject*>(&o)); if (part->widget() && part->widget()->platformWidget()) { QObject* wid = part->widget()->platformWidget(); ts << " [QT: "; ts << "geometry: {" << wid->property("geometry").toRect() << "} "; ts << "isHidden: " << !wid->property("isVisible").toBool() << " "; ts << "isSelfVisible: " << part->widget()->isSelfVisible() << " "; ts << "isParentVisible: " << part->widget()->isParentVisible() << " ] "; } } #endif }
void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* end, int endPos, SelectionPaintInvalidationMode blockPaintInvalidationMode) { // This code makes no assumptions as to if the rendering tree is up to date or not // and will not try to update it. Currently clearSelection calls this // (intentionally) without updating the rendering tree as it doesn't care. // Other callers may want to force recalc style before calling this. // Make sure both our start and end objects are defined. // Check www.msnbc.com and try clicking around to find the case where this happened. if ((start && !end) || (end && !start)) return; // Just return if the selection hasn't changed. if (m_selectionStart == start && m_selectionStartPos == startPos && m_selectionEnd == end && m_selectionEndPos == endPos) return; // Record the old selected objects. These will be used later // when we compare against the new selected objects. int oldStartPos = m_selectionStartPos; int oldEndPos = m_selectionEndPos; // Objects each have a single selection rect to examine. typedef WillBeHeapHashMap<RawPtrWillBeMember<RenderObject>, SelectionState > SelectedObjectMap; SelectedObjectMap oldSelectedObjects; // FIXME: |newSelectedObjects| doesn't really need to store the SelectionState, it's just more convenient // to have it use the same data structure as |oldSelectedObjects|. SelectedObjectMap newSelectedObjects; // Blocks contain selected objects and fill gaps between them, either on the left, right, or in between lines and blocks. // In order to get the paint invalidation rect right, we have to examine left, middle, and right rects individually, since otherwise // the union of those rects might remain the same even when changes have occurred. typedef WillBeHeapHashMap<RawPtrWillBeMember<RenderBlock>, SelectionState > SelectedBlockMap; SelectedBlockMap oldSelectedBlocks; // FIXME: |newSelectedBlocks| doesn't really need to store the SelectionState, it's just more convenient // to have it use the same data structure as |oldSelectedBlocks|. SelectedBlockMap newSelectedBlocks; RenderObject* os = m_selectionStart; RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); bool exploringBackwards = false; bool continueExploring = os && (os != stop); while (continueExploring) { if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. oldSelectedObjects.set(os, os->selectionState()); if (blockPaintInvalidationMode == PaintInvalidationNewXOROld) { RenderBlock* cb = os->containingBlock(); while (cb && !cb->isRenderView()) { SelectedBlockMap::AddResult result = oldSelectedBlocks.add(cb, cb->selectionState()); if (!result.isNewEntry) break; cb = cb->containingBlock(); } } } os = getNextOrPrevRenderObjectBasedOnDirection(os, stop, continueExploring, exploringBackwards); } // Now clear the selection. SelectedObjectMap::iterator oldObjectsEnd = oldSelectedObjects.end(); for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) i->key->setSelectionStateIfNeeded(SelectionNone); // set selection start and end m_selectionStart = start; m_selectionStartPos = startPos; m_selectionEnd = end; m_selectionEndPos = endPos; // Update the selection status of all objects between m_selectionStart and m_selectionEnd if (start && start == end) start->setSelectionStateIfNeeded(SelectionBoth); else { if (start) start->setSelectionStateIfNeeded(SelectionStart); if (end) end->setSelectionStateIfNeeded(SelectionEnd); } RenderObject* o = start; stop = rendererAfterPosition(end, endPos); while (o && o != stop) { if (o != start && o != end && o->canBeSelectionLeaf()) o->setSelectionStateIfNeeded(SelectionInside); o = o->nextInPreOrder(); } layer()->clearBlockSelectionGapsBounds(); // Now that the selection state has been updated for the new objects, walk them again and // put them in the new objects list. o = start; exploringBackwards = false; continueExploring = o && (o != stop); while (continueExploring) { if ((o->canBeSelectionLeaf() || o == start || o == end) && o->selectionState() != SelectionNone) { newSelectedObjects.set(o, o->selectionState()); RenderBlock* cb = o->containingBlock(); while (cb && !cb->isRenderView()) { SelectedBlockMap::AddResult result = newSelectedBlocks.add(cb, cb->selectionState()); if (!result.isNewEntry) break; cb = cb->containingBlock(); } } o = getNextOrPrevRenderObjectBasedOnDirection(o, stop, continueExploring, exploringBackwards); } if (!m_frameView) return; // Have any of the old selected objects changed compared to the new selection? for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) { RenderObject* obj = i->key; SelectionState newSelectionState = obj->selectionState(); SelectionState oldSelectionState = i->value; if (newSelectionState != oldSelectionState || (m_selectionStart == obj && oldStartPos != m_selectionStartPos) || (m_selectionEnd == obj && oldEndPos != m_selectionEndPos)) { obj->setShouldInvalidateSelection(); newSelectedObjects.remove(obj); } } // Any new objects that remain were not found in the old objects dict, and so they need to be updated. SelectedObjectMap::iterator newObjectsEnd = newSelectedObjects.end(); for (SelectedObjectMap::iterator i = newSelectedObjects.begin(); i != newObjectsEnd; ++i) i->key->setShouldInvalidateSelection(); // Have any of the old blocks changed? SelectedBlockMap::iterator oldBlocksEnd = oldSelectedBlocks.end(); for (SelectedBlockMap::iterator i = oldSelectedBlocks.begin(); i != oldBlocksEnd; ++i) { RenderBlock* block = i->key; SelectionState newSelectionState = block->selectionState(); SelectionState oldSelectionState = i->value; if (newSelectionState != oldSelectionState) { block->setShouldInvalidateSelection(); newSelectedBlocks.remove(block); } } // Any new blocks that remain were not found in the old blocks dict, and so they need to be updated. SelectedBlockMap::iterator newBlocksEnd = newSelectedBlocks.end(); for (SelectedBlockMap::iterator i = newSelectedBlocks.begin(); i != newBlocksEnd; ++i) i->key->setShouldInvalidateSelection(); }
VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int x) { Position p = visiblePosition.deepEquivalent(); Node *node = p.node(); Node* highestRoot = highestEditableRoot(p); if (!node) return VisiblePosition(); node->document()->updateLayoutIgnorePendingStylesheets(); RenderObject *renderer = node->renderer(); if (!renderer) return VisiblePosition(); RenderBlock *containingBlock = 0; RootInlineBox *root = 0; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); if (box) { root = box->root()->prevRootBox(); if (root) containingBlock = renderer->containingBlock(); } if (!root) { // This containing editable block does not have a previous line. // Need to move back to previous containing editable block in this root editable // block and find the last root line box in that block. Node* startBlock = enclosingBlock(node); Node* n = previousLeafWithSameEditability(node); while (n && startBlock == enclosingBlock(n)) n = previousLeafWithSameEditability(n); while (n) { if (highestEditableRoot(Position(n, 0)) != highestRoot) break; Position pos(n, caretMinOffset(n)); if (pos.isCandidate()) { ASSERT(n->renderer()); Position maxPos(n, caretMaxOffset(n)); maxPos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset); if (box) { // previous root line box found root = box->root(); containingBlock = n->renderer()->containingBlock(); break; } return VisiblePosition(pos, DOWNSTREAM); } n = previousLeafWithSameEditability(n); } } if (root) { // FIXME: Can be wrong for multi-column layout. int absx, absy; containingBlock->absolutePositionForContent(absx, absy); if (containingBlock->hasOverflowClip()) containingBlock->layer()->subtractScrollOffset(absx, absy); RenderObject *renderer = root->closestLeafChildForXPos(x - absx, isEditablePosition(p))->object(); Node* node = renderer->element(); if (editingIgnoresContent(node)) return Position(node->parent(), node->nodeIndex()); return renderer->positionForCoordinates(x - absx, root->topOverflow()); } // Could not find a previous line. This means we must already be on the first line. // Move to the start of the content in this block, which effectively moves us // to the start of the line we're on. Node* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement(); return VisiblePosition(rootElement, 0, DOWNSTREAM); }
void RenderView::setSelection(RenderObject *s, int sp, RenderObject *e, int ep) { // Make sure both our start and end objects are defined. // Check www.msnbc.com and try clicking around to find the case where this happened. if ((s && !e) || (e && !s)) return; // Just return if the selection hasn't changed. if (m_selectionStart == s && m_selectionStartPos == sp && m_selectionEnd == e && m_selectionEndPos == ep) return; // Record the old selected objects. These will be used later // when we compare against the new selected objects. int oldStartPos = m_selectionStartPos; int oldEndPos = m_selectionEndPos; // Objects each have a single selection rect to examine. typedef HashMap<RenderObject*, SelectionInfo*> SelectedObjectMap; SelectedObjectMap oldSelectedObjects; SelectedObjectMap newSelectedObjects; // Blocks contain selected objects and fill gaps between them, either on the left, right, or in between lines and blocks. // In order to get the repaint rect right, we have to examine left, middle, and right rects individually, since otherwise // the union of those rects might remain the same even when changes have occurred. typedef HashMap<RenderBlock*, BlockSelectionInfo*> SelectedBlockMap; SelectedBlockMap oldSelectedBlocks; SelectedBlockMap newSelectedBlocks; RenderObject* os = m_selectionStart; RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); while (os && os != stop) { if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. oldSelectedObjects.set(os, new SelectionInfo(os)); RenderBlock* cb = os->containingBlock(); while (cb && !cb->isRenderView()) { BlockSelectionInfo* blockInfo = oldSelectedBlocks.get(cb); if (blockInfo) break; oldSelectedBlocks.set(cb, new BlockSelectionInfo(cb)); cb = cb->containingBlock(); } } os = os->nextInPreOrder(); } // Now clear the selection. SelectedObjectMap::iterator oldObjectsEnd = oldSelectedObjects.end(); for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) i->first->setSelectionState(SelectionNone); // set selection start and end m_selectionStart = s; m_selectionStartPos = sp; m_selectionEnd = e; m_selectionEndPos = ep; // Update the selection status of all objects between m_selectionStart and m_selectionEnd if (s && s == e) s->setSelectionState(SelectionBoth); else { if (s) s->setSelectionState(SelectionStart); if (e) e->setSelectionState(SelectionEnd); } RenderObject* o = s; stop = rendererAfterPosition(e, ep); while (o && o != stop) { if (o != s && o != e && o->canBeSelectionLeaf()) o->setSelectionState(SelectionInside); o = o->nextInPreOrder(); } // Now that the selection state has been updated for the new objects, walk them again and // put them in the new objects list. o = s; while (o && o != stop) { if ((o->canBeSelectionLeaf() || o == s || o == e) && o->selectionState() != SelectionNone) { newSelectedObjects.set(o, new SelectionInfo(o)); RenderBlock* cb = o->containingBlock(); while (cb && !cb->isRenderView()) { BlockSelectionInfo* blockInfo = newSelectedBlocks.get(cb); if (blockInfo) break; newSelectedBlocks.set(cb, new BlockSelectionInfo(cb)); cb = cb->containingBlock(); } } o = o->nextInPreOrder(); } if (!m_frameView) return; // Have any of the old selected objects changed compared to the new selection? for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) { RenderObject* obj = i->first; SelectionInfo* newInfo = newSelectedObjects.get(obj); SelectionInfo* oldInfo = i->second; if (!newInfo || oldInfo->rect() != newInfo->rect() || oldInfo->state() != newInfo->state() || (m_selectionStart == obj && oldStartPos != m_selectionStartPos) || (m_selectionEnd == obj && oldEndPos != m_selectionEndPos)) { m_frameView->updateContents(oldInfo->rect()); if (newInfo) { m_frameView->updateContents(newInfo->rect()); newSelectedObjects.remove(obj); delete newInfo; } } delete oldInfo; } // Any new objects that remain were not found in the old objects dict, and so they need to be updated. SelectedObjectMap::iterator newObjectsEnd = newSelectedObjects.end(); for (SelectedObjectMap::iterator i = newSelectedObjects.begin(); i != newObjectsEnd; ++i) { SelectionInfo* newInfo = i->second; m_frameView->updateContents(newInfo->rect()); delete newInfo; } // Have any of the old blocks changed? SelectedBlockMap::iterator oldBlocksEnd = oldSelectedBlocks.end(); for (SelectedBlockMap::iterator i = oldSelectedBlocks.begin(); i != oldBlocksEnd; ++i) { RenderBlock* block = i->first; BlockSelectionInfo* newInfo = newSelectedBlocks.get(block); BlockSelectionInfo* oldInfo = i->second; if (!newInfo || oldInfo->rects() != newInfo->rects() || oldInfo->state() != newInfo->state()) { m_frameView->updateContents(oldInfo->rects()); if (newInfo) { m_frameView->updateContents(newInfo->rects()); newSelectedBlocks.remove(block); delete newInfo; } } delete oldInfo; } // Any new blocks that remain were not found in the old blocks dict, and so they need to be updated. SelectedBlockMap::iterator newBlocksEnd = newSelectedBlocks.end(); for (SelectedBlockMap::iterator i = newSelectedBlocks.begin(); i != newBlocksEnd; ++i) { BlockSelectionInfo* newInfo = i->second; m_frameView->updateContents(newInfo->rects()); delete newInfo; } }
int Frame::checkOverflowScroll(OverflowScrollAction action) { Position extent = selection().selection().extent(); if (extent.isNull()) return OverflowScrollNone; RenderObject* renderer = extent.deprecatedNode()->renderer(); if (!renderer) return OverflowScrollNone; FrameView* view = this->view(); if (!view) return OverflowScrollNone; RenderBlock* containingBlock = renderer->containingBlock(); if (!containingBlock || !containingBlock->hasOverflowClip()) return OverflowScrollNone; RenderLayer* layer = containingBlock->layer(); ASSERT(layer); IntRect visibleRect = IntRect(view->scrollX(), view->scrollY(), view->visibleWidth(), view->visibleHeight()); IntPoint position = m_overflowAutoScrollPos; if (visibleRect.contains(position.x(), position.y())) return OverflowScrollNone; int scrollType = 0; int deltaX = 0; int deltaY = 0; IntPoint selectionPosition; // This constant will make the selection draw a little bit beyond the edge of the visible area. // This prevents a visual glitch, in that you can fail to select a portion of a character that // is being rendered right at the edge of the visible rectangle. // FIXME: This probably needs improvement, and may need to take the font size into account. static const int scrollBoundsAdjustment = 3; // FIXME: Make a small buffer at the end of a visible rectangle so that autoscrolling works // even if the visible extends to the limits of the screen. if (position.x() < visibleRect.x()) { scrollType |= OverflowScrollLeft; if (action == PerformOverflowScroll) { deltaX -= static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setX(view->scrollX() - scrollBoundsAdjustment); } } else if (position.x() > visibleRect.maxX()) { scrollType |= OverflowScrollRight; if (action == PerformOverflowScroll) { deltaX += static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setX(view->scrollX() + view->visibleWidth() + scrollBoundsAdjustment); } } if (position.y() < visibleRect.y()) { scrollType |= OverflowScrollUp; if (action == PerformOverflowScroll) { deltaY -= static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setY(view->scrollY() - scrollBoundsAdjustment); } } else if (position.y() > visibleRect.maxY()) { scrollType |= OverflowScrollDown; if (action == PerformOverflowScroll) { deltaY += static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setY(view->scrollY() + view->visibleHeight() + scrollBoundsAdjustment); } } Ref<Frame> protectedThis(*this); if (action == PerformOverflowScroll && (deltaX || deltaY)) { layer->scrollToOffset(layer->scrollOffset() + IntSize(deltaX, deltaY)); // Handle making selection. VisiblePosition visiblePosition(renderer->positionForPoint(selectionPosition, nullptr)); if (visiblePosition.isNotNull()) { VisibleSelection visibleSelection = selection().selection(); visibleSelection.setExtent(visiblePosition); if (selection().granularity() != CharacterGranularity) visibleSelection.expandUsingGranularity(selection().granularity()); if (selection().shouldChangeSelection(visibleSelection)) selection().setSelection(visibleSelection); } m_overflowAutoScrollDelta *= 1.02f; // Accelerate the scroll } return scrollType; }