1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h"
6
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/renderer/core/layout/layout_inline.h"
9 #include "third_party/blink/renderer/core/layout/ng/inline/layout_ng_text.h"
10 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
11 #include "third_party/blink/renderer/core/layout/ng/ng_layout_test.h"
12 #include "third_party/blink/renderer/core/style/computed_style.h"
13
14 namespace blink {
15
16 // The spec turned into a discussion that may change. Put this logic on hold
17 // until CSSWG resolves the issue.
18 // https://github.com/w3c/csswg-drafts/issues/337
19 #define SEGMENT_BREAK_TRANSFORMATION_FOR_EAST_ASIAN_WIDTH 0
20
21 #define EXPECT_ITEM_OFFSET(item, type, start, end) \
22 EXPECT_EQ(type, (item).Type()); \
23 EXPECT_EQ(start, (item).StartOffset()); \
24 EXPECT_EQ(end, (item).EndOffset());
25
26 class NGInlineItemsBuilderTest : public NGLayoutTest {
27 protected:
SetUp()28 void SetUp() override {
29 NGLayoutTest::SetUp();
30 style_ = ComputedStyle::Create();
31 }
32
TearDown()33 void TearDown() override {
34 for (LayoutObject* anonymous_object : anonymous_objects_)
35 anonymous_object->Destroy();
36 NGLayoutTest::TearDown();
37 }
38
SetWhiteSpace(EWhiteSpace whitespace)39 void SetWhiteSpace(EWhiteSpace whitespace) {
40 style_->SetWhiteSpace(whitespace);
41 }
42
GetStyle(EWhiteSpace whitespace)43 scoped_refptr<ComputedStyle> GetStyle(EWhiteSpace whitespace) {
44 if (whitespace == EWhiteSpace::kNormal)
45 return style_;
46 scoped_refptr<ComputedStyle> style(ComputedStyle::Create());
47 style->SetWhiteSpace(whitespace);
48 return style;
49 }
50
AppendText(const String & text,NGInlineItemsBuilder * builder)51 void AppendText(const String& text, NGInlineItemsBuilder* builder) {
52 LayoutText* layout_text = LayoutText::CreateEmptyAnonymous(
53 GetDocument(), style_.get(), LegacyLayout::kAuto);
54 anonymous_objects_.push_back(layout_text);
55 builder->AppendText(text, layout_text);
56 }
57
AppendAtomicInline(NGInlineItemsBuilder * builder)58 void AppendAtomicInline(NGInlineItemsBuilder* builder) {
59 LayoutBlockFlow* layout_block_flow = LayoutBlockFlow::CreateAnonymous(
60 &GetDocument(), style_, LegacyLayout::kAuto);
61 anonymous_objects_.push_back(layout_block_flow);
62 builder->AppendAtomicInline(layout_block_flow);
63 }
64
65 struct Input {
66 const String text;
67 EWhiteSpace whitespace = EWhiteSpace::kNormal;
68 LayoutText* layout_text = nullptr;
69 };
70
TestAppend(Vector<Input> inputs)71 const String& TestAppend(Vector<Input> inputs) {
72 items_.clear();
73 Vector<LayoutText*> anonymous_objects;
74 NGInlineItemsBuilder builder(&items_);
75 for (Input& input : inputs) {
76 if (!input.layout_text) {
77 input.layout_text = LayoutText::CreateEmptyAnonymous(
78 GetDocument(), GetStyle(input.whitespace), LegacyLayout::kAuto);
79 anonymous_objects.push_back(input.layout_text);
80 }
81 builder.AppendText(input.text, input.layout_text);
82 }
83 builder.ExitBlock();
84 text_ = builder.ToString();
85 ValidateItems();
86 CheckReuseItemsProducesSameResult(inputs, builder.HasBidiControls());
87 for (LayoutObject* anonymous_object : anonymous_objects)
88 anonymous_object->Destroy();
89 return text_;
90 }
91
TestAppend(const String & input)92 const String& TestAppend(const String& input) {
93 return TestAppend({Input{input}});
94 }
TestAppend(const Input & input1,const Input & input2)95 const String& TestAppend(const Input& input1, const Input& input2) {
96 return TestAppend({input1, input2});
97 }
TestAppend(const String & input1,const String & input2)98 const String& TestAppend(const String& input1, const String& input2) {
99 return TestAppend(Input{input1}, Input{input2});
100 }
TestAppend(const String & input1,const String & input2,const String & input3)101 const String& TestAppend(const String& input1,
102 const String& input2,
103 const String& input3) {
104 return TestAppend({{input1}, {input2}, {input3}});
105 }
106
ValidateItems()107 void ValidateItems() {
108 unsigned current_offset = 0;
109 for (unsigned i = 0; i < items_.size(); i++) {
110 const NGInlineItem& item = items_[i];
111 EXPECT_EQ(current_offset, item.StartOffset());
112 EXPECT_LE(item.StartOffset(), item.EndOffset());
113 current_offset = item.EndOffset();
114 }
115 EXPECT_EQ(current_offset, text_.length());
116 }
117
CheckReuseItemsProducesSameResult(Vector<Input> inputs,bool has_bidi_controls)118 void CheckReuseItemsProducesSameResult(Vector<Input> inputs,
119 bool has_bidi_controls) {
120 NGInlineNodeData fake_data;
121 fake_data.text_content = text_;
122 fake_data.is_bidi_enabled_ = has_bidi_controls;
123
124 Vector<NGInlineItem> reuse_items;
125 NGInlineItemsBuilder reuse_builder(&reuse_items);
126 for (Input& input : inputs) {
127 // Collect items for this LayoutObject.
128 DCHECK(input.layout_text);
129 for (NGInlineItem* item = items_.begin(); item != items_.end();) {
130 if (item->GetLayoutObject() == input.layout_text) {
131 NGInlineItem* begin = item;
132 for (++item; item != items_.end(); ++item) {
133 if (item->GetLayoutObject() != input.layout_text)
134 break;
135 }
136 input.layout_text->SetInlineItems(begin, item);
137 } else {
138 ++item;
139 }
140 }
141
142 // Try to re-use previous items, or Append if it was not re-usable.
143 bool reused =
144 input.layout_text->HasValidInlineItems() &&
145 reuse_builder.AppendTextReusing(fake_data, input.layout_text);
146 if (!reused) {
147 reuse_builder.AppendText(input.text, input.layout_text);
148 }
149 }
150
151 reuse_builder.ExitBlock();
152 String reuse_text = reuse_builder.ToString();
153 EXPECT_EQ(text_, reuse_text);
154 }
155
156 Vector<NGInlineItem> items_;
157 String text_;
158 scoped_refptr<ComputedStyle> style_;
159 Vector<LayoutObject*> anonymous_objects_;
160 };
161
162 #define TestWhitespaceValue(expected_text, input, whitespace) \
163 SetWhiteSpace(whitespace); \
164 EXPECT_EQ(expected_text, TestAppend(input)) << "white-space: " #whitespace;
165
TEST_F(NGInlineItemsBuilderTest,CollapseSpaces)166 TEST_F(NGInlineItemsBuilderTest, CollapseSpaces) {
167 String input("text text text text");
168 String collapsed("text text text text");
169 TestWhitespaceValue(collapsed, input, EWhiteSpace::kNormal);
170 TestWhitespaceValue(collapsed, input, EWhiteSpace::kNowrap);
171 TestWhitespaceValue(collapsed, input, EWhiteSpace::kWebkitNowrap);
172 TestWhitespaceValue(collapsed, input, EWhiteSpace::kPreLine);
173 TestWhitespaceValue(input, input, EWhiteSpace::kPre);
174 TestWhitespaceValue(input, input, EWhiteSpace::kPreWrap);
175 }
176
TEST_F(NGInlineItemsBuilderTest,CollapseTabs)177 TEST_F(NGInlineItemsBuilderTest, CollapseTabs) {
178 String input("text text text text");
179 String collapsed("text text text text");
180 TestWhitespaceValue(collapsed, input, EWhiteSpace::kNormal);
181 TestWhitespaceValue(collapsed, input, EWhiteSpace::kNowrap);
182 TestWhitespaceValue(collapsed, input, EWhiteSpace::kWebkitNowrap);
183 TestWhitespaceValue(collapsed, input, EWhiteSpace::kPreLine);
184 TestWhitespaceValue(input, input, EWhiteSpace::kPre);
185 TestWhitespaceValue(input, input, EWhiteSpace::kPreWrap);
186 }
187
TEST_F(NGInlineItemsBuilderTest,CollapseNewLines)188 TEST_F(NGInlineItemsBuilderTest, CollapseNewLines) {
189 String input("text\ntext \ntext\n\ntext");
190 String collapsed("text text text text");
191 TestWhitespaceValue(collapsed, input, EWhiteSpace::kNormal);
192 TestWhitespaceValue(collapsed, input, EWhiteSpace::kNowrap);
193 TestWhitespaceValue("text\ntext\ntext\n\ntext", input, EWhiteSpace::kPreLine);
194 TestWhitespaceValue(input, input, EWhiteSpace::kPre);
195 TestWhitespaceValue(input, input, EWhiteSpace::kPreWrap);
196 }
197
TEST_F(NGInlineItemsBuilderTest,CollapseNewlinesAsSpaces)198 TEST_F(NGInlineItemsBuilderTest, CollapseNewlinesAsSpaces) {
199 EXPECT_EQ("text text", TestAppend("text\ntext"));
200 EXPECT_EQ("text text", TestAppend("text\n\ntext"));
201 EXPECT_EQ("text text", TestAppend("text \n\n text"));
202 EXPECT_EQ("text text", TestAppend("text \n \n text"));
203 }
204
TEST_F(NGInlineItemsBuilderTest,CollapseAcrossElements)205 TEST_F(NGInlineItemsBuilderTest, CollapseAcrossElements) {
206 EXPECT_EQ("text text", TestAppend("text ", " text"))
207 << "Spaces are collapsed even when across elements.";
208 }
209
TEST_F(NGInlineItemsBuilderTest,CollapseLeadingSpaces)210 TEST_F(NGInlineItemsBuilderTest, CollapseLeadingSpaces) {
211 EXPECT_EQ("text", TestAppend(" text"));
212 EXPECT_EQ("text", TestAppend(" ", "text"));
213 EXPECT_EQ("text", TestAppend(" ", " text"));
214 }
215
TEST_F(NGInlineItemsBuilderTest,CollapseTrailingSpaces)216 TEST_F(NGInlineItemsBuilderTest, CollapseTrailingSpaces) {
217 EXPECT_EQ("text", TestAppend("text "));
218 EXPECT_EQ("text", TestAppend("text", " "));
219 EXPECT_EQ("text", TestAppend("text ", " "));
220 }
221
TEST_F(NGInlineItemsBuilderTest,CollapseAllSpaces)222 TEST_F(NGInlineItemsBuilderTest, CollapseAllSpaces) {
223 EXPECT_EQ("", TestAppend(" "));
224 EXPECT_EQ("", TestAppend(" ", " "));
225 EXPECT_EQ("", TestAppend(" ", "\n"));
226 EXPECT_EQ("", TestAppend("\n", " "));
227 }
228
TEST_F(NGInlineItemsBuilderTest,CollapseLeadingNewlines)229 TEST_F(NGInlineItemsBuilderTest, CollapseLeadingNewlines) {
230 EXPECT_EQ("text", TestAppend("\ntext"));
231 EXPECT_EQ("text", TestAppend("\n\ntext"));
232 EXPECT_EQ("text", TestAppend("\n", "text"));
233 EXPECT_EQ("text", TestAppend("\n\n", "text"));
234 EXPECT_EQ("text", TestAppend(" \n", "text"));
235 EXPECT_EQ("text", TestAppend("\n", " text"));
236 EXPECT_EQ("text", TestAppend("\n\n", " text"));
237 EXPECT_EQ("text", TestAppend(" \n", " text"));
238 EXPECT_EQ("text", TestAppend("\n", "\ntext"));
239 EXPECT_EQ("text", TestAppend("\n\n", "\ntext"));
240 EXPECT_EQ("text", TestAppend(" \n", "\ntext"));
241 }
242
TEST_F(NGInlineItemsBuilderTest,CollapseTrailingNewlines)243 TEST_F(NGInlineItemsBuilderTest, CollapseTrailingNewlines) {
244 EXPECT_EQ("text", TestAppend("text\n"));
245 EXPECT_EQ("text", TestAppend("text", "\n"));
246 EXPECT_EQ("text", TestAppend("text\n", "\n"));
247 EXPECT_EQ("text", TestAppend("text\n", " "));
248 EXPECT_EQ("text", TestAppend("text ", "\n"));
249 }
250
TEST_F(NGInlineItemsBuilderTest,CollapseNewlineAcrossElements)251 TEST_F(NGInlineItemsBuilderTest, CollapseNewlineAcrossElements) {
252 EXPECT_EQ("text text", TestAppend("text ", "\ntext"));
253 EXPECT_EQ("text text", TestAppend("text ", "\n text"));
254 EXPECT_EQ("text text", TestAppend("text", " ", "\ntext"));
255 }
256
TEST_F(NGInlineItemsBuilderTest,CollapseBeforeAndAfterNewline)257 TEST_F(NGInlineItemsBuilderTest, CollapseBeforeAndAfterNewline) {
258 SetWhiteSpace(EWhiteSpace::kPreLine);
259 EXPECT_EQ("text\ntext", TestAppend("text \n text"))
260 << "Spaces before and after newline are removed.";
261 }
262
TEST_F(NGInlineItemsBuilderTest,CollapsibleSpaceAfterNonCollapsibleSpaceAcrossElements)263 TEST_F(NGInlineItemsBuilderTest,
264 CollapsibleSpaceAfterNonCollapsibleSpaceAcrossElements) {
265 EXPECT_EQ("text text",
266 TestAppend({"text ", EWhiteSpace::kPreWrap}, {" text"}))
267 << "The whitespace in constructions like '<span style=\"white-space: "
268 "pre-wrap\">text <span><span> text</span>' does not collapse.";
269 }
270
TEST_F(NGInlineItemsBuilderTest,CollapseZeroWidthSpaces)271 TEST_F(NGInlineItemsBuilderTest, CollapseZeroWidthSpaces) {
272 EXPECT_EQ(String(u"text\u200Btext"), TestAppend(u"text\u200B\ntext"))
273 << "Newline is removed if the character before is ZWS.";
274 EXPECT_EQ(String(u"text\u200Btext"), TestAppend(u"text\n\u200Btext"))
275 << "Newline is removed if the character after is ZWS.";
276 EXPECT_EQ(String(u"text\u200B\u200Btext"),
277 TestAppend(u"text\u200B\n\u200Btext"))
278 << "Newline is removed if the character before/after is ZWS.";
279
280 EXPECT_EQ(String(u"text\u200Btext"), TestAppend(u"text\n", u"\u200Btext"))
281 << "Newline is removed if the character after across elements is ZWS.";
282 EXPECT_EQ(String(u"text\u200Btext"), TestAppend(u"text\u200B", u"\ntext"))
283 << "Newline is removed if the character before is ZWS even across "
284 "elements.";
285
286 EXPECT_EQ(String(u"text\u200Btext"), TestAppend(u"text \n", u"\u200Btext"))
287 << "Collapsible space before newline does not affect the result.";
288 EXPECT_EQ(String(u"text\u200B text"), TestAppend(u"text\u200B\n", u" text"))
289 << "Collapsible space after newline is removed even when the "
290 "newline was removed.";
291 EXPECT_EQ(String(u"text\u200Btext"), TestAppend(u"text\u200B ", u"\ntext"))
292 << "A white space sequence containing a segment break before or after "
293 "a zero width space is collapsed to a zero width space.";
294 }
295
TEST_F(NGInlineItemsBuilderTest,CollapseZeroWidthSpaceAndNewLineAtEnd)296 TEST_F(NGInlineItemsBuilderTest, CollapseZeroWidthSpaceAndNewLineAtEnd) {
297 EXPECT_EQ(String(u"\u200B"), TestAppend(u"\u200B\n"));
298 EXPECT_EQ(NGInlineItem::kNotCollapsible, items_[0].EndCollapseType());
299 }
300
301 #if SEGMENT_BREAK_TRANSFORMATION_FOR_EAST_ASIAN_WIDTH
TEST_F(NGInlineItemsBuilderTest,CollapseEastAsianWidth)302 TEST_F(NGInlineItemsBuilderTest, CollapseEastAsianWidth) {
303 EXPECT_EQ(String(u"\u4E00\u4E00"), TestAppend(u"\u4E00\n\u4E00"))
304 << "Newline is removed when both sides are Wide.";
305
306 EXPECT_EQ(String(u"\u4E00 A"), TestAppend(u"\u4E00\nA"))
307 << "Newline is not removed when after is Narrow.";
308 EXPECT_EQ(String(u"A \u4E00"), TestAppend(u"A\n\u4E00"))
309 << "Newline is not removed when before is Narrow.";
310
311 EXPECT_EQ(String(u"\u4E00\u4E00"), TestAppend(u"\u4E00\n", u"\u4E00"))
312 << "Newline at the end of elements is removed when both sides are Wide.";
313 EXPECT_EQ(String(u"\u4E00\u4E00"), TestAppend(u"\u4E00", u"\n\u4E00"))
314 << "Newline at the beginning of elements is removed "
315 "when both sides are Wide.";
316 }
317 #endif
318
TEST_F(NGInlineItemsBuilderTest,OpaqueToSpaceCollapsing)319 TEST_F(NGInlineItemsBuilderTest, OpaqueToSpaceCollapsing) {
320 NGInlineItemsBuilder builder(&items_);
321 AppendText("Hello ", &builder);
322 builder.AppendOpaque(NGInlineItem::kBidiControl,
323 kFirstStrongIsolateCharacter);
324 AppendText(" ", &builder);
325 builder.AppendOpaque(NGInlineItem::kBidiControl,
326 kFirstStrongIsolateCharacter);
327 AppendText(" World", &builder);
328 EXPECT_EQ(String(u"Hello \u2068\u2068World"), builder.ToString());
329 }
330
TEST_F(NGInlineItemsBuilderTest,CollapseAroundReplacedElement)331 TEST_F(NGInlineItemsBuilderTest, CollapseAroundReplacedElement) {
332 NGInlineItemsBuilder builder(&items_);
333 AppendText("Hello ", &builder);
334 AppendAtomicInline(&builder);
335 AppendText(" World", &builder);
336 EXPECT_EQ(String(u"Hello \uFFFC World"), builder.ToString());
337 }
338
TEST_F(NGInlineItemsBuilderTest,CollapseNewlineAfterObject)339 TEST_F(NGInlineItemsBuilderTest, CollapseNewlineAfterObject) {
340 NGInlineItemsBuilder builder(&items_);
341 AppendAtomicInline(&builder);
342 AppendText("\n", &builder);
343 AppendAtomicInline(&builder);
344 EXPECT_EQ(String(u"\uFFFC \uFFFC"), builder.ToString());
345 EXPECT_EQ(3u, items_.size());
346 EXPECT_ITEM_OFFSET(items_[0], NGInlineItem::kAtomicInline, 0u, 1u);
347 EXPECT_ITEM_OFFSET(items_[1], NGInlineItem::kText, 1u, 2u);
348 EXPECT_ITEM_OFFSET(items_[2], NGInlineItem::kAtomicInline, 2u, 3u);
349 }
350
TEST_F(NGInlineItemsBuilderTest,AppendEmptyString)351 TEST_F(NGInlineItemsBuilderTest, AppendEmptyString) {
352 EXPECT_EQ("", TestAppend(""));
353 EXPECT_EQ(1u, items_.size());
354 EXPECT_ITEM_OFFSET(items_[0], NGInlineItem::kText, 0u, 0u);
355 }
356
TEST_F(NGInlineItemsBuilderTest,NewLines)357 TEST_F(NGInlineItemsBuilderTest, NewLines) {
358 SetWhiteSpace(EWhiteSpace::kPre);
359 EXPECT_EQ("apple\norange\ngrape\n", TestAppend("apple\norange\ngrape\n"));
360 EXPECT_EQ(6u, items_.size());
361 EXPECT_EQ(NGInlineItem::kText, items_[0].Type());
362 EXPECT_EQ(NGInlineItem::kControl, items_[1].Type());
363 EXPECT_EQ(NGInlineItem::kText, items_[2].Type());
364 EXPECT_EQ(NGInlineItem::kControl, items_[3].Type());
365 EXPECT_EQ(NGInlineItem::kText, items_[4].Type());
366 EXPECT_EQ(NGInlineItem::kControl, items_[5].Type());
367 }
368
TEST_F(NGInlineItemsBuilderTest,IgnorablePre)369 TEST_F(NGInlineItemsBuilderTest, IgnorablePre) {
370 SetWhiteSpace(EWhiteSpace::kPre);
371 EXPECT_EQ(
372 "apple"
373 "\x0c"
374 "orange"
375 "\n"
376 "grape",
377 TestAppend("apple"
378 "\x0c"
379 "orange"
380 "\n"
381 "grape"));
382 EXPECT_EQ(5u, items_.size());
383 EXPECT_ITEM_OFFSET(items_[0], NGInlineItem::kText, 0u, 5u);
384 EXPECT_ITEM_OFFSET(items_[1], NGInlineItem::kControl, 5u, 6u);
385 EXPECT_ITEM_OFFSET(items_[2], NGInlineItem::kText, 6u, 12u);
386 EXPECT_ITEM_OFFSET(items_[3], NGInlineItem::kControl, 12u, 13u);
387 EXPECT_ITEM_OFFSET(items_[4], NGInlineItem::kText, 13u, 18u);
388 }
389
TEST_F(NGInlineItemsBuilderTest,Empty)390 TEST_F(NGInlineItemsBuilderTest, Empty) {
391 Vector<NGInlineItem> items;
392 NGInlineItemsBuilder builder(&items);
393 scoped_refptr<ComputedStyle> block_style(ComputedStyle::Create());
394 builder.EnterBlock(block_style.get());
395 builder.ExitBlock();
396
397 EXPECT_EQ("", builder.ToString());
398 }
399
400 class CollapsibleSpaceTest : public NGInlineItemsBuilderTest,
401 public testing::WithParamInterface<UChar> {};
402
403 INSTANTIATE_TEST_SUITE_P(NGInlineItemsBuilderTest,
404 CollapsibleSpaceTest,
405 testing::Values(kSpaceCharacter,
406 kTabulationCharacter,
407 kNewlineCharacter));
408
TEST_P(CollapsibleSpaceTest,CollapsedSpaceAfterNoWrap)409 TEST_P(CollapsibleSpaceTest, CollapsedSpaceAfterNoWrap) {
410 UChar space = GetParam();
411 EXPECT_EQ(
412 String("nowrap "
413 u"\u200B"
414 "wrap"),
415 TestAppend({String("nowrap") + space, EWhiteSpace::kNowrap}, {" wrap"}));
416 }
417
TEST_F(NGInlineItemsBuilderTest,GenerateBreakOpportunityAfterLeadingSpaces)418 TEST_F(NGInlineItemsBuilderTest, GenerateBreakOpportunityAfterLeadingSpaces) {
419 EXPECT_EQ(String(" "
420 u"\u200B"
421 "a"),
422 TestAppend({{" a", EWhiteSpace::kPreWrap}}));
423 EXPECT_EQ(String(" "
424 u"\u200B"
425 "a"),
426 TestAppend({{" a", EWhiteSpace::kPreWrap}}));
427 EXPECT_EQ(String("a\n"
428 u" \u200B"),
429 TestAppend({{"a\n ", EWhiteSpace::kPreWrap}}));
430 }
431
TEST_F(NGInlineItemsBuilderTest,BidiBlockOverride)432 TEST_F(NGInlineItemsBuilderTest, BidiBlockOverride) {
433 Vector<NGInlineItem> items;
434 NGInlineItemsBuilder builder(&items);
435 scoped_refptr<ComputedStyle> block_style(ComputedStyle::Create());
436 block_style->SetUnicodeBidi(UnicodeBidi::kBidiOverride);
437 block_style->SetDirection(TextDirection::kRtl);
438 builder.EnterBlock(block_style.get());
439 AppendText("Hello", &builder);
440 builder.ExitBlock();
441
442 // Expected control characters as defined in:
443 // https://drafts.csswg.org/css-writing-modes-3/#bidi-control-codes-injection-table
444 EXPECT_EQ(String(u"\u202E"
445 u"Hello"
446 u"\u202C"),
447 builder.ToString());
448 }
449
CreateLayoutInline(Document * document,void (* initialize_style)(ComputedStyle *))450 static LayoutInline* CreateLayoutInline(
451 Document* document,
452 void (*initialize_style)(ComputedStyle*)) {
453 scoped_refptr<ComputedStyle> style(ComputedStyle::Create());
454 initialize_style(style.get());
455 LayoutInline* const node = LayoutInline::CreateAnonymous(document);
456 node->SetModifiedStyleOutsideStyleRecalc(
457 std::move(style), LayoutObject::ApplyStyleChanges::kNo);
458 node->SetIsInLayoutNGInlineFormattingContext(true);
459 return node;
460 }
461
TEST_F(NGInlineItemsBuilderTest,BidiIsolate)462 TEST_F(NGInlineItemsBuilderTest, BidiIsolate) {
463 Vector<NGInlineItem> items;
464 NGInlineItemsBuilder builder(&items);
465 AppendText("Hello ", &builder);
466 LayoutInline* const isolate_rtl =
467 CreateLayoutInline(&GetDocument(), [](ComputedStyle* style) {
468 style->SetUnicodeBidi(UnicodeBidi::kIsolate);
469 style->SetDirection(TextDirection::kRtl);
470 });
471 builder.EnterInline(isolate_rtl);
472 AppendText(u"\u05E2\u05D1\u05E8\u05D9\u05EA", &builder);
473 builder.ExitInline(isolate_rtl);
474 AppendText(" World", &builder);
475
476 // Expected control characters as defined in:
477 // https://drafts.csswg.org/css-writing-modes-3/#bidi-control-codes-injection-table
478 EXPECT_EQ(String(u"Hello "
479 u"\u2067"
480 u"\u05E2\u05D1\u05E8\u05D9\u05EA"
481 u"\u2069"
482 u" World"),
483 builder.ToString());
484 isolate_rtl->Destroy();
485 }
486
TEST_F(NGInlineItemsBuilderTest,BidiIsolateOverride)487 TEST_F(NGInlineItemsBuilderTest, BidiIsolateOverride) {
488 Vector<NGInlineItem> items;
489 NGInlineItemsBuilder builder(&items);
490 AppendText("Hello ", &builder);
491 LayoutInline* const isolate_override_rtl =
492 CreateLayoutInline(&GetDocument(), [](ComputedStyle* style) {
493 style->SetUnicodeBidi(UnicodeBidi::kIsolateOverride);
494 style->SetDirection(TextDirection::kRtl);
495 });
496 builder.EnterInline(isolate_override_rtl);
497 AppendText(u"\u05E2\u05D1\u05E8\u05D9\u05EA", &builder);
498 builder.ExitInline(isolate_override_rtl);
499 AppendText(" World", &builder);
500
501 // Expected control characters as defined in:
502 // https://drafts.csswg.org/css-writing-modes-3/#bidi-control-codes-injection-table
503 EXPECT_EQ(String(u"Hello "
504 u"\u2068\u202E"
505 u"\u05E2\u05D1\u05E8\u05D9\u05EA"
506 u"\u202C\u2069"
507 u" World"),
508 builder.ToString());
509 isolate_override_rtl->Destroy();
510 }
511
512 } // namespace blink
513