1/**
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 2000 Simon Hausmann <hausmann@kde.org>
4 *           (C) 2000 Stefan Schimanski (1Stein@gmx.de)
5 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 *
22 */
23
24#include "config.h"
25#include "core/rendering/RenderFrameSet.h"
26
27#include "core/dom/Document.h"
28#include "core/events/MouseEvent.h"
29#include "core/frame/LocalFrame.h"
30#include "core/html/HTMLDimension.h"
31#include "core/html/HTMLFrameSetElement.h"
32#include "core/page/EventHandler.h"
33#include "core/rendering/GraphicsContextAnnotator.h"
34#include "core/rendering/PaintInfo.h"
35#include "core/rendering/RenderFrame.h"
36#include "core/rendering/RenderView.h"
37#include "platform/Cursor.h"
38#include "platform/graphics/GraphicsContext.h"
39
40namespace blink {
41
42RenderFrameSet::RenderFrameSet(HTMLFrameSetElement* frameSet)
43    : RenderBox(frameSet)
44    , m_isResizing(false)
45    , m_isChildResizing(false)
46{
47    setInline(false);
48}
49
50RenderFrameSet::~RenderFrameSet()
51{
52}
53
54void RenderFrameSet::trace(Visitor* visitor)
55{
56    visitor->trace(m_children);
57    RenderBox::trace(visitor);
58}
59
60RenderFrameSet::GridAxis::GridAxis()
61    : m_splitBeingResized(noSplit)
62{
63}
64
65inline HTMLFrameSetElement* RenderFrameSet::frameSet() const
66{
67    return toHTMLFrameSetElement(node());
68}
69
70static Color borderStartEdgeColor()
71{
72    return Color(170, 170, 170);
73}
74
75static Color borderEndEdgeColor()
76{
77    return Color::black;
78}
79
80static Color borderFillColor()
81{
82    return Color(208, 208, 208);
83}
84
85void RenderFrameSet::paintColumnBorder(const PaintInfo& paintInfo, const IntRect& borderRect)
86{
87    if (!paintInfo.rect.intersects(borderRect))
88        return;
89
90    // FIXME: We should do something clever when borders from distinct framesets meet at a join.
91
92    // Fill first.
93    GraphicsContext* context = paintInfo.context;
94    context->fillRect(borderRect, frameSet()->hasBorderColor() ? resolveColor(CSSPropertyBorderLeftColor) : borderFillColor());
95
96    // Now stroke the edges but only if we have enough room to paint both edges with a little
97    // bit of the fill color showing through.
98    if (borderRect.width() >= 3) {
99        context->fillRect(IntRect(borderRect.location(), IntSize(1, height())), borderStartEdgeColor());
100        context->fillRect(IntRect(IntPoint(borderRect.maxX() - 1, borderRect.y()), IntSize(1, height())), borderEndEdgeColor());
101    }
102}
103
104void RenderFrameSet::paintRowBorder(const PaintInfo& paintInfo, const IntRect& borderRect)
105{
106    if (!paintInfo.rect.intersects(borderRect))
107        return;
108
109    // FIXME: We should do something clever when borders from distinct framesets meet at a join.
110
111    // Fill first.
112    GraphicsContext* context = paintInfo.context;
113    context->fillRect(borderRect, frameSet()->hasBorderColor() ? resolveColor(CSSPropertyBorderLeftColor) : borderFillColor());
114
115    // Now stroke the edges but only if we have enough room to paint both edges with a little
116    // bit of the fill color showing through.
117    if (borderRect.height() >= 3) {
118        context->fillRect(IntRect(borderRect.location(), IntSize(width(), 1)), borderStartEdgeColor());
119        context->fillRect(IntRect(IntPoint(borderRect.x(), borderRect.maxY() - 1), IntSize(width(), 1)), borderEndEdgeColor());
120    }
121}
122
123void RenderFrameSet::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
124{
125    ANNOTATE_GRAPHICS_CONTEXT(paintInfo, this);
126
127    if (paintInfo.phase != PaintPhaseForeground)
128        return;
129
130    RenderObject* child = firstChild();
131    if (!child)
132        return;
133
134    LayoutPoint adjustedPaintOffset = paintOffset + location();
135
136    size_t rows = m_rows.m_sizes.size();
137    size_t cols = m_cols.m_sizes.size();
138    LayoutUnit borderThickness = frameSet()->border();
139
140    LayoutUnit yPos = 0;
141    for (size_t r = 0; r < rows; r++) {
142        LayoutUnit xPos = 0;
143        for (size_t c = 0; c < cols; c++) {
144            child->paint(paintInfo, adjustedPaintOffset);
145            xPos += m_cols.m_sizes[c];
146            if (borderThickness && m_cols.m_allowBorder[c + 1]) {
147                paintColumnBorder(paintInfo, pixelSnappedIntRect(LayoutRect(adjustedPaintOffset.x() + xPos, adjustedPaintOffset.y() + yPos, borderThickness, height())));
148                xPos += borderThickness;
149            }
150            child = child->nextSibling();
151            if (!child)
152                return;
153        }
154        yPos += m_rows.m_sizes[r];
155        if (borderThickness && m_rows.m_allowBorder[r + 1]) {
156            paintRowBorder(paintInfo, pixelSnappedIntRect(LayoutRect(adjustedPaintOffset.x(), adjustedPaintOffset.y() + yPos, width(), borderThickness)));
157            yPos += borderThickness;
158        }
159    }
160}
161
162void RenderFrameSet::computePreferredLogicalWidths()
163{
164    m_minPreferredLogicalWidth = 0;
165    m_maxPreferredLogicalWidth = 0;
166    clearPreferredLogicalWidthsDirty();
167}
168
169void RenderFrameSet::GridAxis::resize(int size)
170{
171    m_sizes.resize(size);
172    m_deltas.resize(size);
173    m_deltas.fill(0);
174
175    // To track edges for resizability and borders, we need to be (size + 1). This is because a parent frameset
176    // may ask us for information about our left/top/right/bottom edges in order to make its own decisions about
177    // what to do. We are capable of tainting that parent frameset's borders, so we have to cache this info.
178    m_preventResize.resize(size + 1);
179    m_allowBorder.resize(size + 1);
180}
181
182void RenderFrameSet::layOutAxis(GridAxis& axis, const Vector<HTMLDimension>& grid, int availableLen)
183{
184    availableLen = max(availableLen, 0);
185
186    int* gridLayout = axis.m_sizes.data();
187
188    if (grid.isEmpty()) {
189        gridLayout[0] = availableLen;
190        return;
191    }
192
193    int gridLen = axis.m_sizes.size();
194    ASSERT(gridLen);
195
196    int totalRelative = 0;
197    int totalFixed = 0;
198    int totalPercent = 0;
199    int countRelative = 0;
200    int countFixed = 0;
201    int countPercent = 0;
202
203    // First we need to investigate how many columns of each type we have and
204    // how much space these columns are going to require.
205    for (int i = 0; i < gridLen; ++i) {
206        // Count the total length of all of the fixed columns/rows -> totalFixed
207        // Count the number of columns/rows which are fixed -> countFixed
208        if (grid[i].isAbsolute()) {
209            gridLayout[i] = max<int>(grid[i].value(), 0);
210            totalFixed += gridLayout[i];
211            countFixed++;
212        }
213
214        // Count the total percentage of all of the percentage columns/rows -> totalPercent
215        // Count the number of columns/rows which are percentages -> countPercent
216        if (grid[i].isPercentage()) {
217            gridLayout[i] = max<int>(grid[i].value() * availableLen / 100., 0);
218            totalPercent += gridLayout[i];
219            countPercent++;
220        }
221
222        // Count the total relative of all the relative columns/rows -> totalRelative
223        // Count the number of columns/rows which are relative -> countRelative
224        if (grid[i].isRelative()) {
225            totalRelative += max<int>(grid[i].value(), 1);
226            countRelative++;
227        }
228    }
229
230    int remainingLen = availableLen;
231
232    // Fixed columns/rows are our first priority. If there is not enough space to fit all fixed
233    // columns/rows we need to proportionally adjust their size.
234    if (totalFixed > remainingLen) {
235        int remainingFixed = remainingLen;
236
237        for (int i = 0; i < gridLen; ++i) {
238            if (grid[i].isAbsolute()) {
239                gridLayout[i] = (gridLayout[i] * remainingFixed) / totalFixed;
240                remainingLen -= gridLayout[i];
241            }
242        }
243    } else
244        remainingLen -= totalFixed;
245
246    // Percentage columns/rows are our second priority. Divide the remaining space proportionally
247    // over all percentage columns/rows. IMPORTANT: the size of each column/row is not relative
248    // to 100%, but to the total percentage. For example, if there are three columns, each of 75%,
249    // and the available space is 300px, each column will become 100px in width.
250    if (totalPercent > remainingLen) {
251        int remainingPercent = remainingLen;
252
253        for (int i = 0; i < gridLen; ++i) {
254            if (grid[i].isPercentage()) {
255                gridLayout[i] = (gridLayout[i] * remainingPercent) / totalPercent;
256                remainingLen -= gridLayout[i];
257            }
258        }
259    } else
260        remainingLen -= totalPercent;
261
262    // Relative columns/rows are our last priority. Divide the remaining space proportionally
263    // over all relative columns/rows. IMPORTANT: the relative value of 0* is treated as 1*.
264    if (countRelative) {
265        int lastRelative = 0;
266        int remainingRelative = remainingLen;
267
268        for (int i = 0; i < gridLen; ++i) {
269            if (grid[i].isRelative()) {
270                gridLayout[i] = (max(grid[i].value(), 1.) * remainingRelative) / totalRelative;
271                remainingLen -= gridLayout[i];
272                lastRelative = i;
273            }
274        }
275
276        // If we could not evenly distribute the available space of all of the relative
277        // columns/rows, the remainder will be added to the last column/row.
278        // For example: if we have a space of 100px and three columns (*,*,*), the remainder will
279        // be 1px and will be added to the last column: 33px, 33px, 34px.
280        if (remainingLen) {
281            gridLayout[lastRelative] += remainingLen;
282            remainingLen = 0;
283        }
284    }
285
286    // If we still have some left over space we need to divide it over the already existing
287    // columns/rows
288    if (remainingLen) {
289        // Our first priority is to spread if over the percentage columns. The remaining
290        // space is spread evenly, for example: if we have a space of 100px, the columns
291        // definition of 25%,25% used to result in two columns of 25px. After this the
292        // columns will each be 50px in width.
293        if (countPercent && totalPercent) {
294            int remainingPercent = remainingLen;
295            int changePercent = 0;
296
297            for (int i = 0; i < gridLen; ++i) {
298                if (grid[i].isPercentage()) {
299                    changePercent = (remainingPercent * gridLayout[i]) / totalPercent;
300                    gridLayout[i] += changePercent;
301                    remainingLen -= changePercent;
302                }
303            }
304        } else if (totalFixed) {
305            // Our last priority is to spread the remaining space over the fixed columns.
306            // For example if we have 100px of space and two column of each 40px, both
307            // columns will become exactly 50px.
308            int remainingFixed = remainingLen;
309            int changeFixed = 0;
310
311            for (int i = 0; i < gridLen; ++i) {
312                if (grid[i].isAbsolute()) {
313                    changeFixed = (remainingFixed * gridLayout[i]) / totalFixed;
314                    gridLayout[i] += changeFixed;
315                    remainingLen -= changeFixed;
316                }
317            }
318        }
319    }
320
321    // If we still have some left over space we probably ended up with a remainder of
322    // a division. We cannot spread it evenly anymore. If we have any percentage
323    // columns/rows simply spread the remainder equally over all available percentage columns,
324    // regardless of their size.
325    if (remainingLen && countPercent) {
326        int remainingPercent = remainingLen;
327        int changePercent = 0;
328
329        for (int i = 0; i < gridLen; ++i) {
330            if (grid[i].isPercentage()) {
331                changePercent = remainingPercent / countPercent;
332                gridLayout[i] += changePercent;
333                remainingLen -= changePercent;
334            }
335        }
336    } else if (remainingLen && countFixed) {
337        // If we don't have any percentage columns/rows we only have
338        // fixed columns. Spread the remainder equally over all fixed
339        // columns/rows.
340        int remainingFixed = remainingLen;
341        int changeFixed = 0;
342
343        for (int i = 0; i < gridLen; ++i) {
344            if (grid[i].isAbsolute()) {
345                changeFixed = remainingFixed / countFixed;
346                gridLayout[i] += changeFixed;
347                remainingLen -= changeFixed;
348            }
349        }
350    }
351
352    // Still some left over. Add it to the last column, because it is impossible
353    // spread it evenly or equally.
354    if (remainingLen)
355        gridLayout[gridLen - 1] += remainingLen;
356
357    // now we have the final layout, distribute the delta over it
358    bool worked = true;
359    int* gridDelta = axis.m_deltas.data();
360    for (int i = 0; i < gridLen; ++i) {
361        if (gridLayout[i] && gridLayout[i] + gridDelta[i] <= 0)
362            worked = false;
363        gridLayout[i] += gridDelta[i];
364    }
365    // if the deltas broke something, undo them
366    if (!worked) {
367        for (int i = 0; i < gridLen; ++i)
368            gridLayout[i] -= gridDelta[i];
369        axis.m_deltas.fill(0);
370    }
371}
372
373void RenderFrameSet::notifyFrameEdgeInfoChanged()
374{
375    if (needsLayout())
376        return;
377    // FIXME: We should only recompute the edge info with respect to the frame that changed
378    // and its adjacent frame(s) instead of recomputing the edge info for the entire frameset.
379    computeEdgeInfo();
380}
381
382void RenderFrameSet::fillFromEdgeInfo(const FrameEdgeInfo& edgeInfo, int r, int c)
383{
384    if (edgeInfo.allowBorder(LeftFrameEdge))
385        m_cols.m_allowBorder[c] = true;
386    if (edgeInfo.allowBorder(RightFrameEdge))
387        m_cols.m_allowBorder[c + 1] = true;
388    if (edgeInfo.preventResize(LeftFrameEdge))
389        m_cols.m_preventResize[c] = true;
390    if (edgeInfo.preventResize(RightFrameEdge))
391        m_cols.m_preventResize[c + 1] = true;
392
393    if (edgeInfo.allowBorder(TopFrameEdge))
394        m_rows.m_allowBorder[r] = true;
395    if (edgeInfo.allowBorder(BottomFrameEdge))
396        m_rows.m_allowBorder[r + 1] = true;
397    if (edgeInfo.preventResize(TopFrameEdge))
398        m_rows.m_preventResize[r] = true;
399    if (edgeInfo.preventResize(BottomFrameEdge))
400        m_rows.m_preventResize[r + 1] = true;
401}
402
403void RenderFrameSet::computeEdgeInfo()
404{
405    m_rows.m_preventResize.fill(frameSet()->noResize());
406    m_rows.m_allowBorder.fill(false);
407    m_cols.m_preventResize.fill(frameSet()->noResize());
408    m_cols.m_allowBorder.fill(false);
409
410    RenderObject* child = firstChild();
411    if (!child)
412        return;
413
414    size_t rows = m_rows.m_sizes.size();
415    size_t cols = m_cols.m_sizes.size();
416    for (size_t r = 0; r < rows; ++r) {
417        for (size_t c = 0; c < cols; ++c) {
418            FrameEdgeInfo edgeInfo;
419            if (child->isFrameSet())
420                edgeInfo = toRenderFrameSet(child)->edgeInfo();
421            else
422                edgeInfo = toRenderFrame(child)->edgeInfo();
423            fillFromEdgeInfo(edgeInfo, r, c);
424            child = child->nextSibling();
425            if (!child)
426                return;
427        }
428    }
429}
430
431FrameEdgeInfo RenderFrameSet::edgeInfo() const
432{
433    FrameEdgeInfo result(frameSet()->noResize(), true);
434
435    int rows = frameSet()->totalRows();
436    int cols = frameSet()->totalCols();
437    if (rows && cols) {
438        result.setPreventResize(LeftFrameEdge, m_cols.m_preventResize[0]);
439        result.setAllowBorder(LeftFrameEdge, m_cols.m_allowBorder[0]);
440        result.setPreventResize(RightFrameEdge, m_cols.m_preventResize[cols]);
441        result.setAllowBorder(RightFrameEdge, m_cols.m_allowBorder[cols]);
442        result.setPreventResize(TopFrameEdge, m_rows.m_preventResize[0]);
443        result.setAllowBorder(TopFrameEdge, m_rows.m_allowBorder[0]);
444        result.setPreventResize(BottomFrameEdge, m_rows.m_preventResize[rows]);
445        result.setAllowBorder(BottomFrameEdge, m_rows.m_allowBorder[rows]);
446    }
447
448    return result;
449}
450
451void RenderFrameSet::layout()
452{
453    ASSERT(needsLayout());
454
455    if (!parent()->isFrameSet() && !document().printing()) {
456        setWidth(view()->viewWidth());
457        setHeight(view()->viewHeight());
458    }
459
460    unsigned cols = frameSet()->totalCols();
461    unsigned rows = frameSet()->totalRows();
462
463    if (m_rows.m_sizes.size() != rows || m_cols.m_sizes.size() != cols) {
464        m_rows.resize(rows);
465        m_cols.resize(cols);
466    }
467
468    LayoutUnit borderThickness = frameSet()->border();
469    layOutAxis(m_rows, frameSet()->rowLengths(), height() - (rows - 1) * borderThickness);
470    layOutAxis(m_cols, frameSet()->colLengths(), width() - (cols - 1) * borderThickness);
471
472    positionFrames();
473
474    RenderBox::layout();
475
476    computeEdgeInfo();
477
478    updateLayerTransformAfterLayout();
479
480    clearNeedsLayout();
481}
482
483static void clearNeedsLayoutOnHiddenFrames(RenderBox* frame)
484{
485    for (; frame; frame = frame->nextSiblingBox()) {
486        frame->setWidth(0);
487        frame->setHeight(0);
488        frame->clearNeedsLayout();
489        clearNeedsLayoutOnHiddenFrames(frame->firstChildBox());
490    }
491}
492
493void RenderFrameSet::positionFrames()
494{
495    RenderBox* child = firstChildBox();
496    if (!child)
497        return;
498
499    int rows = frameSet()->totalRows();
500    int cols = frameSet()->totalCols();
501
502    int yPos = 0;
503    int borderThickness = frameSet()->border();
504    for (int r = 0; r < rows; r++) {
505        int xPos = 0;
506        int height = m_rows.m_sizes[r];
507        for (int c = 0; c < cols; c++) {
508            child->setLocation(IntPoint(xPos, yPos));
509            int width = m_cols.m_sizes[c];
510
511            // has to be resized and itself resize its contents
512            if (width != child->width() || height != child->height()) {
513                child->setWidth(width);
514                child->setHeight(height);
515                child->setNeedsLayoutAndFullPaintInvalidation();
516                child->layout();
517            }
518
519            xPos += width + borderThickness;
520
521            child = child->nextSiblingBox();
522            if (!child)
523                return;
524        }
525        yPos += height + borderThickness;
526    }
527
528    // All the remaining frames are hidden to avoid ugly spurious unflowed frames.
529    clearNeedsLayoutOnHiddenFrames(child);
530}
531
532void RenderFrameSet::startResizing(GridAxis& axis, int position)
533{
534    int split = hitTestSplit(axis, position);
535    if (split == noSplit || axis.m_preventResize[split]) {
536        axis.m_splitBeingResized = noSplit;
537        return;
538    }
539    axis.m_splitBeingResized = split;
540    axis.m_splitResizeOffset = position - splitPosition(axis, split);
541}
542
543void RenderFrameSet::continueResizing(GridAxis& axis, int position)
544{
545    if (needsLayout())
546        return;
547    if (axis.m_splitBeingResized == noSplit)
548        return;
549    int currentSplitPosition = splitPosition(axis, axis.m_splitBeingResized);
550    int delta = (position - currentSplitPosition) - axis.m_splitResizeOffset;
551    if (!delta)
552        return;
553    axis.m_deltas[axis.m_splitBeingResized - 1] += delta;
554    axis.m_deltas[axis.m_splitBeingResized] -= delta;
555    setNeedsLayoutAndFullPaintInvalidation();
556}
557
558bool RenderFrameSet::userResize(MouseEvent* evt)
559{
560    if (!m_isResizing) {
561        if (needsLayout())
562            return false;
563        if (evt->type() == EventTypeNames::mousedown && evt->button() == LeftButton) {
564            FloatPoint localPos = absoluteToLocal(evt->absoluteLocation(), UseTransforms);
565            startResizing(m_cols, localPos.x());
566            startResizing(m_rows, localPos.y());
567            if (m_cols.m_splitBeingResized != noSplit || m_rows.m_splitBeingResized != noSplit) {
568                setIsResizing(true);
569                return true;
570            }
571        }
572    } else {
573        if (evt->type() == EventTypeNames::mousemove || (evt->type() == EventTypeNames::mouseup && evt->button() == LeftButton)) {
574            FloatPoint localPos = absoluteToLocal(evt->absoluteLocation(), UseTransforms);
575            continueResizing(m_cols, localPos.x());
576            continueResizing(m_rows, localPos.y());
577            if (evt->type() == EventTypeNames::mouseup && evt->button() == LeftButton) {
578                setIsResizing(false);
579                return true;
580            }
581        }
582    }
583
584    return false;
585}
586
587void RenderFrameSet::setIsResizing(bool isResizing)
588{
589    m_isResizing = isResizing;
590    for (RenderObject* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
591        if (ancestor->isFrameSet())
592            toRenderFrameSet(ancestor)->m_isChildResizing = isResizing;
593    }
594    if (LocalFrame* frame = this->frame())
595        frame->eventHandler().setResizingFrameSet(isResizing ? frameSet() : 0);
596}
597
598bool RenderFrameSet::canResizeRow(const IntPoint& p) const
599{
600    int r = hitTestSplit(m_rows, p.y());
601    return r != noSplit && !m_rows.m_preventResize[r];
602}
603
604bool RenderFrameSet::canResizeColumn(const IntPoint& p) const
605{
606    int c = hitTestSplit(m_cols, p.x());
607    return c != noSplit && !m_cols.m_preventResize[c];
608}
609
610int RenderFrameSet::splitPosition(const GridAxis& axis, int split) const
611{
612    if (needsLayout())
613        return 0;
614
615    int borderThickness = frameSet()->border();
616
617    int size = axis.m_sizes.size();
618    if (!size)
619        return 0;
620
621    int position = 0;
622    for (int i = 0; i < split && i < size; ++i)
623        position += axis.m_sizes[i] + borderThickness;
624    return position - borderThickness;
625}
626
627int RenderFrameSet::hitTestSplit(const GridAxis& axis, int position) const
628{
629    if (needsLayout())
630        return noSplit;
631
632    int borderThickness = frameSet()->border();
633    if (borderThickness <= 0)
634        return noSplit;
635
636    size_t size = axis.m_sizes.size();
637    if (!size)
638        return noSplit;
639
640    int splitPosition = axis.m_sizes[0];
641    for (size_t i = 1; i < size; ++i) {
642        if (position >= splitPosition && position < splitPosition + borderThickness)
643            return i;
644        splitPosition += borderThickness + axis.m_sizes[i];
645    }
646    return noSplit;
647}
648
649bool RenderFrameSet::isChildAllowed(RenderObject* child, RenderStyle*) const
650{
651    return child->isFrame() || child->isFrameSet();
652}
653
654CursorDirective RenderFrameSet::getCursor(const LayoutPoint& point, Cursor& cursor) const
655{
656    IntPoint roundedPoint = roundedIntPoint(point);
657    if (canResizeRow(roundedPoint)) {
658        cursor = rowResizeCursor();
659        return SetCursor;
660    }
661    if (canResizeColumn(roundedPoint)) {
662        cursor = columnResizeCursor();
663        return SetCursor;
664    }
665    return RenderBox::getCursor(point, cursor);
666}
667
668} // namespace blink
669