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, 2013 Apple 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 "RenderFrameSet.h" |
26 | |
27 | #include "Cursor.h" |
28 | #include "Document.h" |
29 | #include "EventHandler.h" |
30 | #include "EventNames.h" |
31 | #include "Frame.h" |
32 | #include "FrameView.h" |
33 | #include "GraphicsContext.h" |
34 | #include "HTMLFrameSetElement.h" |
35 | #include "HitTestRequest.h" |
36 | #include "HitTestResult.h" |
37 | #include "MouseEvent.h" |
38 | #include "PaintInfo.h" |
39 | #include "RenderFrame.h" |
40 | #include "RenderIterator.h" |
41 | #include "RenderLayer.h" |
42 | #include "RenderView.h" |
43 | #include "Settings.h" |
44 | #include <wtf/IsoMallocInlines.h> |
45 | #include <wtf/StackStats.h> |
46 | |
47 | namespace WebCore { |
48 | |
49 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderFrameSet); |
50 | |
51 | RenderFrameSet::RenderFrameSet(HTMLFrameSetElement& frameSet, RenderStyle&& style) |
52 | : RenderBox(frameSet, WTFMove(style), 0) |
53 | , m_isResizing(false) |
54 | , m_isChildResizing(false) |
55 | { |
56 | setInline(false); |
57 | } |
58 | |
59 | RenderFrameSet::~RenderFrameSet() = default; |
60 | |
61 | HTMLFrameSetElement& RenderFrameSet::frameSetElement() const |
62 | { |
63 | return downcast<HTMLFrameSetElement>(nodeForNonAnonymous()); |
64 | } |
65 | |
66 | RenderFrameSet::GridAxis::GridAxis() |
67 | : m_splitBeingResized(noSplit) |
68 | { |
69 | } |
70 | |
71 | static const Color& borderStartEdgeColor() |
72 | { |
73 | static NeverDestroyed<Color> color(170, 170, 170); |
74 | return color; |
75 | } |
76 | |
77 | static const Color& borderEndEdgeColor() |
78 | { |
79 | static NeverDestroyed<Color> color = Color::black; |
80 | return color; |
81 | } |
82 | |
83 | static const Color& borderFillColor() |
84 | { |
85 | static NeverDestroyed<Color> color(208, 208, 208); |
86 | return color; |
87 | } |
88 | |
89 | void RenderFrameSet::paintColumnBorder(const PaintInfo& paintInfo, const IntRect& borderRect) |
90 | { |
91 | if (!paintInfo.rect.intersects(borderRect)) |
92 | return; |
93 | |
94 | // FIXME: We should do something clever when borders from distinct framesets meet at a join. |
95 | |
96 | // Fill first. |
97 | GraphicsContext& context = paintInfo.context(); |
98 | context.fillRect(borderRect, frameSetElement().hasBorderColor() ? style().visitedDependentColorWithColorFilter(CSSPropertyBorderLeftColor) : borderFillColor()); |
99 | |
100 | // Now stroke the edges but only if we have enough room to paint both edges with a little |
101 | // bit of the fill color showing through. |
102 | if (borderRect.width() >= 3) { |
103 | context.fillRect(IntRect(borderRect.location(), IntSize(1, height())), borderStartEdgeColor()); |
104 | context.fillRect(IntRect(IntPoint(borderRect.maxX() - 1, borderRect.y()), IntSize(1, height())), borderEndEdgeColor()); |
105 | } |
106 | } |
107 | |
108 | void RenderFrameSet::paintRowBorder(const PaintInfo& paintInfo, const IntRect& borderRect) |
109 | { |
110 | if (!paintInfo.rect.intersects(borderRect)) |
111 | return; |
112 | |
113 | // FIXME: We should do something clever when borders from distinct framesets meet at a join. |
114 | |
115 | // Fill first. |
116 | GraphicsContext& context = paintInfo.context(); |
117 | context.fillRect(borderRect, frameSetElement().hasBorderColor() ? style().visitedDependentColorWithColorFilter(CSSPropertyBorderLeftColor) : borderFillColor()); |
118 | |
119 | // Now stroke the edges but only if we have enough room to paint both edges with a little |
120 | // bit of the fill color showing through. |
121 | if (borderRect.height() >= 3) { |
122 | context.fillRect(IntRect(borderRect.location(), IntSize(width(), 1)), borderStartEdgeColor()); |
123 | context.fillRect(IntRect(IntPoint(borderRect.x(), borderRect.maxY() - 1), IntSize(width(), 1)), borderEndEdgeColor()); |
124 | } |
125 | } |
126 | |
127 | void RenderFrameSet::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) |
128 | { |
129 | if (paintInfo.phase != PaintPhase::Foreground) |
130 | return; |
131 | |
132 | RenderObject* child = firstChild(); |
133 | if (!child) |
134 | return; |
135 | |
136 | LayoutPoint adjustedPaintOffset = paintOffset + location(); |
137 | |
138 | size_t rows = m_rows.m_sizes.size(); |
139 | size_t cols = m_cols.m_sizes.size(); |
140 | LayoutUnit borderThickness = frameSetElement().border(); |
141 | |
142 | LayoutUnit yPos; |
143 | for (size_t r = 0; r < rows; r++) { |
144 | LayoutUnit xPos; |
145 | for (size_t c = 0; c < cols; c++) { |
146 | downcast<RenderElement>(*child).paint(paintInfo, adjustedPaintOffset); |
147 | xPos += m_cols.m_sizes[c]; |
148 | if (borderThickness && m_cols.m_allowBorder[c + 1]) { |
149 | paintColumnBorder(paintInfo, snappedIntRect(LayoutRect(adjustedPaintOffset.x() + xPos, adjustedPaintOffset.y() + yPos, borderThickness, height()))); |
150 | xPos += borderThickness; |
151 | } |
152 | child = child->nextSibling(); |
153 | if (!child) |
154 | return; |
155 | } |
156 | yPos += m_rows.m_sizes[r]; |
157 | if (borderThickness && m_rows.m_allowBorder[r + 1]) { |
158 | paintRowBorder(paintInfo, snappedIntRect(LayoutRect(adjustedPaintOffset.x(), adjustedPaintOffset.y() + yPos, width(), borderThickness))); |
159 | yPos += borderThickness; |
160 | } |
161 | } |
162 | } |
163 | |
164 | void RenderFrameSet::GridAxis::resize(int size) |
165 | { |
166 | m_sizes.resize(size); |
167 | m_deltas.resize(size); |
168 | m_deltas.fill(0); |
169 | |
170 | // To track edges for resizability and borders, we need to be (size + 1). This is because a parent frameset |
171 | // may ask us for information about our left/top/right/bottom edges in order to make its own decisions about |
172 | // what to do. We are capable of tainting that parent frameset's borders, so we have to cache this info. |
173 | m_preventResize.resize(size + 1); |
174 | m_allowBorder.resize(size + 1); |
175 | } |
176 | |
177 | void RenderFrameSet::layOutAxis(GridAxis& axis, const Length* grid, int availableLen) |
178 | { |
179 | availableLen = std::max(availableLen, 0); |
180 | |
181 | int* gridLayout = axis.m_sizes.data(); |
182 | |
183 | if (!grid) { |
184 | gridLayout[0] = availableLen; |
185 | return; |
186 | } |
187 | |
188 | int gridLen = axis.m_sizes.size(); |
189 | ASSERT(gridLen); |
190 | |
191 | int totalRelative = 0; |
192 | int totalFixed = 0; |
193 | int totalPercent = 0; |
194 | int countRelative = 0; |
195 | int countFixed = 0; |
196 | int countPercent = 0; |
197 | |
198 | // First we need to investigate how many columns of each type we have and |
199 | // how much space these columns are going to require. |
200 | for (int i = 0; i < gridLen; ++i) { |
201 | // Count the total length of all of the fixed columns/rows -> totalFixed |
202 | // Count the number of columns/rows which are fixed -> countFixed |
203 | if (grid[i].isFixed()) { |
204 | gridLayout[i] = std::max(grid[i].intValue(), 0); |
205 | totalFixed += gridLayout[i]; |
206 | countFixed++; |
207 | } |
208 | |
209 | // Count the total percentage of all of the percentage columns/rows -> totalPercent |
210 | // Count the number of columns/rows which are percentages -> countPercent |
211 | if (grid[i].isPercentOrCalculated()) { |
212 | gridLayout[i] = std::max(intValueForLength(grid[i], availableLen), 0); |
213 | totalPercent += gridLayout[i]; |
214 | countPercent++; |
215 | } |
216 | |
217 | // Count the total relative of all the relative columns/rows -> totalRelative |
218 | // Count the number of columns/rows which are relative -> countRelative |
219 | if (grid[i].isRelative()) { |
220 | totalRelative += std::max(grid[i].intValue(), 1); |
221 | countRelative++; |
222 | } |
223 | } |
224 | |
225 | int remainingLen = availableLen; |
226 | |
227 | // Fixed columns/rows are our first priority. If there is not enough space to fit all fixed |
228 | // columns/rows we need to proportionally adjust their size. |
229 | if (totalFixed > remainingLen) { |
230 | int remainingFixed = remainingLen; |
231 | |
232 | for (int i = 0; i < gridLen; ++i) { |
233 | if (grid[i].isFixed()) { |
234 | gridLayout[i] = (gridLayout[i] * remainingFixed) / totalFixed; |
235 | remainingLen -= gridLayout[i]; |
236 | } |
237 | } |
238 | } else |
239 | remainingLen -= totalFixed; |
240 | |
241 | // Percentage columns/rows are our second priority. Divide the remaining space proportionally |
242 | // over all percentage columns/rows. IMPORTANT: the size of each column/row is not relative |
243 | // to 100%, but to the total percentage. For example, if there are three columns, each of 75%, |
244 | // and the available space is 300px, each column will become 100px in width. |
245 | if (totalPercent > remainingLen) { |
246 | int remainingPercent = remainingLen; |
247 | |
248 | for (int i = 0; i < gridLen; ++i) { |
249 | if (grid[i].isPercentOrCalculated()) { |
250 | gridLayout[i] = (gridLayout[i] * remainingPercent) / totalPercent; |
251 | remainingLen -= gridLayout[i]; |
252 | } |
253 | } |
254 | } else |
255 | remainingLen -= totalPercent; |
256 | |
257 | // Relative columns/rows are our last priority. Divide the remaining space proportionally |
258 | // over all relative columns/rows. IMPORTANT: the relative value of 0* is treated as 1*. |
259 | if (countRelative) { |
260 | int lastRelative = 0; |
261 | int remainingRelative = remainingLen; |
262 | |
263 | for (int i = 0; i < gridLen; ++i) { |
264 | if (grid[i].isRelative()) { |
265 | gridLayout[i] = (std::max(grid[i].intValue(), 1) * remainingRelative) / totalRelative; |
266 | remainingLen -= gridLayout[i]; |
267 | lastRelative = i; |
268 | } |
269 | } |
270 | |
271 | // If we could not evenly distribute the available space of all of the relative |
272 | // columns/rows, the remainder will be added to the last column/row. |
273 | // For example: if we have a space of 100px and three columns (*,*,*), the remainder will |
274 | // be 1px and will be added to the last column: 33px, 33px, 34px. |
275 | if (remainingLen) { |
276 | gridLayout[lastRelative] += remainingLen; |
277 | remainingLen = 0; |
278 | } |
279 | } |
280 | |
281 | // If we still have some left over space we need to divide it over the already existing |
282 | // columns/rows |
283 | if (remainingLen) { |
284 | // Our first priority is to spread if over the percentage columns. The remaining |
285 | // space is spread evenly, for example: if we have a space of 100px, the columns |
286 | // definition of 25%,25% used to result in two columns of 25px. After this the |
287 | // columns will each be 50px in width. |
288 | if (countPercent && totalPercent) { |
289 | int remainingPercent = remainingLen; |
290 | int changePercent = 0; |
291 | |
292 | for (int i = 0; i < gridLen; ++i) { |
293 | if (grid[i].isPercentOrCalculated()) { |
294 | changePercent = (remainingPercent * gridLayout[i]) / totalPercent; |
295 | gridLayout[i] += changePercent; |
296 | remainingLen -= changePercent; |
297 | } |
298 | } |
299 | } else if (totalFixed) { |
300 | // Our last priority is to spread the remaining space over the fixed columns. |
301 | // For example if we have 100px of space and two column of each 40px, both |
302 | // columns will become exactly 50px. |
303 | int remainingFixed = remainingLen; |
304 | int changeFixed = 0; |
305 | |
306 | for (int i = 0; i < gridLen; ++i) { |
307 | if (grid[i].isFixed()) { |
308 | changeFixed = (remainingFixed * gridLayout[i]) / totalFixed; |
309 | gridLayout[i] += changeFixed; |
310 | remainingLen -= changeFixed; |
311 | } |
312 | } |
313 | } |
314 | } |
315 | |
316 | // If we still have some left over space we probably ended up with a remainder of |
317 | // a division. We cannot spread it evenly anymore. If we have any percentage |
318 | // columns/rows simply spread the remainder equally over all available percentage columns, |
319 | // regardless of their size. |
320 | if (remainingLen && countPercent) { |
321 | int remainingPercent = remainingLen; |
322 | int changePercent = 0; |
323 | |
324 | for (int i = 0; i < gridLen; ++i) { |
325 | if (grid[i].isPercentOrCalculated()) { |
326 | changePercent = remainingPercent / countPercent; |
327 | gridLayout[i] += changePercent; |
328 | remainingLen -= changePercent; |
329 | } |
330 | } |
331 | } else if (remainingLen && countFixed) { |
332 | // If we don't have any percentage columns/rows we only have |
333 | // fixed columns. Spread the remainder equally over all fixed |
334 | // columns/rows. |
335 | int remainingFixed = remainingLen; |
336 | int changeFixed = 0; |
337 | |
338 | for (int i = 0; i < gridLen; ++i) { |
339 | if (grid[i].isFixed()) { |
340 | changeFixed = remainingFixed / countFixed; |
341 | gridLayout[i] += changeFixed; |
342 | remainingLen -= changeFixed; |
343 | } |
344 | } |
345 | } |
346 | |
347 | // Still some left over. Add it to the last column, because it is impossible |
348 | // spread it evenly or equally. |
349 | if (remainingLen) |
350 | gridLayout[gridLen - 1] += remainingLen; |
351 | |
352 | // now we have the final layout, distribute the delta over it |
353 | bool worked = true; |
354 | int* gridDelta = axis.m_deltas.data(); |
355 | for (int i = 0; i < gridLen; ++i) { |
356 | if (gridLayout[i] && gridLayout[i] + gridDelta[i] <= 0) |
357 | worked = false; |
358 | gridLayout[i] += gridDelta[i]; |
359 | } |
360 | // if the deltas broke something, undo them |
361 | if (!worked) { |
362 | for (int i = 0; i < gridLen; ++i) |
363 | gridLayout[i] -= gridDelta[i]; |
364 | axis.m_deltas.fill(0); |
365 | } |
366 | } |
367 | |
368 | void RenderFrameSet::notifyFrameEdgeInfoChanged() |
369 | { |
370 | if (needsLayout()) |
371 | return; |
372 | // FIXME: We should only recompute the edge info with respect to the frame that changed |
373 | // and its adjacent frame(s) instead of recomputing the edge info for the entire frameset. |
374 | computeEdgeInfo(); |
375 | } |
376 | |
377 | void RenderFrameSet::fillFromEdgeInfo(const FrameEdgeInfo& edgeInfo, int r, int c) |
378 | { |
379 | if (edgeInfo.allowBorder(LeftFrameEdge)) |
380 | m_cols.m_allowBorder[c] = true; |
381 | if (edgeInfo.allowBorder(RightFrameEdge)) |
382 | m_cols.m_allowBorder[c + 1] = true; |
383 | if (edgeInfo.preventResize(LeftFrameEdge)) |
384 | m_cols.m_preventResize[c] = true; |
385 | if (edgeInfo.preventResize(RightFrameEdge)) |
386 | m_cols.m_preventResize[c + 1] = true; |
387 | |
388 | if (edgeInfo.allowBorder(TopFrameEdge)) |
389 | m_rows.m_allowBorder[r] = true; |
390 | if (edgeInfo.allowBorder(BottomFrameEdge)) |
391 | m_rows.m_allowBorder[r + 1] = true; |
392 | if (edgeInfo.preventResize(TopFrameEdge)) |
393 | m_rows.m_preventResize[r] = true; |
394 | if (edgeInfo.preventResize(BottomFrameEdge)) |
395 | m_rows.m_preventResize[r + 1] = true; |
396 | } |
397 | |
398 | void RenderFrameSet::computeEdgeInfo() |
399 | { |
400 | m_rows.m_preventResize.fill(frameSetElement().noResize()); |
401 | m_rows.m_allowBorder.fill(false); |
402 | m_cols.m_preventResize.fill(frameSetElement().noResize()); |
403 | m_cols.m_allowBorder.fill(false); |
404 | |
405 | RenderObject* child = firstChild(); |
406 | if (!child) |
407 | return; |
408 | |
409 | size_t rows = m_rows.m_sizes.size(); |
410 | size_t cols = m_cols.m_sizes.size(); |
411 | for (size_t r = 0; r < rows; ++r) { |
412 | for (size_t c = 0; c < cols; ++c) { |
413 | FrameEdgeInfo edgeInfo; |
414 | if (is<RenderFrameSet>(*child)) |
415 | edgeInfo = downcast<RenderFrameSet>(*child).edgeInfo(); |
416 | else |
417 | edgeInfo = downcast<RenderFrame>(*child).edgeInfo(); |
418 | fillFromEdgeInfo(edgeInfo, r, c); |
419 | child = child->nextSibling(); |
420 | if (!child) |
421 | return; |
422 | } |
423 | } |
424 | } |
425 | |
426 | FrameEdgeInfo RenderFrameSet::edgeInfo() const |
427 | { |
428 | FrameEdgeInfo result(frameSetElement().noResize(), true); |
429 | |
430 | int rows = frameSetElement().totalRows(); |
431 | int cols = frameSetElement().totalCols(); |
432 | if (rows && cols) { |
433 | result.setPreventResize(LeftFrameEdge, m_cols.m_preventResize[0]); |
434 | result.setAllowBorder(LeftFrameEdge, m_cols.m_allowBorder[0]); |
435 | result.setPreventResize(RightFrameEdge, m_cols.m_preventResize[cols]); |
436 | result.setAllowBorder(RightFrameEdge, m_cols.m_allowBorder[cols]); |
437 | result.setPreventResize(TopFrameEdge, m_rows.m_preventResize[0]); |
438 | result.setAllowBorder(TopFrameEdge, m_rows.m_allowBorder[0]); |
439 | result.setPreventResize(BottomFrameEdge, m_rows.m_preventResize[rows]); |
440 | result.setAllowBorder(BottomFrameEdge, m_rows.m_allowBorder[rows]); |
441 | } |
442 | |
443 | return result; |
444 | } |
445 | |
446 | void RenderFrameSet::layout() |
447 | { |
448 | StackStats::LayoutCheckPoint layoutCheckPoint; |
449 | ASSERT(needsLayout()); |
450 | |
451 | bool doFullRepaint = selfNeedsLayout() && checkForRepaintDuringLayout(); |
452 | LayoutRect oldBounds; |
453 | RenderLayerModelObject* repaintContainer = 0; |
454 | if (doFullRepaint) { |
455 | repaintContainer = containerForRepaint(); |
456 | oldBounds = clippedOverflowRectForRepaint(repaintContainer); |
457 | } |
458 | |
459 | if (!parent()->isFrameSet() && !document().printing()) { |
460 | setWidth(view().viewWidth()); |
461 | setHeight(view().viewHeight()); |
462 | } |
463 | |
464 | unsigned cols = frameSetElement().totalCols(); |
465 | unsigned rows = frameSetElement().totalRows(); |
466 | |
467 | if (m_rows.m_sizes.size() != rows || m_cols.m_sizes.size() != cols) { |
468 | m_rows.resize(rows); |
469 | m_cols.resize(cols); |
470 | } |
471 | |
472 | LayoutUnit borderThickness = frameSetElement().border(); |
473 | layOutAxis(m_rows, frameSetElement().rowLengths(), height() - (rows - 1) * borderThickness); |
474 | layOutAxis(m_cols, frameSetElement().colLengths(), width() - (cols - 1) * borderThickness); |
475 | |
476 | if (flattenFrameSet()) |
477 | positionFramesWithFlattening(); |
478 | else |
479 | positionFrames(); |
480 | |
481 | RenderBox::layout(); |
482 | |
483 | computeEdgeInfo(); |
484 | |
485 | updateLayerTransform(); |
486 | |
487 | if (doFullRepaint) { |
488 | repaintUsingContainer(repaintContainer, snappedIntRect(oldBounds)); |
489 | LayoutRect newBounds = clippedOverflowRectForRepaint(repaintContainer); |
490 | if (newBounds != oldBounds) |
491 | repaintUsingContainer(repaintContainer, snappedIntRect(newBounds)); |
492 | } |
493 | |
494 | clearNeedsLayout(); |
495 | } |
496 | |
497 | static void resetFrameRendererAndDescendents(RenderBox* frameSetChild, RenderFrameSet& parentFrameSet) |
498 | { |
499 | if (!frameSetChild) |
500 | return; |
501 | |
502 | for (auto* descendant = frameSetChild; descendant; descendant = downcast<RenderBox>(RenderObjectTraversal::next(*descendant, &parentFrameSet))) { |
503 | descendant->setWidth(0); |
504 | descendant->setHeight(0); |
505 | descendant->clearNeedsLayout(); |
506 | } |
507 | } |
508 | |
509 | void RenderFrameSet::positionFrames() |
510 | { |
511 | RenderBox* child = firstChildBox(); |
512 | if (!child) |
513 | return; |
514 | |
515 | int rows = frameSetElement().totalRows(); |
516 | int cols = frameSetElement().totalCols(); |
517 | |
518 | int yPos = 0; |
519 | int borderThickness = frameSetElement().border(); |
520 | for (int r = 0; r < rows; r++) { |
521 | int xPos = 0; |
522 | int height = m_rows.m_sizes[r]; |
523 | for (int c = 0; c < cols; c++) { |
524 | child->setLocation(IntPoint(xPos, yPos)); |
525 | int width = m_cols.m_sizes[c]; |
526 | |
527 | // has to be resized and itself resize its contents |
528 | if (width != child->width() || height != child->height()) { |
529 | child->setWidth(width); |
530 | child->setHeight(height); |
531 | #if PLATFORM(IOS_FAMILY) |
532 | // FIXME: Is this iOS-specific? |
533 | child->setNeedsLayout(MarkOnlyThis); |
534 | #else |
535 | child->setNeedsLayout(); |
536 | #endif |
537 | child->layout(); |
538 | } |
539 | |
540 | xPos += width + borderThickness; |
541 | |
542 | child = child->nextSiblingBox(); |
543 | if (!child) |
544 | return; |
545 | } |
546 | yPos += height + borderThickness; |
547 | } |
548 | |
549 | resetFrameRendererAndDescendents(child, *this); |
550 | } |
551 | |
552 | void RenderFrameSet::positionFramesWithFlattening() |
553 | { |
554 | RenderBox* child = firstChildBox(); |
555 | if (!child) |
556 | return; |
557 | |
558 | int rows = frameSetElement().totalRows(); |
559 | int cols = frameSetElement().totalCols(); |
560 | |
561 | int borderThickness = frameSetElement().border(); |
562 | bool repaintNeeded = false; |
563 | |
564 | // calculate frameset height based on actual content height to eliminate scrolling |
565 | bool out = false; |
566 | for (int r = 0; r < rows && !out; ++r) { |
567 | int = 0; |
568 | int height = m_rows.m_sizes[r]; |
569 | |
570 | for (int c = 0; c < cols; ++c) { |
571 | IntRect oldFrameRect = snappedIntRect(child->frameRect()); |
572 | |
573 | int width = m_cols.m_sizes[c]; |
574 | |
575 | bool fixedWidth = frameSetElement().colLengths() && frameSetElement().colLengths()[c].isFixed(); |
576 | bool fixedHeight = frameSetElement().rowLengths() && frameSetElement().rowLengths()[r].isFixed(); |
577 | |
578 | // has to be resized and itself resize its contents |
579 | if (!fixedWidth) |
580 | child->setWidth(width ? width + extra / (cols - c) : 0); |
581 | else |
582 | child->setWidth(width); |
583 | child->setHeight(height); |
584 | |
585 | child->setNeedsLayout(); |
586 | |
587 | if (is<RenderFrameSet>(*child)) |
588 | downcast<RenderFrameSet>(*child).layout(); |
589 | else |
590 | downcast<RenderFrame>(*child).layoutWithFlattening(fixedWidth, fixedHeight); |
591 | |
592 | if (child->height() > m_rows.m_sizes[r]) |
593 | m_rows.m_sizes[r] = child->height(); |
594 | if (child->width() > m_cols.m_sizes[c]) |
595 | m_cols.m_sizes[c] = child->width(); |
596 | |
597 | if (child->frameRect() != oldFrameRect) |
598 | repaintNeeded = true; |
599 | |
600 | // difference between calculated frame width and the width it actually decides to have |
601 | extra += width - m_cols.m_sizes[c]; |
602 | |
603 | child = child->nextSiblingBox(); |
604 | if (!child) { |
605 | out = true; |
606 | break; |
607 | } |
608 | } |
609 | } |
610 | |
611 | int xPos = 0; |
612 | int yPos = 0; |
613 | out = false; |
614 | child = firstChildBox(); |
615 | for (int r = 0; r < rows && !out; ++r) { |
616 | xPos = 0; |
617 | for (int c = 0; c < cols; ++c) { |
618 | // ensure the rows and columns are filled |
619 | IntRect oldRect = snappedIntRect(child->frameRect()); |
620 | |
621 | child->setLocation(IntPoint(xPos, yPos)); |
622 | child->setHeight(m_rows.m_sizes[r]); |
623 | child->setWidth(m_cols.m_sizes[c]); |
624 | |
625 | if (child->frameRect() != oldRect) { |
626 | repaintNeeded = true; |
627 | |
628 | // update to final size |
629 | child->setNeedsLayout(); |
630 | if (is<RenderFrameSet>(*child)) |
631 | downcast<RenderFrameSet>(*child).layout(); |
632 | else |
633 | downcast<RenderFrame>(*child).layoutWithFlattening(true, true); |
634 | } |
635 | |
636 | xPos += m_cols.m_sizes[c] + borderThickness; |
637 | child = child->nextSiblingBox(); |
638 | if (!child) { |
639 | out = true; |
640 | break; |
641 | } |
642 | } |
643 | yPos += m_rows.m_sizes[r] + borderThickness; |
644 | } |
645 | |
646 | setWidth(xPos - borderThickness); |
647 | setHeight(yPos - borderThickness); |
648 | |
649 | if (repaintNeeded) |
650 | repaint(); |
651 | |
652 | resetFrameRendererAndDescendents(child, *this); |
653 | } |
654 | |
655 | bool RenderFrameSet::flattenFrameSet() const |
656 | { |
657 | return view().frameView().effectiveFrameFlattening() != FrameFlattening::Disabled; |
658 | } |
659 | |
660 | void RenderFrameSet::startResizing(GridAxis& axis, int position) |
661 | { |
662 | int split = hitTestSplit(axis, position); |
663 | if (split == noSplit || axis.m_preventResize[split]) { |
664 | axis.m_splitBeingResized = noSplit; |
665 | return; |
666 | } |
667 | axis.m_splitBeingResized = split; |
668 | axis.m_splitResizeOffset = position - splitPosition(axis, split); |
669 | } |
670 | |
671 | void RenderFrameSet::continueResizing(GridAxis& axis, int position) |
672 | { |
673 | if (needsLayout()) |
674 | return; |
675 | if (axis.m_splitBeingResized == noSplit) |
676 | return; |
677 | int currentSplitPosition = splitPosition(axis, axis.m_splitBeingResized); |
678 | int delta = (position - currentSplitPosition) - axis.m_splitResizeOffset; |
679 | if (!delta) |
680 | return; |
681 | axis.m_deltas[axis.m_splitBeingResized - 1] += delta; |
682 | axis.m_deltas[axis.m_splitBeingResized] -= delta; |
683 | setNeedsLayout(); |
684 | } |
685 | |
686 | bool RenderFrameSet::userResize(MouseEvent& event) |
687 | { |
688 | if (flattenFrameSet()) |
689 | return false; |
690 | |
691 | if (!m_isResizing) { |
692 | if (needsLayout()) |
693 | return false; |
694 | if (event.type() == eventNames().mousedownEvent && event.button() == LeftButton) { |
695 | FloatPoint localPos = absoluteToLocal(event.absoluteLocation(), UseTransforms); |
696 | startResizing(m_cols, localPos.x()); |
697 | startResizing(m_rows, localPos.y()); |
698 | if (m_cols.m_splitBeingResized != noSplit || m_rows.m_splitBeingResized != noSplit) { |
699 | setIsResizing(true); |
700 | return true; |
701 | } |
702 | } |
703 | } else { |
704 | if (event.type() == eventNames().mousemoveEvent || (event.type() == eventNames().mouseupEvent && event.button() == LeftButton)) { |
705 | FloatPoint localPos = absoluteToLocal(event.absoluteLocation(), UseTransforms); |
706 | continueResizing(m_cols, localPos.x()); |
707 | continueResizing(m_rows, localPos.y()); |
708 | if (event.type() == eventNames().mouseupEvent && event.button() == LeftButton) { |
709 | setIsResizing(false); |
710 | return true; |
711 | } |
712 | } |
713 | } |
714 | |
715 | return false; |
716 | } |
717 | |
718 | void RenderFrameSet::setIsResizing(bool isResizing) |
719 | { |
720 | m_isResizing = isResizing; |
721 | for (auto& ancestor : ancestorsOfType<RenderFrameSet>(*this)) |
722 | ancestor.m_isChildResizing = isResizing; |
723 | frame().eventHandler().setResizingFrameSet(isResizing ? &frameSetElement() : nullptr); |
724 | } |
725 | |
726 | bool RenderFrameSet::isResizingRow() const |
727 | { |
728 | return m_isResizing && m_rows.m_splitBeingResized != noSplit; |
729 | } |
730 | |
731 | bool RenderFrameSet::isResizingColumn() const |
732 | { |
733 | return m_isResizing && m_cols.m_splitBeingResized != noSplit; |
734 | } |
735 | |
736 | bool RenderFrameSet::canResizeRow(const IntPoint& p) const |
737 | { |
738 | int r = hitTestSplit(m_rows, p.y()); |
739 | return r != noSplit && !m_rows.m_preventResize[r]; |
740 | } |
741 | |
742 | bool RenderFrameSet::canResizeColumn(const IntPoint& p) const |
743 | { |
744 | int c = hitTestSplit(m_cols, p.x()); |
745 | return c != noSplit && !m_cols.m_preventResize[c]; |
746 | } |
747 | |
748 | int RenderFrameSet::splitPosition(const GridAxis& axis, int split) const |
749 | { |
750 | if (needsLayout()) |
751 | return 0; |
752 | |
753 | int borderThickness = frameSetElement().border(); |
754 | |
755 | int size = axis.m_sizes.size(); |
756 | if (!size) |
757 | return 0; |
758 | |
759 | int position = 0; |
760 | for (int i = 0; i < split && i < size; ++i) |
761 | position += axis.m_sizes[i] + borderThickness; |
762 | return position - borderThickness; |
763 | } |
764 | |
765 | int RenderFrameSet::hitTestSplit(const GridAxis& axis, int position) const |
766 | { |
767 | if (needsLayout()) |
768 | return noSplit; |
769 | |
770 | int borderThickness = frameSetElement().border(); |
771 | if (borderThickness <= 0) |
772 | return noSplit; |
773 | |
774 | size_t size = axis.m_sizes.size(); |
775 | if (!size) |
776 | return noSplit; |
777 | |
778 | int splitPosition = axis.m_sizes[0]; |
779 | for (size_t i = 1; i < size; ++i) { |
780 | if (position >= splitPosition && position < splitPosition + borderThickness) |
781 | return i; |
782 | splitPosition += borderThickness + axis.m_sizes[i]; |
783 | } |
784 | return noSplit; |
785 | } |
786 | |
787 | bool RenderFrameSet::isChildAllowed(const RenderObject& child, const RenderStyle&) const |
788 | { |
789 | return child.isFrame() || child.isFrameSet(); |
790 | } |
791 | |
792 | CursorDirective RenderFrameSet::getCursor(const LayoutPoint& point, Cursor& cursor) const |
793 | { |
794 | IntPoint roundedPoint = roundedIntPoint(point); |
795 | if (canResizeRow(roundedPoint)) { |
796 | cursor = rowResizeCursor(); |
797 | return SetCursor; |
798 | } |
799 | if (canResizeColumn(roundedPoint)) { |
800 | cursor = columnResizeCursor(); |
801 | return SetCursor; |
802 | } |
803 | return RenderBox::getCursor(point, cursor); |
804 | } |
805 | |
806 | } // namespace WebCore |
807 | |