Changeset 259773 in webkit
- Timestamp:
- Apr 8, 2020, 5:43:49 PM (6 years ago)
- Location:
- trunk
- Files:
-
- 2 added
- 7 edited
-
LayoutTests/imported/w3c/ChangeLog (modified) (1 diff)
-
LayoutTests/imported/w3c/web-platform-tests/dom/nodes/ParentNode-querySelector-escapes-expected.txt (added)
-
LayoutTests/imported/w3c/web-platform-tests/dom/nodes/ParentNode-querySelector-escapes.html (added)
-
Source/WTF/ChangeLog (modified) (1 diff)
-
Source/WTF/wtf/text/StringImpl.cpp (modified) (1 diff)
-
Source/WebCore/ChangeLog (modified) (1 diff)
-
Source/WebCore/css/parser/CSSTokenizer.cpp (modified) (28 diffs)
-
Source/WebCore/css/parser/CSSTokenizer.h (modified) (1 diff)
-
Source/WebCore/css/parser/CSSTokenizerInputStream.h (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/imported/w3c/ChangeLog
r259725 r259773 1 2020-04-08 Chris Dumez <cdumez@apple.com> 2 3 querySelector("#\u0000") should match an element with ID U+FFFD 4 https://bugs.webkit.org/show_bug.cgi?id=210119 5 6 Reviewed by Darin Adler. 7 8 Import test coverage from upstream WPT. 9 10 * web-platform-tests/dom/nodes/ParentNode-querySelector-escapes-expected.txt: Added. 11 * web-platform-tests/dom/nodes/ParentNode-querySelector-escapes.html: Added. 12 1 13 2020-04-08 Rob Buis <rbuis@igalia.com> 2 14 -
trunk/Source/WTF/ChangeLog
r259767 r259773 1 2020-04-08 Chris Dumez <cdumez@apple.com> 2 3 querySelector("#\u0000") should match an element with ID U+FFFD 4 https://bugs.webkit.org/show_bug.cgi?id=210119 5 6 Reviewed by Darin Adler. 7 8 * wtf/text/StringImpl.cpp: 9 (WTF::StringImpl::replace): 10 Slightly optimize the 16-bit code path of StringImpl::replace(). Since we know 11 there is no character match from indexes 0 to i, we can simply use memcpy for 12 this range. 13 1 14 2020-04-08 Ross Kirsling <ross.kirsling@sony.com> 2 15 -
trunk/Source/WTF/wtf/text/StringImpl.cpp
r258828 r259773 1303 1303 auto newImpl = createUninitializedInternalNonEmpty(m_length, data); 1304 1304 1305 for (i = 0; i != m_length; ++i) { 1306 UChar character = m_data16[i]; 1305 memcpy(data, m_data16, i * sizeof(UChar)); 1306 for (unsigned j = i; j != m_length; ++j) { 1307 UChar character = m_data16[j]; 1307 1308 if (character == target) 1308 1309 character = replacement; 1309 data[ i] = character;1310 data[j] = character; 1310 1311 } 1311 1312 return newImpl; -
trunk/Source/WebCore/ChangeLog
r259772 r259773 1 2020-04-08 Chris Dumez <cdumez@apple.com> 2 3 querySelector("#\u0000") should match an element with ID U+FFFD 4 https://bugs.webkit.org/show_bug.cgi?id=210119 5 6 Reviewed by Darin Adler. 7 8 As per the specification [1][2], we should preprocess the input string before performing 9 CSS tokenization. The preprocessing step replaces certain characters in the input string. 10 11 However, our code did not have this preprocessing step and instead was trying to deal 12 with those characters during tokenization. This is however not working as expected for 13 the '\0' character (which is supposed to be replaced with U+FFFD REPLACEMENT CHARACTER) 14 because our code deals with StringViews of the input String and just converts part of 15 the input stream to Strings / AtomStrings. 16 17 To address the issue, this patch adds a preprocessing step that replaces the '\0' 18 character with the U+FFFD REPLACEMENT CHARACTER). I opted not to replace '\r' or '\f' 19 characters since our tokenizer seems to be dealing fine with those. 20 21 [1] https://drafts.csswg.org/css-syntax/#input-preprocessing 22 [2] https://drafts.csswg.org/css-syntax/#parser-entry-points 23 24 Test: imported/w3c/web-platform-tests/dom/nodes/ParentNode-querySelector-escapes.html 25 26 * css/parser/CSSTokenizer.cpp: 27 (WebCore::preprocessString): 28 (WebCore::CSSTokenizer::CSSTokenizer): 29 (WebCore::CSSTokenizer::lessThan): 30 (WebCore::CSSTokenizer::hyphenMinus): 31 (WebCore::CSSTokenizer::hash): 32 (WebCore::CSSTokenizer::reverseSolidus): 33 (WebCore::CSSTokenizer::letterU): 34 (WebCore::CSSTokenizer::consumeNumber): 35 (WebCore::CSSTokenizer::consumeIdentLikeToken): 36 (WebCore::CSSTokenizer::consumeStringTokenUntil): 37 (WebCore::CSSTokenizer::consumeUnicodeRange): 38 (WebCore::CSSTokenizer::consumeUrlToken): 39 (WebCore::CSSTokenizer::consumeBadUrlRemnants): 40 (WebCore::CSSTokenizer::consumeSingleWhitespaceIfNext): 41 (WebCore::CSSTokenizer::consumeIfNext): 42 (WebCore::CSSTokenizer::consumeName): 43 (WebCore::CSSTokenizer::consumeEscape): 44 (WebCore::CSSTokenizer::nextTwoCharsAreValidEscape): 45 (WebCore::CSSTokenizer::nextCharsAreNumber): 46 (WebCore::CSSTokenizer::nextCharsAreIdentifier): 47 * css/parser/CSSTokenizer.h: 48 * css/parser/CSSTokenizerInputStream.h: 49 (WebCore::CSSTokenizerInputStream::nextInputChar const): 50 (WebCore::CSSTokenizerInputStream::peek const): 51 (WebCore::CSSTokenizerInputStream::peekWithoutReplacement const): Deleted. 52 1 53 2020-04-08 Alex Christensen <achristensen@webkit.org> 2 54 -
trunk/Source/WebCore/css/parser/CSSTokenizer.cpp
r251655 r259773 41 41 namespace WebCore { 42 42 43 // See: http://dev.w3.org/csswg/css-syntax/#input-preprocessing 44 static String preprocessString(String string) 45 { 46 // According to the specification, we should replace '\r' and '\f' with '\n' but we do not need to 47 // because our CSSTokenizer treats all of them as new lines. 48 return string.replace('\0', replacementCharacter); 49 } 50 43 51 CSSTokenizer::CSSTokenizer(const String& string) 52 : CSSTokenizer(preprocessString(string), nullptr) 53 { 54 } 55 56 CSSTokenizer::CSSTokenizer(const String& string, CSSParserObserverWrapper& wrapper) 57 : CSSTokenizer(preprocessString(string), &wrapper) 58 { 59 } 60 61 inline CSSTokenizer::CSSTokenizer(String&& string, CSSParserObserverWrapper* wrapper) 44 62 : m_input(string) 45 63 { 46 // According to the spec, we should perform preprocessing here.47 // See: http://dev.w3.org/csswg/css-syntax/#input-preprocessing48 //49 // However, we can skip this step since:50 // * We're using HTML spaces (which accept \r and \f as a valid white space)51 // * Do not count white spaces52 // * CSSTokenizerInputStream::nextInputChar() replaces NULLs for replacement characters53 54 64 if (string.isEmpty()) 55 65 return; … … 58 68 // Most strings we tokenize have about 3.5 to 5 characters per token. 59 69 m_tokens.reserveInitialCapacity(string.length() / 3); 60 61 while (true) {62 CSSParserToken token = nextToken();63 if (token.type() == CommentToken)64 continue;65 if (token.type() == EOFToken)66 return;67 m_tokens.append(token);68 }69 }70 71 CSSTokenizer::CSSTokenizer(const String& string, CSSParserObserverWrapper& wrapper)72 : m_input(string)73 {74 if (string.isEmpty())75 return;76 70 77 71 unsigned offset = 0; … … 80 74 if (token.type() == EOFToken) 81 75 break; 82 if (token.type() == CommentToken) 83 wrapper.addComment(offset, m_input.offset(), m_tokens.size()); 84 else { 76 if (token.type() == CommentToken) { 77 if (wrapper) 78 wrapper->addComment(offset, m_input.offset(), m_tokens.size()); 79 } else { 85 80 m_tokens.append(token); 86 wrapper.addToken(offset); 81 if (wrapper) 82 wrapper->addToken(offset); 87 83 } 88 84 offset = m_input.offset(); 89 85 } 90 86 91 wrapper.addToken(offset); 92 wrapper.finalizeConstruction(m_tokens.begin()); 87 if (wrapper) { 88 wrapper->addToken(offset); 89 wrapper->finalizeConstruction(m_tokens.begin()); 90 } 93 91 } 94 92 … … 204 202 { 205 203 ASSERT_UNUSED(cc, cc == '<'); 206 if (m_input.peekWithoutReplacement(0) == '!' 207 && m_input.peekWithoutReplacement(1) == '-' 208 && m_input.peekWithoutReplacement(2) == '-') { 204 if (m_input.peek(0) == '!' && m_input.peek(1) == '-' && m_input.peek(2) == '-') { 209 205 m_input.advance(3); 210 206 return CSSParserToken(CDOToken); … … 224 220 return consumeNumericToken(); 225 221 } 226 if (m_input.peekWithoutReplacement(0) == '-' 227 && m_input.peekWithoutReplacement(1) == '>') { 222 if (m_input.peek(0) == '-' && m_input.peek(1) == '>') { 228 223 m_input.advance(2); 229 224 return CSSParserToken(CDCToken); … … 259 254 CSSParserToken CSSTokenizer::hash(UChar cc) 260 255 { 261 UChar nextChar = m_input.peek WithoutReplacement(0);262 if (isNameCodePoint(nextChar) || twoCharsAreValidEscape(nextChar, m_input.peek WithoutReplacement(1))) {256 UChar nextChar = m_input.peek(0); 257 if (isNameCodePoint(nextChar) || twoCharsAreValidEscape(nextChar, m_input.peek(1))) { 263 258 HashTokenType type = nextCharsAreIdentifier() ? HashTokenId : HashTokenUnrestricted; 264 259 return CSSParserToken(type, consumeName()); … … 312 307 CSSParserToken CSSTokenizer::reverseSolidus(UChar cc) 313 308 { 314 if (twoCharsAreValidEscape(cc, m_input.peek WithoutReplacement(0))) {309 if (twoCharsAreValidEscape(cc, m_input.peek(0))) { 315 310 reconsume(cc); 316 311 return consumeIdentLikeToken(); … … 327 322 CSSParserToken CSSTokenizer::letterU(UChar cc) 328 323 { 329 if (m_input.peekWithoutReplacement(0) == '+' 330 && (isASCIIHexDigit(m_input.peekWithoutReplacement(1)) || m_input.peekWithoutReplacement(1) == '?')) { 324 if (m_input.peek(0) == '+' && (isASCIIHexDigit(m_input.peek(1)) || m_input.peek(1) == '?')) { 331 325 m_input.advance(); 332 326 return consumeUnicodeRange(); … … 520 514 unsigned numberLength = 0; 521 515 522 UChar next = m_input.peek WithoutReplacement(0);516 UChar next = m_input.peek(0); 523 517 if (next == '+') { 524 518 ++numberLength; … … 530 524 531 525 numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength); 532 next = m_input.peek WithoutReplacement(numberLength);533 if (next == '.' && isASCIIDigit(m_input.peek WithoutReplacement(numberLength + 1))) {526 next = m_input.peek(numberLength); 527 if (next == '.' && isASCIIDigit(m_input.peek(numberLength + 1))) { 534 528 type = NumberValueType; 535 529 numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength + 2); 536 next = m_input.peek WithoutReplacement(numberLength);530 next = m_input.peek(numberLength); 537 531 } 538 532 539 533 if (next == 'E' || next == 'e') { 540 next = m_input.peek WithoutReplacement(numberLength + 1);534 next = m_input.peek(numberLength + 1); 541 535 if (isASCIIDigit(next)) { 542 536 type = NumberValueType; 543 537 numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength + 1); 544 } else if ((next == '+' || next == '-') && isASCIIDigit(m_input.peek WithoutReplacement(numberLength + 2))) {538 } else if ((next == '+' || next == '-') && isASCIIDigit(m_input.peek(numberLength + 2))) { 545 539 type = NumberValueType; 546 540 numberLength = m_input.skipWhilePredicate<isASCIIDigit>(numberLength + 3); … … 574 568 // tokens, but they wouldn't be used and this is easier. 575 569 m_input.advanceUntilNonWhitespace(); 576 UChar next = m_input.peek WithoutReplacement(0);570 UChar next = m_input.peek(0); 577 571 if (next != '"' && next != '\'') 578 572 return consumeUrlToken(); … … 588 582 // Strings without escapes get handled without allocations 589 583 for (unsigned size = 0; ; size++) { 590 UChar cc = m_input.peek WithoutReplacement(size);584 UChar cc = m_input.peek(size); 591 585 if (cc == endingCodePoint) { 592 586 unsigned startOffset = m_input.offset(); … … 598 592 return CSSParserToken(BadStringToken); 599 593 } 600 if (cc == '\0'|| cc == '\\')594 if (cc == kEndOfFileMarker || cc == '\\') 601 595 break; 602 596 } … … 614 608 if (m_input.nextInputChar() == kEndOfFileMarker) 615 609 continue; 616 if (isNewLine(m_input.peek WithoutReplacement(0)))610 if (isNewLine(m_input.peek(0))) 617 611 consumeSingleWhitespaceIfNext(); // This handles \r\n for us 618 612 else … … 625 619 CSSParserToken CSSTokenizer::consumeUnicodeRange() 626 620 { 627 ASSERT(isASCIIHexDigit(m_input.peek WithoutReplacement(0)) || m_input.peekWithoutReplacement(0) == '?');621 ASSERT(isASCIIHexDigit(m_input.peek(0)) || m_input.peek(0) == '?'); 628 622 int lengthRemaining = 6; 629 623 UChar32 start = 0; 630 624 631 while (lengthRemaining && isASCIIHexDigit(m_input.peek WithoutReplacement(0))) {625 while (lengthRemaining && isASCIIHexDigit(m_input.peek(0))) { 632 626 start = start * 16 + toASCIIHexValue(consume()); 633 627 --lengthRemaining; … … 641 635 --lengthRemaining; 642 636 } while (lengthRemaining && consumeIfNext('?')); 643 } else if (m_input.peek WithoutReplacement(0) == '-' && isASCIIHexDigit(m_input.peekWithoutReplacement(1))) {637 } else if (m_input.peek(0) == '-' && isASCIIHexDigit(m_input.peek(1))) { 644 638 m_input.advance(); 645 639 lengthRemaining = 6; … … 648 642 end = end * 16 + toASCIIHexValue(consume()); 649 643 --lengthRemaining; 650 } while (lengthRemaining && isASCIIHexDigit(m_input.peek WithoutReplacement(0)));644 } while (lengthRemaining && isASCIIHexDigit(m_input.peek(0))); 651 645 } 652 646 … … 667 661 // URL tokens without escapes get handled without allocations 668 662 for (unsigned size = 0; ; size++) { 669 UChar cc = m_input.peek WithoutReplacement(size);663 UChar cc = m_input.peek(size); 670 664 if (cc == ')') { 671 665 unsigned startOffset = m_input.offset(); … … 694 688 695 689 if (cc == '\\') { 696 if (twoCharsAreValidEscape(cc, m_input.peek WithoutReplacement(0))) {690 if (twoCharsAreValidEscape(cc, m_input.peek(0))) { 697 691 result.appendCharacter(consumeEscape()); 698 692 continue; … … 715 709 if (cc == ')' || cc == kEndOfFileMarker) 716 710 return; 717 if (twoCharsAreValidEscape(cc, m_input.peek WithoutReplacement(0)))711 if (twoCharsAreValidEscape(cc, m_input.peek(0))) 718 712 consumeEscape(); 719 713 } … … 723 717 { 724 718 // We check for \r\n and HTML spaces since we don't do preprocessing 725 UChar next = m_input.peek WithoutReplacement(0);726 if (next == '\r' && m_input.peek WithoutReplacement(1) == '\n')719 UChar next = m_input.peek(0); 720 if (next == '\r' && m_input.peek(1) == '\n') 727 721 m_input.advance(2); 728 722 else if (isHTMLSpace(next)) … … 752 746 // NUL. 753 747 ASSERT(character); 754 if (m_input.peek WithoutReplacement(0) == character) {748 if (m_input.peek(0) == character) { 755 749 m_input.advance(); 756 750 return true; … … 764 758 // Names without escapes get handled without allocations 765 759 for (unsigned size = 0; ; ++size) { 766 UChar cc = m_input.peek WithoutReplacement(size);760 UChar cc = m_input.peek(size); 767 761 if (isNameCodePoint(cc)) 768 762 continue; 769 // peek WithoutReplacementwill return NUL when we hit the end of the763 // peek will return NUL when we hit the end of the 770 764 // input. In that case we want to still use the rangeAt() fast path 771 765 // below. 772 if (cc == '\0'&& m_input.offset() + size < m_input.length())766 if (cc == kEndOfFileMarker && m_input.offset() + size < m_input.length()) 773 767 break; 774 768 if (cc == '\\') … … 786 780 continue; 787 781 } 788 if (twoCharsAreValidEscape(cc, m_input.peek WithoutReplacement(0))) {782 if (twoCharsAreValidEscape(cc, m_input.peek(0))) { 789 783 result.appendCharacter(consumeEscape()); 790 784 continue; … … 804 798 StringBuilder hexChars; 805 799 hexChars.append(cc); 806 while (consumedHexDigits < 6 && isASCIIHexDigit(m_input.peek WithoutReplacement(0))) {800 while (consumedHexDigits < 6 && isASCIIHexDigit(m_input.peek(0))) { 807 801 cc = consume(); 808 802 hexChars.append(cc); … … 825 819 bool CSSTokenizer::nextTwoCharsAreValidEscape() 826 820 { 827 return twoCharsAreValidEscape(m_input.peek WithoutReplacement(0), m_input.peekWithoutReplacement(1));821 return twoCharsAreValidEscape(m_input.peek(0), m_input.peek(1)); 828 822 } 829 823 … … 831 825 bool CSSTokenizer::nextCharsAreNumber(UChar first) 832 826 { 833 UChar second = m_input.peek WithoutReplacement(0);827 UChar second = m_input.peek(0); 834 828 if (isASCIIDigit(first)) 835 829 return true; 836 830 if (first == '+' || first == '-') 837 return ((isASCIIDigit(second)) || (second == '.' && isASCIIDigit(m_input.peek WithoutReplacement(1))));831 return ((isASCIIDigit(second)) || (second == '.' && isASCIIDigit(m_input.peek(1)))); 838 832 if (first =='.') 839 833 return (isASCIIDigit(second)); … … 852 846 bool CSSTokenizer::nextCharsAreIdentifier(UChar first) 853 847 { 854 UChar second = m_input.peek WithoutReplacement(0);848 UChar second = m_input.peek(0); 855 849 if (isNameStartCodePoint(first) || twoCharsAreValidEscape(first, second)) 856 850 return true; -
trunk/Source/WebCore/css/parser/CSSTokenizer.h
r247688 r259773 55 55 56 56 private: 57 CSSTokenizer(String&&, CSSParserObserverWrapper*); 58 57 59 CSSParserToken nextToken(); 58 60 -
trunk/Source/WebCore/css/parser/CSSTokenizerInputStream.h
r209466 r259773 48 48 { 49 49 if (m_offset >= m_stringLength) 50 return '\0'; 51 UChar result = (*m_string)[m_offset]; 52 return result ? result : 0xFFFD; 50 return kEndOfFileMarker; 51 return (*m_string)[m_offset]; 53 52 } 54 53 55 54 // Gets the char at lookaheadOffset from the current stream position. Will 56 55 // return NUL (kEndOfFileMarker) if the stream position is at the end. 57 // NOTE: This may *also* return NUL if there's one in the input! Never 58 // compare the return value to '\0'. 59 UChar peekWithoutReplacement(unsigned lookaheadOffset) const 56 UChar peek(unsigned lookaheadOffset) const 60 57 { 61 58 if ((m_offset + lookaheadOffset) >= m_stringLength) 62 return '\0';59 return kEndOfFileMarker; 63 60 return (*m_string)[m_offset + lookaheadOffset]; 64 61 }
Note:
See TracChangeset
for help on using the changeset viewer.