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