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