1 /*
2 * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following
13 * disclaimer in the documentation and/or other materials
14 * provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
26 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30 #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h"
31
32 #include <memory>
33
34 #include "base/auto_reset.h"
35 #include "third_party/blink/renderer/core/frame/web_feature.h"
36 #include "third_party/blink/renderer/core/inspector/console_message.h"
37 #include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h"
38 #include "third_party/blink/renderer/core/layout/floating_objects.h"
39 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
40 #include "third_party/blink/renderer/core/layout/layout_box.h"
41 #include "third_party/blink/renderer/core/layout/layout_image.h"
42 #include "third_party/blink/renderer/core/paint/rounded_border_geometry.h"
43 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
44 #include "third_party/blink/renderer/platform/heap/heap.h"
45 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
46
47 namespace blink {
48
ReferenceBox(const ShapeValue & shape_value)49 CSSBoxType ReferenceBox(const ShapeValue& shape_value) {
50 if (shape_value.CssBox() == CSSBoxType::kMissing)
51 return CSSBoxType::kMargin;
52 return shape_value.CssBox();
53 }
54
SetReferenceBoxLogicalSize(LayoutSize new_reference_box_logical_size)55 void ShapeOutsideInfo::SetReferenceBoxLogicalSize(
56 LayoutSize new_reference_box_logical_size) {
57 Document& document = layout_box_->GetDocument();
58 bool is_horizontal_writing_mode =
59 layout_box_->ContainingBlock()->StyleRef().IsHorizontalWritingMode();
60
61 LayoutSize margin_box_for_use_counter = new_reference_box_logical_size;
62 if (is_horizontal_writing_mode) {
63 margin_box_for_use_counter.Expand(layout_box_->MarginWidth(),
64 layout_box_->MarginHeight());
65 } else {
66 margin_box_for_use_counter.Expand(layout_box_->MarginHeight(),
67 layout_box_->MarginWidth());
68 }
69
70 const ShapeValue& shape_value = *layout_box_->StyleRef().ShapeOutside();
71 switch (ReferenceBox(shape_value)) {
72 case CSSBoxType::kMargin:
73 UseCounter::Count(document, WebFeature::kShapeOutsideMarginBox);
74 if (is_horizontal_writing_mode) {
75 new_reference_box_logical_size.Expand(layout_box_->MarginWidth(),
76 layout_box_->MarginHeight());
77 } else {
78 new_reference_box_logical_size.Expand(layout_box_->MarginHeight(),
79 layout_box_->MarginWidth());
80 }
81 break;
82 case CSSBoxType::kBorder:
83 UseCounter::Count(document, WebFeature::kShapeOutsideBorderBox);
84 break;
85 case CSSBoxType::kPadding:
86 UseCounter::Count(document, WebFeature::kShapeOutsidePaddingBox);
87 if (is_horizontal_writing_mode) {
88 new_reference_box_logical_size.Shrink(layout_box_->BorderWidth(),
89 layout_box_->BorderHeight());
90 } else {
91 new_reference_box_logical_size.Shrink(layout_box_->BorderHeight(),
92 layout_box_->BorderWidth());
93 }
94
95 if (new_reference_box_logical_size != margin_box_for_use_counter) {
96 UseCounter::Count(
97 document,
98 WebFeature::kShapeOutsidePaddingBoxDifferentFromMarginBox);
99 }
100 break;
101 case CSSBoxType::kContent: {
102 bool is_shape_image = shape_value.GetType() == ShapeValue::kImage;
103
104 if (!is_shape_image)
105 UseCounter::Count(document, WebFeature::kShapeOutsideContentBox);
106
107 if (is_horizontal_writing_mode) {
108 new_reference_box_logical_size.Shrink(
109 layout_box_->BorderAndPaddingWidth(),
110 layout_box_->BorderAndPaddingHeight());
111 } else {
112 new_reference_box_logical_size.Shrink(
113 layout_box_->BorderAndPaddingHeight(),
114 layout_box_->BorderAndPaddingWidth());
115 }
116
117 if (!is_shape_image &&
118 new_reference_box_logical_size != margin_box_for_use_counter) {
119 UseCounter::Count(
120 document,
121 WebFeature::kShapeOutsideContentBoxDifferentFromMarginBox);
122 }
123 break;
124 }
125 case CSSBoxType::kMissing:
126 NOTREACHED();
127 break;
128 }
129
130 new_reference_box_logical_size.ClampNegativeToZero();
131
132 if (reference_box_logical_size_ == new_reference_box_logical_size)
133 return;
134 MarkShapeAsDirty();
135 reference_box_logical_size_ = new_reference_box_logical_size;
136 }
137
SetPercentageResolutionInlineSize(LayoutUnit percentage_resolution_inline_size)138 void ShapeOutsideInfo::SetPercentageResolutionInlineSize(
139 LayoutUnit percentage_resolution_inline_size) {
140 DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled());
141
142 if (percentage_resolution_inline_size_ == percentage_resolution_inline_size)
143 return;
144
145 MarkShapeAsDirty();
146 percentage_resolution_inline_size_ = percentage_resolution_inline_size;
147 }
148
CheckShapeImageOrigin(Document & document,const StyleImage & style_image)149 static bool CheckShapeImageOrigin(Document& document,
150 const StyleImage& style_image) {
151 if (style_image.IsGeneratedImage())
152 return true;
153
154 DCHECK(style_image.CachedImage());
155 ImageResourceContent& image_content = *(style_image.CachedImage());
156 if (image_content.IsAccessAllowed())
157 return true;
158
159 const KURL& url = image_content.Url();
160 String url_string = url.IsNull() ? "''" : url.ElidedString();
161 document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
162 mojom::ConsoleMessageSource::kSecurity,
163 mojom::ConsoleMessageLevel::kError,
164 "Unsafe attempt to load URL " + url_string + "."));
165
166 return false;
167 }
168
GetShapeImageMarginRect(const LayoutBox & layout_box,const LayoutSize & reference_box_logical_size)169 static LayoutRect GetShapeImageMarginRect(
170 const LayoutBox& layout_box,
171 const LayoutSize& reference_box_logical_size) {
172 LayoutPoint margin_box_origin(
173 -layout_box.MarginLineLeft() - layout_box.BorderAndPaddingLogicalLeft(),
174 -layout_box.MarginBefore() - layout_box.BorderBefore() -
175 layout_box.PaddingBefore());
176 LayoutSize margin_box_size_delta(
177 layout_box.MarginLogicalWidth() +
178 layout_box.BorderAndPaddingLogicalWidth(),
179 layout_box.MarginLogicalHeight() +
180 layout_box.BorderAndPaddingLogicalHeight());
181 LayoutSize margin_rect_size(reference_box_logical_size +
182 margin_box_size_delta);
183 margin_rect_size.ClampNegativeToZero();
184 return LayoutRect(margin_box_origin, margin_rect_size);
185 }
186
CreateShapeForImage(StyleImage * style_image,float shape_image_threshold,WritingMode writing_mode,float margin) const187 std::unique_ptr<Shape> ShapeOutsideInfo::CreateShapeForImage(
188 StyleImage* style_image,
189 float shape_image_threshold,
190 WritingMode writing_mode,
191 float margin) const {
192 DCHECK(!style_image->IsPendingImage());
193 const LayoutSize& image_size = RoundedLayoutSize(style_image->ImageSize(
194 layout_box_->GetDocument(), layout_box_->StyleRef().EffectiveZoom(),
195 FloatSize(reference_box_logical_size_),
196 LayoutObject::ShouldRespectImageOrientation(layout_box_)));
197
198 const LayoutRect& margin_rect =
199 GetShapeImageMarginRect(*layout_box_, reference_box_logical_size_);
200 const LayoutRect& image_rect =
201 (layout_box_->IsLayoutImage())
202 ? To<LayoutImage>(layout_box_)->ReplacedContentRect().ToLayoutRect()
203 : LayoutRect(LayoutPoint(), image_size);
204
205 scoped_refptr<Image> image =
206 style_image->GetImage(*layout_box_, layout_box_->GetDocument(),
207 layout_box_->StyleRef(), FloatSize(image_size));
208
209 return Shape::CreateRasterShape(
210 image.get(), shape_image_threshold, image_rect, margin_rect, writing_mode,
211 margin, LayoutObject::ShouldRespectImageOrientation(layout_box_));
212 }
213
ComputedShape() const214 const Shape& ShapeOutsideInfo::ComputedShape() const {
215 if (Shape* shape = shape_.get())
216 return *shape;
217
218 base::AutoReset<bool> is_in_computing_shape(&is_computing_shape_, true);
219
220 const ComputedStyle& style = *layout_box_->Style();
221 DCHECK(layout_box_->ContainingBlock());
222 const LayoutBlock& containing_block = *layout_box_->ContainingBlock();
223 const ComputedStyle& containing_block_style = containing_block.StyleRef();
224
225 WritingMode writing_mode = containing_block_style.GetWritingMode();
226 // Make sure contentWidth is not negative. This can happen when containing
227 // block has a vertical scrollbar and its content is smaller than the
228 // scrollbar width.
229 LayoutUnit percentage_resolution_inline_size =
230 containing_block.IsLayoutNGMixin()
231 ? percentage_resolution_inline_size_
232 : std::max(LayoutUnit(), containing_block.ContentWidth());
233
234 float margin =
235 FloatValueForLength(layout_box_->StyleRef().ShapeMargin(),
236 percentage_resolution_inline_size.ToFloat());
237
238 float shape_image_threshold = style.ShapeImageThreshold();
239 DCHECK(style.ShapeOutside());
240 const ShapeValue& shape_value = *style.ShapeOutside();
241
242 switch (shape_value.GetType()) {
243 case ShapeValue::kShape:
244 DCHECK(shape_value.Shape());
245 shape_ =
246 Shape::CreateShape(shape_value.Shape(), reference_box_logical_size_,
247 writing_mode, margin);
248 break;
249 case ShapeValue::kImage:
250 DCHECK(shape_value.GetImage());
251 DCHECK(shape_value.GetImage()->CanRender());
252 shape_ = CreateShapeForImage(shape_value.GetImage(),
253 shape_image_threshold, writing_mode, margin);
254 break;
255 case ShapeValue::kBox: {
256 // TODO(layout-dev): It seems incorrect to pass logical size to
257 // RoundedBorderGeometry().
258 const FloatRoundedRect& shape_rect = RoundedBorderGeometry::RoundedBorder(
259 style, PhysicalRect(PhysicalOffset(), reference_box_logical_size_));
260 shape_ = Shape::CreateLayoutBoxShape(shape_rect, writing_mode, margin);
261 break;
262 }
263 }
264
265 DCHECK(shape_);
266 return *shape_;
267 }
268
BorderBeforeInWritingMode(const LayoutBox & layout_box,WritingMode writing_mode)269 inline LayoutUnit BorderBeforeInWritingMode(const LayoutBox& layout_box,
270 WritingMode writing_mode) {
271 switch (writing_mode) {
272 case WritingMode::kHorizontalTb:
273 return LayoutUnit(layout_box.BorderTop());
274 case WritingMode::kVerticalLr:
275 return LayoutUnit(layout_box.BorderLeft());
276 case WritingMode::kVerticalRl:
277 return LayoutUnit(layout_box.BorderRight());
278 // TODO(layout-dev): Sideways-lr and sideways-rl are not yet supported.
279 default:
280 break;
281 }
282
283 NOTREACHED();
284 return LayoutUnit(layout_box.BorderBefore());
285 }
286
BorderAndPaddingBeforeInWritingMode(const LayoutBox & layout_box,WritingMode writing_mode)287 inline LayoutUnit BorderAndPaddingBeforeInWritingMode(
288 const LayoutBox& layout_box,
289 WritingMode writing_mode) {
290 switch (writing_mode) {
291 case WritingMode::kHorizontalTb:
292 return layout_box.BorderTop() + layout_box.PaddingTop();
293 case WritingMode::kVerticalLr:
294 return layout_box.BorderLeft() + layout_box.PaddingLeft();
295 case WritingMode::kVerticalRl:
296 return layout_box.BorderRight() + layout_box.PaddingRight();
297 // TODO(layout-dev): Sideways-lr and sideways-rl are not yet supported.
298 default:
299 break;
300 }
301
302 NOTREACHED();
303 return layout_box.BorderAndPaddingBefore();
304 }
305
LogicalTopOffset() const306 LayoutUnit ShapeOutsideInfo::LogicalTopOffset() const {
307 switch (ReferenceBox(*layout_box_->StyleRef().ShapeOutside())) {
308 case CSSBoxType::kMargin:
309 return -layout_box_->MarginBefore(
310 layout_box_->ContainingBlock()->Style());
311 case CSSBoxType::kBorder:
312 return LayoutUnit();
313 case CSSBoxType::kPadding:
314 return BorderBeforeInWritingMode(
315 *layout_box_,
316 layout_box_->ContainingBlock()->StyleRef().GetWritingMode());
317 case CSSBoxType::kContent:
318 return BorderAndPaddingBeforeInWritingMode(
319 *layout_box_,
320 layout_box_->ContainingBlock()->StyleRef().GetWritingMode());
321 case CSSBoxType::kMissing:
322 break;
323 }
324
325 NOTREACHED();
326 return LayoutUnit();
327 }
328
BorderStartWithStyleForWritingMode(const LayoutBox & layout_box,const ComputedStyle * style)329 inline LayoutUnit BorderStartWithStyleForWritingMode(
330 const LayoutBox& layout_box,
331 const ComputedStyle* style) {
332 if (style->IsHorizontalWritingMode()) {
333 if (style->IsLeftToRightDirection())
334 return LayoutUnit(layout_box.BorderLeft());
335
336 return LayoutUnit(layout_box.BorderRight());
337 }
338 if (style->IsLeftToRightDirection())
339 return LayoutUnit(layout_box.BorderTop());
340
341 return LayoutUnit(layout_box.BorderBottom());
342 }
343
BorderAndPaddingStartWithStyleForWritingMode(const LayoutBox & layout_box,const ComputedStyle * style)344 inline LayoutUnit BorderAndPaddingStartWithStyleForWritingMode(
345 const LayoutBox& layout_box,
346 const ComputedStyle* style) {
347 if (style->IsHorizontalWritingMode()) {
348 if (style->IsLeftToRightDirection())
349 return layout_box.BorderLeft() + layout_box.PaddingLeft();
350
351 return layout_box.BorderRight() + layout_box.PaddingRight();
352 }
353 if (style->IsLeftToRightDirection())
354 return layout_box.BorderTop() + layout_box.PaddingTop();
355
356 return layout_box.BorderBottom() + layout_box.PaddingBottom();
357 }
358
LogicalLeftOffset() const359 LayoutUnit ShapeOutsideInfo::LogicalLeftOffset() const {
360 switch (ReferenceBox(*layout_box_->StyleRef().ShapeOutside())) {
361 case CSSBoxType::kMargin:
362 return -layout_box_->MarginStart(layout_box_->ContainingBlock()->Style());
363 case CSSBoxType::kBorder:
364 return LayoutUnit();
365 case CSSBoxType::kPadding:
366 return BorderStartWithStyleForWritingMode(
367 *layout_box_, layout_box_->ContainingBlock()->Style());
368 case CSSBoxType::kContent:
369 return BorderAndPaddingStartWithStyleForWritingMode(
370 *layout_box_, layout_box_->ContainingBlock()->Style());
371 case CSSBoxType::kMissing:
372 break;
373 }
374
375 NOTREACHED();
376 return LayoutUnit();
377 }
378
IsEnabledFor(const LayoutBox & box)379 bool ShapeOutsideInfo::IsEnabledFor(const LayoutBox& box) {
380 ShapeValue* shape_value = box.StyleRef().ShapeOutside();
381 if (!box.IsFloating() || !shape_value)
382 return false;
383
384 switch (shape_value->GetType()) {
385 case ShapeValue::kShape:
386 return shape_value->Shape();
387 case ShapeValue::kImage: {
388 StyleImage* image = shape_value->GetImage();
389 DCHECK(image);
390 return image->CanRender() &&
391 CheckShapeImageOrigin(box.GetDocument(), *image);
392 }
393 case ShapeValue::kBox:
394 return true;
395 }
396
397 return false;
398 }
399
ComputeDeltasForContainingBlockLine(const LineLayoutBlockFlow & containing_block,const FloatingObject & floating_object,LayoutUnit line_top,LayoutUnit line_height)400 ShapeOutsideDeltas ShapeOutsideInfo::ComputeDeltasForContainingBlockLine(
401 const LineLayoutBlockFlow& containing_block,
402 const FloatingObject& floating_object,
403 LayoutUnit line_top,
404 LayoutUnit line_height) {
405 DCHECK_GE(line_height, 0);
406
407 LayoutUnit border_box_top =
408 containing_block.LogicalTopForFloat(floating_object) +
409 containing_block.MarginBeforeForChild(*layout_box_);
410 LayoutUnit border_box_line_top = line_top - border_box_top;
411
412 if (IsShapeDirty() ||
413 !shape_outside_deltas_.IsForLine(border_box_line_top, line_height)) {
414 LayoutUnit reference_box_line_top =
415 border_box_line_top - LogicalTopOffset();
416 LayoutUnit float_margin_box_width = std::max(
417 containing_block.LogicalWidthForFloat(floating_object), LayoutUnit());
418
419 if (ComputedShape().LineOverlapsShapeMarginBounds(reference_box_line_top,
420 line_height)) {
421 LineSegment segment = ComputedShape().GetExcludedInterval(
422 (border_box_line_top - LogicalTopOffset()),
423 std::min(line_height, ShapeLogicalBottom() - border_box_line_top));
424 if (segment.is_valid) {
425 LayoutUnit logical_left_margin =
426 containing_block.StyleRef().IsLeftToRightDirection()
427 ? containing_block.MarginStartForChild(*layout_box_)
428 : containing_block.MarginEndForChild(*layout_box_);
429 LayoutUnit raw_left_margin_box_delta =
430 segment.logical_left + LogicalLeftOffset() + logical_left_margin;
431 LayoutUnit left_margin_box_delta = clampTo<LayoutUnit>(
432 raw_left_margin_box_delta, LayoutUnit(), float_margin_box_width);
433
434 LayoutUnit logical_right_margin =
435 containing_block.StyleRef().IsLeftToRightDirection()
436 ? containing_block.MarginEndForChild(*layout_box_)
437 : containing_block.MarginStartForChild(*layout_box_);
438 LayoutUnit raw_right_margin_box_delta =
439 segment.logical_right + LogicalLeftOffset() -
440 containing_block.LogicalWidthForChild(*layout_box_) -
441 logical_right_margin;
442 LayoutUnit right_margin_box_delta = clampTo<LayoutUnit>(
443 raw_right_margin_box_delta, -float_margin_box_width, LayoutUnit());
444
445 shape_outside_deltas_ =
446 ShapeOutsideDeltas(left_margin_box_delta, right_margin_box_delta,
447 true, border_box_line_top, line_height);
448 return shape_outside_deltas_;
449 }
450 }
451
452 // Lines that do not overlap the shape should act as if the float
453 // wasn't there for layout purposes. So we set the deltas to remove the
454 // entire width of the float.
455 shape_outside_deltas_ =
456 ShapeOutsideDeltas(float_margin_box_width, -float_margin_box_width,
457 false, border_box_line_top, line_height);
458 }
459
460 return shape_outside_deltas_;
461 }
462
ComputedShapePhysicalBoundingBox() const463 PhysicalRect ShapeOutsideInfo::ComputedShapePhysicalBoundingBox() const {
464 LayoutRect physical_bounding_box =
465 ComputedShape().ShapeMarginLogicalBoundingBox();
466 physical_bounding_box.SetX(physical_bounding_box.X() + LogicalLeftOffset());
467
468 if (layout_box_->StyleRef().IsFlippedBlocksWritingMode())
469 physical_bounding_box.SetY(layout_box_->LogicalHeight() -
470 physical_bounding_box.MaxY());
471 else
472 physical_bounding_box.SetY(physical_bounding_box.Y() + LogicalTopOffset());
473
474 if (!layout_box_->StyleRef().IsHorizontalWritingMode())
475 physical_bounding_box = physical_bounding_box.TransposedRect();
476 else
477 physical_bounding_box.SetY(physical_bounding_box.Y() + LogicalTopOffset());
478
479 return PhysicalRect(physical_bounding_box);
480 }
481
ShapeToLayoutObjectPoint(FloatPoint point) const482 FloatPoint ShapeOutsideInfo::ShapeToLayoutObjectPoint(FloatPoint point) const {
483 FloatPoint result = FloatPoint(point.X() + LogicalLeftOffset(),
484 point.Y() + LogicalTopOffset());
485 if (layout_box_->StyleRef().IsFlippedBlocksWritingMode())
486 result.SetY(layout_box_->LogicalHeight() - result.Y());
487 if (!layout_box_->StyleRef().IsHorizontalWritingMode())
488 result = result.TransposedPoint();
489 return result;
490 }
491
492 } // namespace blink
493