diff --git a/editor/reftests/passwd-5-with-Preview.html b/editor/reftests/passwd-5-with-Preview.html new file mode 100644 index 000000000000..d95382deb364 --- /dev/null +++ b/editor/reftests/passwd-5-with-Preview.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/editor/reftests/passwd-5-with-TextEditor.html b/editor/reftests/passwd-5-with-TextEditor.html new file mode 100644 index 000000000000..2b6c5958e801 --- /dev/null +++ b/editor/reftests/passwd-5-with-TextEditor.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/editor/reftests/passwd-6-ref.html b/editor/reftests/passwd-6-ref.html new file mode 100644 index 000000000000..e66f4e0b8cb5 --- /dev/null +++ b/editor/reftests/passwd-6-ref.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/editor/reftests/passwd-6-with-Preview.html b/editor/reftests/passwd-6-with-Preview.html new file mode 100644 index 000000000000..745ced22510a --- /dev/null +++ b/editor/reftests/passwd-6-with-Preview.html @@ -0,0 +1,10 @@ + + + + + + diff --git a/editor/reftests/passwd-6-with-TextEditor.html b/editor/reftests/passwd-6-with-TextEditor.html new file mode 100644 index 000000000000..f9fcb1892439 --- /dev/null +++ b/editor/reftests/passwd-6-with-TextEditor.html @@ -0,0 +1,12 @@ + + + + + + diff --git a/editor/reftests/passwd-7-with-Preview.html b/editor/reftests/passwd-7-with-Preview.html new file mode 100644 index 000000000000..c101743ce0ac --- /dev/null +++ b/editor/reftests/passwd-7-with-Preview.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/editor/reftests/passwd-7-with-TextEditor.html b/editor/reftests/passwd-7-with-TextEditor.html new file mode 100644 index 000000000000..f2940db62565 --- /dev/null +++ b/editor/reftests/passwd-7-with-TextEditor.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/editor/reftests/passwd-8-with-Preview.html b/editor/reftests/passwd-8-with-Preview.html new file mode 100644 index 000000000000..2cc4ba243e99 --- /dev/null +++ b/editor/reftests/passwd-8-with-Preview.html @@ -0,0 +1,7 @@ + + + + + + diff --git a/editor/reftests/passwd-8-with-TextEditor.html b/editor/reftests/passwd-8-with-TextEditor.html new file mode 100644 index 000000000000..d4f9aa7d6f38 --- /dev/null +++ b/editor/reftests/passwd-8-with-TextEditor.html @@ -0,0 +1,9 @@ + + + + + + diff --git a/editor/reftests/passwd-9-with-Preview.html b/editor/reftests/passwd-9-with-Preview.html new file mode 100644 index 000000000000..528d8f8ac571 --- /dev/null +++ b/editor/reftests/passwd-9-with-Preview.html @@ -0,0 +1,9 @@ + + + + + + diff --git a/editor/reftests/passwd-9-with-TextEditor.html b/editor/reftests/passwd-9-with-TextEditor.html new file mode 100644 index 000000000000..d775c44238d2 --- /dev/null +++ b/editor/reftests/passwd-9-with-TextEditor.html @@ -0,0 +1,7 @@ + + + + + + diff --git a/editor/reftests/reftest.list b/editor/reftests/reftest.list index 76044936ed3a..8824f88fe445 100644 --- a/editor/reftests/reftest.list +++ b/editor/reftests/reftest.list @@ -14,6 +14,16 @@ include xul/reftest.list != passwd-2.html passwd-ref.html == passwd-3.html passwd-ref.html needs-focus == passwd-4.html passwd-ref.html +== passwd-5-with-Preview.html passwd-ref.html +needs-focus == passwd-5-with-TextEditor.html passwd-ref.html +== passwd-6-with-Preview.html passwd-6-ref.html +needs-focus == passwd-6-with-TextEditor.html passwd-6-ref.html +== passwd-7-with-Preview.html passwd-ref.html +needs-focus == passwd-7-with-TextEditor.html passwd-ref.html +== passwd-8-with-Preview.html passwd-ref.html +needs-focus == passwd-8-with-TextEditor.html passwd-ref.html +== passwd-9-with-Preview.html passwd-2.html +needs-focus == passwd-9-with-TextEditor.html passwd-2.html == emptypasswd-1.html emptypasswd-ref.html == emptypasswd-2.html emptypasswd-ref.html == caret_on_positioned.html caret_on_positioned-ref.html diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index f6801f3e9768..df2a7c347271 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -19,6 +19,7 @@ #include "mozilla/MathAlgorithms.h" #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs.h" +#include "mozilla/TextEditor.h" #include "mozilla/TextEvents.h" #include "mozilla/BinarySearch.h" #include "mozilla/IntegerRange.h" @@ -103,6 +104,12 @@ using namespace mozilla::gfx; typedef mozilla::layout::TextDrawTarget TextDrawTarget; +static bool NeedsToMaskPassword(nsTextFrame* aFrame) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aFrame->GetContent()); + return aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED); +} + struct TabWidth { TabWidth(uint32_t aOffset, uint32_t aWidth) : mOffset(aOffset), mWidth(float(aWidth)) {} @@ -2368,8 +2375,9 @@ already_AddRefed BuildTextRunsScanner::BuildTextRunForFrames( } // Setup factory chain + bool needsToMaskPassword = NeedsToMaskPassword(firstFrame); UniquePtr transformingFactory; - if (anyTextTransformStyle) { + if (anyTextTransformStyle || needsToMaskPassword) { transformingFactory = MakeUnique( std::move(transformingFactory)); } @@ -2380,29 +2388,67 @@ already_AddRefed BuildTextRunsScanner::BuildTextRunForFrames( } nsTArray> styles; if (transformingFactory) { + uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX; + if (needsToMaskPassword) { + unmaskStart = unmaskEnd = UINT32_MAX; + TextEditor* passwordEditor = + nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation( + firstFrame->GetContent()); + if (passwordEditor && !passwordEditor->IsAllMasked()) { + unmaskStart = passwordEditor->UnmaskedStart(); + unmaskEnd = passwordEditor->UnmaskedEnd(); + } + } + iter.SetOriginalOffset(0); for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { MappedFlow* mappedFlow = &mMappedFlows[i]; nsTextFrame* f; ComputedStyle* sc = nullptr; - RefPtr charStyle; + RefPtr defaultStyle; + RefPtr unmaskStyle; for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame; f = f->GetNextContinuation()) { - uint32_t offset = iter.GetSkippedOffset(); - iter.AdvanceOriginal(f->GetContentLength()); - uint32_t end = iter.GetSkippedOffset(); + uint32_t skippedOffset = iter.GetSkippedOffset(); // Text-combined frames have content-dependent transform, so we // want to create new nsTransformedCharStyle for them anyway. if (sc != f->Style() || sc->IsTextCombined()) { sc = f->Style(); - charStyle = new nsTransformedCharStyle(sc, f->PresContext()); + defaultStyle = new nsTransformedCharStyle(sc, f->PresContext()); if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) { - charStyle->mForceNonFullWidth = true; + defaultStyle->mForceNonFullWidth = true; + } + if (needsToMaskPassword) { + defaultStyle->mMaskPassword = true; + if (unmaskStart != unmaskEnd) { + unmaskStyle = new nsTransformedCharStyle(sc, f->PresContext()); + unmaskStyle->mForceNonFullWidth = + defaultStyle->mForceNonFullWidth; + } } } - uint32_t j; - for (j = offset; j < end; ++j) { - styles.AppendElement(charStyle); + iter.AdvanceOriginal(f->GetContentLength()); + uint32_t skippedEnd = iter.GetSkippedOffset(); + if (unmaskStyle) { + uint32_t skippedUnmaskStart = + iter.ConvertOriginalToSkipped(unmaskStart); + uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd); + iter.SetSkippedOffset(skippedEnd); + for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart); + ++skippedOffset) { + styles.AppendElement(defaultStyle); + } + for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd); + ++skippedOffset) { + styles.AppendElement(unmaskStyle); + } + for (; skippedOffset < skippedEnd; ++skippedOffset) { + styles.AppendElement(defaultStyle); + } + } else { + for (; skippedOffset < skippedEnd; ++skippedOffset) { + styles.AppendElement(defaultStyle); + } } } } @@ -9576,7 +9622,7 @@ static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle, int32_t aFragLen, nsAString& aOut) { nsAutoString fragString; char16_t* out; - if (aStyle->mTextTransform.IsNone()) { + if (aStyle->mTextTransform.IsNone() && !NeedsToMaskPassword(aFrame)) { // No text-transform, so we can copy directly to the output string. aOut.SetLength(aOut.Length() + aFragLen); out = aOut.EndWriting() - aFragLen; @@ -9596,7 +9642,7 @@ static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle, out[i] = ch; } - if (!aStyle->mTextTransform.IsNone()) { + if (!aStyle->mTextTransform.IsNone() || NeedsToMaskPassword(aFrame)) { MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed); if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) { // Apply text-transform according to style in the transformed run. diff --git a/layout/generic/nsTextRunTransformations.cpp b/layout/generic/nsTextRunTransformations.cpp index e0e569887418..8c1bf00bc1ae 100644 --- a/layout/generic/nsTextRunTransformations.cpp +++ b/layout/generic/nsTextRunTransformations.cpp @@ -7,6 +7,7 @@ #include "nsTextRunTransformations.h" #include "mozilla/ComputedStyleInlines.h" +#include "mozilla/LookAndFeel.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Move.h" @@ -278,6 +279,7 @@ bool nsCaseTransformTextRunFactory::TransformString( uint32_t length = aString.Length(); const char16_t* str = aString.BeginReading(); + const char16_t kPasswordMask = LookAndFeel::GetPasswordCharacter(); bool mergeNeeded = false; @@ -334,6 +336,7 @@ bool nsCaseTransformTextRunFactory::TransformString( } } + bool maskPassword = charStyle && charStyle->mMaskPassword; int extraChars = 0; const mozilla::unicode::MultiCharMapping* mcm; bool inhibitBreakBefore = false; // have we just deleted preceding hyphen? @@ -341,417 +344,431 @@ bool nsCaseTransformTextRunFactory::TransformString( if (NS_IS_HIGH_SURROGATE(ch) && i < length - 1 && NS_IS_LOW_SURROGATE(str[i + 1])) { ch = SURROGATE_TO_UCS4(ch, str[i + 1]); + // A non-BMP character is masked with 2 mask characters. Therefore, + // one of them is unmasked, we should unmask the surrogate pair. + if (maskPassword && + !aTextRun->mStyles[aOffsetInTextRun + 1]->mMaskPassword) { + maskPassword = false; + } } - switch (style.case_) { - case StyleTextTransformCase::None: - break; + // Skip case transform if we're masking current character. + if (!maskPassword) { + switch (style.case_) { + case StyleTextTransformCase::None: + break; - case StyleTextTransformCase::Lowercase: - if (languageSpecificCasing == eLSCB_Turkish) { - if (ch == 'I') { - ch = LATIN_SMALL_LETTER_DOTLESS_I; - prevIsLetter = true; - sigmaIndex = uint32_t(-1); - break; - } - if (ch == LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) { - ch = 'i'; - prevIsLetter = true; - sigmaIndex = uint32_t(-1); - break; - } - } - - if (languageSpecificCasing == eLSCB_Lithuanian) { - // clang-format off - /* From SpecialCasing.txt: - * # Introduce an explicit dot above when lowercasing capital I's and J's - * # whenever there are more accents above. - * # (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek) - * - * 0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I - * 004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J - * 012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK - * 00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE - * 00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE - * 0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE - */ - // clang-format on - if (ch == 'I' || ch == 'J' || ch == 0x012E) { - ch = ToLowerCase(ch); - prevIsLetter = true; - seenSoftDotted = true; - sigmaIndex = uint32_t(-1); - break; - } - if (ch == 0x00CC) { - aConvertedString.Append('i'); - aConvertedString.Append(0x0307); - extraChars += 2; - ch = 0x0300; - prevIsLetter = true; - seenSoftDotted = false; - sigmaIndex = uint32_t(-1); - break; - } - if (ch == 0x00CD) { - aConvertedString.Append('i'); - aConvertedString.Append(0x0307); - extraChars += 2; - ch = 0x0301; - prevIsLetter = true; - seenSoftDotted = false; - sigmaIndex = uint32_t(-1); - break; - } - if (ch == 0x0128) { - aConvertedString.Append('i'); - aConvertedString.Append(0x0307); - extraChars += 2; - ch = 0x0303; - prevIsLetter = true; - seenSoftDotted = false; - sigmaIndex = uint32_t(-1); - break; - } - } - - cat = mozilla::unicode::GetGenCategory(ch); - - if (languageSpecificCasing == eLSCB_Irish && - cat == nsUGenCategory::kLetter) { - // See bug 1018805 for Irish lowercasing requirements - if (!prevIsLetter && (ch == 'n' || ch == 't')) { - ntPrefix = true; - } else { - if (ntPrefix && mozilla::IrishCasing::IsUpperVowel(ch)) { - aConvertedString.Append('-'); - ++extraChars; + case StyleTextTransformCase::Lowercase: + if (languageSpecificCasing == eLSCB_Turkish) { + if (ch == 'I') { + ch = LATIN_SMALL_LETTER_DOTLESS_I; + prevIsLetter = true; + sigmaIndex = uint32_t(-1); + break; } + if (ch == LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE) { + ch = 'i'; + prevIsLetter = true; + sigmaIndex = uint32_t(-1); + break; + } + } + + if (languageSpecificCasing == eLSCB_Lithuanian) { + // clang-format off + /* From SpecialCasing.txt: + * # Introduce an explicit dot above when lowercasing capital I's and J's + * # whenever there are more accents above. + * # (of the accents used in Lithuanian: grave, acute, tilde above, and ogonek) + * + * 0049; 0069 0307; 0049; 0049; lt More_Above; # LATIN CAPITAL LETTER I + * 004A; 006A 0307; 004A; 004A; lt More_Above; # LATIN CAPITAL LETTER J + * 012E; 012F 0307; 012E; 012E; lt More_Above; # LATIN CAPITAL LETTER I WITH OGONEK + * 00CC; 0069 0307 0300; 00CC; 00CC; lt; # LATIN CAPITAL LETTER I WITH GRAVE + * 00CD; 0069 0307 0301; 00CD; 00CD; lt; # LATIN CAPITAL LETTER I WITH ACUTE + * 0128; 0069 0307 0303; 0128; 0128; lt; # LATIN CAPITAL LETTER I WITH TILDE + */ + // clang-format on + if (ch == 'I' || ch == 'J' || ch == 0x012E) { + ch = ToLowerCase(ch); + prevIsLetter = true; + seenSoftDotted = true; + sigmaIndex = uint32_t(-1); + break; + } + if (ch == 0x00CC) { + aConvertedString.Append('i'); + aConvertedString.Append(0x0307); + extraChars += 2; + ch = 0x0300; + prevIsLetter = true; + seenSoftDotted = false; + sigmaIndex = uint32_t(-1); + break; + } + if (ch == 0x00CD) { + aConvertedString.Append('i'); + aConvertedString.Append(0x0307); + extraChars += 2; + ch = 0x0301; + prevIsLetter = true; + seenSoftDotted = false; + sigmaIndex = uint32_t(-1); + break; + } + if (ch == 0x0128) { + aConvertedString.Append('i'); + aConvertedString.Append(0x0307); + extraChars += 2; + ch = 0x0303; + prevIsLetter = true; + seenSoftDotted = false; + sigmaIndex = uint32_t(-1); + break; + } + } + + cat = mozilla::unicode::GetGenCategory(ch); + + if (languageSpecificCasing == eLSCB_Irish && + cat == nsUGenCategory::kLetter) { + // See bug 1018805 for Irish lowercasing requirements + if (!prevIsLetter && (ch == 'n' || ch == 't')) { + ntPrefix = true; + } else { + if (ntPrefix && mozilla::IrishCasing::IsUpperVowel(ch)) { + aConvertedString.Append('-'); + ++extraChars; + } + ntPrefix = false; + } + } else { ntPrefix = false; } - } else { - ntPrefix = false; - } - if (seenSoftDotted && cat == nsUGenCategory::kMark) { - // The seenSoftDotted flag will only be set in Lithuanian mode. - if (ch == 0x0300 || ch == 0x0301 || ch == 0x0303) { - aConvertedString.Append(0x0307); - ++extraChars; + if (seenSoftDotted && cat == nsUGenCategory::kMark) { + // The seenSoftDotted flag will only be set in Lithuanian mode. + if (ch == 0x0300 || ch == 0x0301 || ch == 0x0303) { + aConvertedString.Append(0x0307); + ++extraChars; + } } - } - seenSoftDotted = false; + seenSoftDotted = false; - // Special lowercasing behavior for Greek Sigma: note that this is - // listed as context-sensitive in Unicode's SpecialCasing.txt, but is - // *not* a language-specific mapping; it applies regardless of the - // language of the element. - // - // The lowercase mapping for CAPITAL SIGMA should be to SMALL SIGMA - // (i.e. the non-final form) whenever there is a following letter, or - // when the CAPITAL SIGMA occurs in isolation (neither preceded nor - // followed by a LETTER); and to FINAL SIGMA when it is preceded by - // another letter but not followed by one. - // - // To implement the context-sensitive nature of this mapping, we keep - // track of whether the previous character was a letter. If not, CAPITAL - // SIGMA will map directly to SMALL SIGMA. If the previous character - // was a letter, CAPITAL SIGMA maps to FINAL SIGMA and we record the - // position in the converted string; if we then encounter another - // letter, that FINAL SIGMA is replaced with a standard SMALL SIGMA. + // Special lowercasing behavior for Greek Sigma: note that this is + // listed as context-sensitive in Unicode's SpecialCasing.txt, but is + // *not* a language-specific mapping; it applies regardless of the + // language of the element. + // + // The lowercase mapping for CAPITAL SIGMA should be to SMALL SIGMA + // (i.e. the non-final form) whenever there is a following letter, or + // when the CAPITAL SIGMA occurs in isolation (neither preceded nor + // followed by a LETTER); and to FINAL SIGMA when it is preceded by + // another letter but not followed by one. + // + // To implement the context-sensitive nature of this mapping, we keep + // track of whether the previous character was a letter. If not, + // CAPITAL SIGMA will map directly to SMALL SIGMA. If the previous + // character was a letter, CAPITAL SIGMA maps to FINAL SIGMA and we + // record the position in the converted string; if we then encounter + // another letter, that FINAL SIGMA is replaced with a standard + // SMALL SIGMA. - // If sigmaIndex is not -1, it marks where we have provisionally mapped - // a CAPITAL SIGMA to FINAL SIGMA; if we now find another letter, we - // need to change it to SMALL SIGMA. - if (sigmaIndex != uint32_t(-1)) { - if (cat == nsUGenCategory::kLetter) { - aConvertedString.SetCharAt(GREEK_SMALL_LETTER_SIGMA, sigmaIndex); + // If sigmaIndex is not -1, it marks where we have provisionally + // mapped a CAPITAL SIGMA to FINAL SIGMA; if we now find another + // letter, we need to change it to SMALL SIGMA. + if (sigmaIndex != uint32_t(-1)) { + if (cat == nsUGenCategory::kLetter) { + aConvertedString.SetCharAt(GREEK_SMALL_LETTER_SIGMA, sigmaIndex); + } } - } - if (ch == GREEK_CAPITAL_LETTER_SIGMA) { - // If preceding char was a letter, map to FINAL instead of SMALL, - // and note where it occurred by setting sigmaIndex; we'll change it - // to standard SMALL SIGMA later if another letter follows - if (prevIsLetter) { - ch = GREEK_SMALL_LETTER_FINAL_SIGMA; - sigmaIndex = aConvertedString.Length(); - } else { - // CAPITAL SIGMA not preceded by a letter is unconditionally mapped - // to SMALL SIGMA - ch = GREEK_SMALL_LETTER_SIGMA; + if (ch == GREEK_CAPITAL_LETTER_SIGMA) { + // If preceding char was a letter, map to FINAL instead of SMALL, + // and note where it occurred by setting sigmaIndex; we'll change + // it to standard SMALL SIGMA later if another letter follows + if (prevIsLetter) { + ch = GREEK_SMALL_LETTER_FINAL_SIGMA; + sigmaIndex = aConvertedString.Length(); + } else { + // CAPITAL SIGMA not preceded by a letter is unconditionally + // mapped to SMALL SIGMA + ch = GREEK_SMALL_LETTER_SIGMA; + sigmaIndex = uint32_t(-1); + } + prevIsLetter = true; + break; + } + + // ignore diacritics for the purpose of contextual sigma mapping; + // otherwise, reset prevIsLetter appropriately and clear the + // sigmaIndex marker + if (cat != nsUGenCategory::kMark) { + prevIsLetter = (cat == nsUGenCategory::kLetter); sigmaIndex = uint32_t(-1); } - prevIsLetter = true; - break; - } - // ignore diacritics for the purpose of contextual sigma mapping; - // otherwise, reset prevIsLetter appropriately and clear the - // sigmaIndex marker - if (cat != nsUGenCategory::kMark) { - prevIsLetter = (cat == nsUGenCategory::kLetter); - sigmaIndex = uint32_t(-1); - } - - mcm = mozilla::unicode::SpecialLower(ch); - if (mcm) { - int j = 0; - while (j < 2 && mcm->mMappedChars[j + 1]) { - aConvertedString.Append(mcm->mMappedChars[j]); - ++extraChars; - ++j; - } - ch = mcm->mMappedChars[j]; - break; - } - - ch = ToLowerCase(ch); - break; - - case StyleTextTransformCase::Uppercase: - if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') { - ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE; - break; - } - - if (languageSpecificCasing == eLSCB_Greek) { - bool markEta; - bool updateEta; - ch = mozilla::GreekCasing::UpperCase(ch, greekState, markEta, - updateEta); - if (markEta) { - greekMark = aConvertedString.Length(); - } else if (updateEta) { - // Remove the TONOS from an uppercase ETA-TONOS that turned out - // not to be disjunctive-eta. - MOZ_ASSERT(aConvertedString.Length() > 0 && - greekMark < aConvertedString.Length(), - "bad greekMark!"); - aConvertedString.SetCharAt(kGreekUpperEta, greekMark); - greekMark = uint32_t(-1); - } - break; - } - - if (languageSpecificCasing == eLSCB_Lithuanian) { - /* - * # Remove DOT ABOVE after "i" with upper or titlecase - * - * 0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE - */ - if (ch == 'i' || ch == 'j' || ch == 0x012F) { - seenSoftDotted = true; - ch = ToTitleCase(ch); + mcm = mozilla::unicode::SpecialLower(ch); + if (mcm) { + int j = 0; + while (j < 2 && mcm->mMappedChars[j + 1]) { + aConvertedString.Append(mcm->mMappedChars[j]); + ++extraChars; + ++j; + } + ch = mcm->mMappedChars[j]; break; } - if (seenSoftDotted) { - seenSoftDotted = false; - if (ch == 0x0307) { - ch = uint32_t(-1); + + ch = ToLowerCase(ch); + break; + + case StyleTextTransformCase::Uppercase: + if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') { + ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE; + break; + } + + if (languageSpecificCasing == eLSCB_Greek) { + bool markEta; + bool updateEta; + ch = mozilla::GreekCasing::UpperCase(ch, greekState, markEta, + updateEta); + if (markEta) { + greekMark = aConvertedString.Length(); + } else if (updateEta) { + // Remove the TONOS from an uppercase ETA-TONOS that turned out + // not to be disjunctive-eta. + MOZ_ASSERT(aConvertedString.Length() > 0 && + greekMark < aConvertedString.Length(), + "bad greekMark!"); + aConvertedString.SetCharAt(kGreekUpperEta, greekMark); + greekMark = uint32_t(-1); + } + break; + } + + if (languageSpecificCasing == eLSCB_Lithuanian) { + /* + * # Remove DOT ABOVE after "i" with upper or titlecase + * + * 0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE + */ + if (ch == 'i' || ch == 'j' || ch == 0x012F) { + seenSoftDotted = true; + ch = ToTitleCase(ch); break; } - } - } - - if (languageSpecificCasing == eLSCB_Irish) { - bool mark; - uint8_t action; - ch = mozilla::IrishCasing::UpperCase(ch, irishState, mark, action); - if (mark) { - irishMark = aConvertedString.Length(); - irishMarkSrc = i; - break; - } else if (action) { - nsString& str = aConvertedString; // shorthand - switch (action) { - case 1: - // lowercase a single prefix letter - NS_ASSERTION(str.Length() > 0 && irishMark < str.Length(), - "bad irishMark!"); - str.SetCharAt(ToLowerCase(str[irishMark]), irishMark); - irishMark = uint32_t(-1); - irishMarkSrc = uint32_t(-1); - break; - case 2: - // lowercase two prefix letters (immediately before current pos) - NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2, - "bad irishMark!"); - str.SetCharAt(ToLowerCase(str[irishMark]), irishMark); - str.SetCharAt(ToLowerCase(str[irishMark + 1]), irishMark + 1); - irishMark = uint32_t(-1); - irishMarkSrc = uint32_t(-1); - break; - case 3: - // lowercase one prefix letter, and delete following hyphen - // (which must be the immediately-preceding char) - NS_ASSERTION(str.Length() >= 2 && irishMark == str.Length() - 2, - "bad irishMark!"); - MOZ_ASSERT( - irishMark != uint32_t(-1) && irishMarkSrc != uint32_t(-1), - "failed to set irishMarks"); - str.Replace(irishMark, 2, ToLowerCase(str[irishMark])); - aDeletedCharsArray[irishMarkSrc + 1] = true; - // Remove the trailing entries (corresponding to the deleted - // hyphen) from the auxiliary arrays. - aCharsToMergeArray.SetLength(aCharsToMergeArray.Length() - 1); - if (auxiliaryOutputArrays) { - aStyleArray->SetLength(aStyleArray->Length() - 1); - aCanBreakBeforeArray->SetLength( - aCanBreakBeforeArray->Length() - 1); - inhibitBreakBefore = true; - } - mergeNeeded = true; - irishMark = uint32_t(-1); - irishMarkSrc = uint32_t(-1); - break; - } - // ch has been set to the uppercase for current char; - // No need to check for SpecialUpper here as none of the characters - // that could trigger an Irish casing action have special mappings. - break; - } - // If we didn't have any special action to perform, fall through - // to check for special uppercase (ß) - } - - mcm = mozilla::unicode::SpecialUpper(ch); - if (mcm) { - int j = 0; - while (j < 2 && mcm->mMappedChars[j + 1]) { - aConvertedString.Append(mcm->mMappedChars[j]); - ++extraChars; - ++j; - } - ch = mcm->mMappedChars[j]; - break; - } - - // Bug 1476304: we exclude Georgian letters U+10D0..10FF because of lack - // of widespread font support for the corresponding Mtavruli characters - // at this time (July 2018). - // This condition is to be removed once the major platforms ship with - // fonts that support U+1C90..1CBF. - if (ch < 0x10D0 || ch > 0x10FF) { - ch = ToUpperCase(ch); - } - break; - - case StyleTextTransformCase::Capitalize: - if (aTextRun) { - if (capitalizeDutchIJ && ch == 'j') { - ch = 'J'; - capitalizeDutchIJ = false; - break; - } - capitalizeDutchIJ = false; - if (aOffsetInTextRun < aTextRun->mCapitalize.Length() && - aTextRun->mCapitalize[aOffsetInTextRun]) { - if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') { - ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE; - break; - } - if (languageSpecificCasing == eLSCB_Dutch && ch == 'i') { - ch = 'I'; - capitalizeDutchIJ = true; - break; - } - if (languageSpecificCasing == eLSCB_Lithuanian) { - /* - * # Remove DOT ABOVE after "i" with upper or titlecase - * - * 0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE - */ - if (ch == 'i' || ch == 'j' || ch == 0x012F) { - seenSoftDotted = true; - ch = ToTitleCase(ch); + if (seenSoftDotted) { + seenSoftDotted = false; + if (ch == 0x0307) { + ch = uint32_t(-1); break; } - if (seenSoftDotted) { - seenSoftDotted = false; - if (ch == 0x0307) { - ch = uint32_t(-1); + } + } + + if (languageSpecificCasing == eLSCB_Irish) { + bool mark; + uint8_t action; + ch = mozilla::IrishCasing::UpperCase(ch, irishState, mark, action); + if (mark) { + irishMark = aConvertedString.Length(); + irishMarkSrc = i; + break; + } else if (action) { + nsString& str = aConvertedString; // shorthand + switch (action) { + case 1: + // lowercase a single prefix letter + NS_ASSERTION(str.Length() > 0 && irishMark < str.Length(), + "bad irishMark!"); + str.SetCharAt(ToLowerCase(str[irishMark]), irishMark); + irishMark = uint32_t(-1); + irishMarkSrc = uint32_t(-1); + break; + case 2: + // lowercase two prefix letters (immediately before current + // pos) + NS_ASSERTION( + str.Length() >= 2 && irishMark == str.Length() - 2, + "bad irishMark!"); + str.SetCharAt(ToLowerCase(str[irishMark]), irishMark); + str.SetCharAt(ToLowerCase(str[irishMark + 1]), irishMark + 1); + irishMark = uint32_t(-1); + irishMarkSrc = uint32_t(-1); + break; + case 3: + // lowercase one prefix letter, and delete following hyphen + // (which must be the immediately-preceding char) + NS_ASSERTION( + str.Length() >= 2 && irishMark == str.Length() - 2, + "bad irishMark!"); + MOZ_ASSERT( + irishMark != uint32_t(-1) && irishMarkSrc != uint32_t(-1), + "failed to set irishMarks"); + str.Replace(irishMark, 2, ToLowerCase(str[irishMark])); + aDeletedCharsArray[irishMarkSrc + 1] = true; + // Remove the trailing entries (corresponding to the deleted + // hyphen) from the auxiliary arrays. + aCharsToMergeArray.SetLength(aCharsToMergeArray.Length() - 1); + if (auxiliaryOutputArrays) { + aStyleArray->SetLength(aStyleArray->Length() - 1); + aCanBreakBeforeArray->SetLength( + aCanBreakBeforeArray->Length() - 1); + inhibitBreakBefore = true; + } + mergeNeeded = true; + irishMark = uint32_t(-1); + irishMarkSrc = uint32_t(-1); + break; + } + // ch has been set to the uppercase for current char; + // No need to check for SpecialUpper here as none of the + // characters that could trigger an Irish casing action have + // special mappings. + break; + } + // If we didn't have any special action to perform, fall through + // to check for special uppercase (ß) + } + + mcm = mozilla::unicode::SpecialUpper(ch); + if (mcm) { + int j = 0; + while (j < 2 && mcm->mMappedChars[j + 1]) { + aConvertedString.Append(mcm->mMappedChars[j]); + ++extraChars; + ++j; + } + ch = mcm->mMappedChars[j]; + break; + } + + // Bug 1476304: we exclude Georgian letters U+10D0..10FF because of + // lack of widespread font support for the corresponding Mtavruli + // characters at this time (July 2018). + // This condition is to be removed once the major platforms ship with + // fonts that support U+1C90..1CBF. + if (ch < 0x10D0 || ch > 0x10FF) { + ch = ToUpperCase(ch); + } + break; + + case StyleTextTransformCase::Capitalize: + if (aTextRun) { + if (capitalizeDutchIJ && ch == 'j') { + ch = 'J'; + capitalizeDutchIJ = false; + break; + } + capitalizeDutchIJ = false; + if (aOffsetInTextRun < aTextRun->mCapitalize.Length() && + aTextRun->mCapitalize[aOffsetInTextRun]) { + if (languageSpecificCasing == eLSCB_Turkish && ch == 'i') { + ch = LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE; + break; + } + if (languageSpecificCasing == eLSCB_Dutch && ch == 'i') { + ch = 'I'; + capitalizeDutchIJ = true; + break; + } + if (languageSpecificCasing == eLSCB_Lithuanian) { + /* + * # Remove DOT ABOVE after "i" with upper or titlecase + * + * 0307; 0307; ; ; lt After_Soft_Dotted; # COMBINING DOT ABOVE + */ + if (ch == 'i' || ch == 'j' || ch == 0x012F) { + seenSoftDotted = true; + ch = ToTitleCase(ch); break; } + if (seenSoftDotted) { + seenSoftDotted = false; + if (ch == 0x0307) { + ch = uint32_t(-1); + break; + } + } } - } - mcm = mozilla::unicode::SpecialTitle(ch); - if (mcm) { - int j = 0; - while (j < 2 && mcm->mMappedChars[j + 1]) { - aConvertedString.Append(mcm->mMappedChars[j]); - ++extraChars; - ++j; + mcm = mozilla::unicode::SpecialTitle(ch); + if (mcm) { + int j = 0; + while (j < 2 && mcm->mMappedChars[j + 1]) { + aConvertedString.Append(mcm->mMappedChars[j]); + ++extraChars; + ++j; + } + ch = mcm->mMappedChars[j]; + break; } - ch = mcm->mMappedChars[j]; - break; - } - ch = ToTitleCase(ch); + ch = ToTitleCase(ch); + } + } + break; + + default: + MOZ_ASSERT_UNREACHABLE("all cases should be handled"); + break; + } + + if (!aCaseTransformsOnly) { + if (!forceNonFullWidth && + (style.other_ & StyleTextTransformOther_FULL_WIDTH)) { + ch = mozilla::unicode::GetFullWidth(ch); + } + + if (style.other_ & StyleTextTransformOther_FULL_SIZE_KANA) { + // clang-format off + static const uint16_t kSmallKanas[] = { + // ぁ ぃ ぅ ぇ ぉ っ ゃ ゅ ょ + 0x3041, 0x3043, 0x3045, 0x3047, 0x3049, 0x3063, 0x3083, 0x3085, 0x3087, + // ゎ ゕ ゖ + 0x308E, 0x3095, 0x3096, + // ァ ィ ゥ ェ ォ ッ ャ ュ ョ + 0x30A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30C3, 0x30E3, 0x30E5, 0x30E7, + // ヮ ヵ ヶ ㇰ ㇱ ㇲ ㇳ ㇴ ㇵ + 0x30EE, 0x30F5, 0x30F6, 0x31F0, 0x31F1, 0x31F2, 0x31F3, 0x31F4, 0x31F5, + // ㇶ ㇷ ㇸ ㇹ ㇺ ㇻ ㇼ ㇽ ㇾ + 0x31F6, 0x31F7, 0x31F8, 0x31F9, 0x31FA, 0x31FB, 0x31FC, 0x31FD, 0x31FE, + // ㇿ + 0x31FF, + // ァ ィ ゥ ェ ォ ャ ュ ョ ッ + 0xFF67, 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F}; + static const uint16_t kFullSizeKanas[] = { + // あ い う え お つ や ゆ よ + 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x3064, 0x3084, 0x3086, 0x3088, + // わ か け + 0x308F, 0x304B, 0x3051, + // ア イ ウ エ オ ツ ヤ ユ ヨ + 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30C4, 0x30E4, 0x30E6, 0x30E8, + // ワ カ ケ ク シ ス ト ヌ ハ + 0x30EF, 0x30AB, 0x30B1, 0x30AF, 0x30B7, 0x30B9, 0x30C8, 0x30CC, 0x30CF, + // ヒ フ ヘ ホ ム ラ リ ル レ + 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30E0, 0x30E9, 0x30EA, 0x30EB, 0x30EC, + // ロ + 0x30ED, + // ア イ ウ エ オ ヤ ユ ヨ ツ + 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF94, 0xFF95, 0xFF96, 0xFF82}; + // clang-format on + + size_t index; + const uint16_t len = MOZ_ARRAY_LENGTH(kSmallKanas); + if (mozilla::BinarySearch(kSmallKanas, 0, len, ch, &index)) { + ch = kFullSizeKanas[index]; } } - break; - - default: - MOZ_ASSERT_UNREACHABLE("all cases should be handled"); - break; - } - - if (!aCaseTransformsOnly) { - if (!forceNonFullWidth && - (style.other_ & StyleTextTransformOther_FULL_WIDTH)) { - ch = mozilla::unicode::GetFullWidth(ch); } - if (style.other_ & StyleTextTransformOther_FULL_SIZE_KANA) { - // clang-format off - static const uint16_t kSmallKanas[] = { - // ぁ ぃ ぅ ぇ ぉ っ ゃ ゅ ょ - 0x3041, 0x3043, 0x3045, 0x3047, 0x3049, 0x3063, 0x3083, 0x3085, 0x3087, - // ゎ ゕ ゖ - 0x308E, 0x3095, 0x3096, - // ァ ィ ゥ ェ ォ ッ ャ ュ ョ - 0x30A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30C3, 0x30E3, 0x30E5, 0x30E7, - // ヮ ヵ ヶ ㇰ ㇱ ㇲ ㇳ ㇴ ㇵ - 0x30EE, 0x30F5, 0x30F6, 0x31F0, 0x31F1, 0x31F2, 0x31F3, 0x31F4, 0x31F5, - // ㇶ ㇷ ㇸ ㇹ ㇺ ㇻ ㇼ ㇽ ㇾ - 0x31F6, 0x31F7, 0x31F8, 0x31F9, 0x31FA, 0x31FB, 0x31FC, 0x31FD, 0x31FE, - // ㇿ - 0x31FF, - // ァ ィ ゥ ェ ォ ャ ュ ョ ッ - 0xFF67, 0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F}; - static const uint16_t kFullSizeKanas[] = { - // あ い う え お つ や ゆ よ - 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x3064, 0x3084, 0x3086, 0x3088, - // わ か け - 0x308F, 0x304B, 0x3051, - // ア イ ウ エ オ ツ ヤ ユ ヨ - 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30C4, 0x30E4, 0x30E6, 0x30E8, - // ワ カ ケ ク シ ス ト ヌ ハ - 0x30EF, 0x30AB, 0x30B1, 0x30AF, 0x30B7, 0x30B9, 0x30C8, 0x30CC, 0x30CF, - // ヒ フ ヘ ホ ム ラ リ ル レ - 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30E0, 0x30E9, 0x30EA, 0x30EB, 0x30EC, - // ロ - 0x30ED, - // ア イ ウ エ オ ヤ ユ ヨ ツ - 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF94, 0xFF95, 0xFF96, 0xFF82}; - // clang-format on - - size_t index; - const uint16_t len = MOZ_ARRAY_LENGTH(kSmallKanas); - if (mozilla::BinarySearch(kSmallKanas, 0, len, ch, &index)) { - ch = kFullSizeKanas[index]; - } + if (forceNonFullWidth) { + ch = mozilla::unicode::GetFullWidthInverse(ch); } } - if (forceNonFullWidth) { - ch = mozilla::unicode::GetFullWidthInverse(ch); - } - if (ch == uint32_t(-1)) { aDeletedCharsArray.AppendElement(true); mergeNeeded = true; @@ -767,16 +784,21 @@ bool nsCaseTransformTextRunFactory::TransformString( } if (IS_IN_BMP(ch)) { - aConvertedString.Append(ch); + aConvertedString.Append(maskPassword ? kPasswordMask : ch); } else { - aConvertedString.Append(H_SURROGATE(ch)); - aConvertedString.Append(L_SURROGATE(ch)); - i++; - aOffsetInTextRun++; - aDeletedCharsArray.AppendElement( - true); // not exactly deleted, but the - // trailing surrogate is skipped + if (maskPassword) { + aConvertedString.Append(kPasswordMask); + // TODO: We should show a password mask for a surrogate pair later. + aConvertedString.Append(kPasswordMask); + } else { + aConvertedString.Append(H_SURROGATE(ch)); + aConvertedString.Append(L_SURROGATE(ch)); + } ++extraChars; + ++i; + ++aOffsetInTextRun; + // Skip the trailing surrogate. + aDeletedCharsArray.AppendElement(true); } while (extraChars-- > 0) { diff --git a/layout/generic/nsTextRunTransformations.h b/layout/generic/nsTextRunTransformations.h index 6d8061d04c31..c388239639f1 100644 --- a/layout/generic/nsTextRunTransformations.h +++ b/layout/generic/nsTextRunTransformations.h @@ -38,6 +38,7 @@ struct nsTransformedCharStyle final { uint8_t mMathVariant; bool mExplicitLanguage; bool mForceNonFullWidth = false; + bool mMaskPassword = false; private: ~nsTransformedCharStyle() {} @@ -105,6 +106,12 @@ class nsCaseTransformTextRunFactory : public nsTransformingTextRunFactory { // these are ignored. // If aCaseTransformsOnly is true, then only the upper/lower/capitalize // transformations are performed; full-width and full-size-kana are ignored. + // If `aTextRun` is not nullptr and characters which are styled with setting + // `nsTransformedCharStyle::mMaskPassword` to true, they are replaced with + // password mask characters and are not transformed (i.e., won't be added + // or merged for the specified transform). However, unmasked characters + // whose `nsTransformedCharStyle::mMaskPassword` is set to false are + // transformed normally. static bool TransformString( const nsAString& aString, nsString& aConvertedString, bool aAllUppercase, bool aCaseTransformsOnly, const nsAtom* aLanguage, diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index 246aa3b39f18..32e03a8a2145 100644 --- a/layout/style/res/forms.css +++ b/layout/style/res/forms.css @@ -173,6 +173,17 @@ input > .preview-div { white-space: pre; } +input[type=password] > .anonymous-div, +input[type=password] > .preview-div { + /* + * In password fields, any character should be put same direction. Otherwise, + * caret position at typing tells everybody whether the character is an RTL + * or an LTR character. Unfortunately, this makes odd rendering when bidi + * text is unmasked. + */ + unicode-bidi: bidi-override; +} + textarea > .anonymous-div { scroll-behavior: inherit; overscroll-behavior: inherit;