Bug 1866777 - Disable test_race_cache_with_network.js on windows opt for frequent...
[gecko.git] / gfx / thebes / gfxGraphiteShaper.cpp
blob7021ffb36209e9b2f81f978f6a38fc43a41e87d4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "gfxGraphiteShaper.h"
7 #include "nsString.h"
8 #include "gfxContext.h"
9 #include "gfxFontConstants.h"
10 #include "gfxTextRun.h"
12 #include "graphite2/Font.h"
13 #include "graphite2/GraphiteExtra.h"
14 #include "graphite2/Segment.h"
16 #include "harfbuzz/hb.h"
18 #include "mozilla/ScopeExit.h"
20 #include "ThebesRLBox.h"
22 #define FloatToFixed(f) (65536 * (f))
23 #define FixedToFloat(f) ((f) * (1.0 / 65536.0))
24 // Right shifts of negative (signed) integers are undefined, as are overflows
25 // when converting unsigned to negative signed integers.
26 // (If speed were an issue we could make some 2's complement assumptions.)
27 #define FixedToIntRound(f) \
28 ((f) > 0 ? ((32768 + (f)) >> 16) : -((32767 - (f)) >> 16))
30 #define CopyAndVerifyOrFail(t, cond, failed) \
31 (t).copy_and_verify([&](auto val) { \
32 if (!(cond)) { \
33 *(failed) = true; \
34 } \
35 return val; \
38 using namespace mozilla; // for AutoSwap_* types
41 * Creation and destruction; on deletion, release any font tables we're holding
44 gfxGraphiteShaper::gfxGraphiteShaper(gfxFont* aFont)
45 : gfxFontShaper(aFont),
46 mGrFace(mFont->GetFontEntry()->GetGrFace()),
47 mSandbox(mFont->GetFontEntry()->GetGrSandbox()),
48 mCallback(mFont->GetFontEntry()->GetGrSandboxAdvanceCallbackHandle()),
49 mFallbackToSmallCaps(false) {
50 mCallbackData.mFont = aFont;
53 gfxGraphiteShaper::~gfxGraphiteShaper() {
54 auto t_mGrFont = rlbox::from_opaque(mGrFont);
55 if (t_mGrFont) {
56 sandbox_invoke(*mSandbox, gr_font_destroy, t_mGrFont);
58 mFont->GetFontEntry()->ReleaseGrFace(mGrFace);
61 /*static*/
62 thread_local gfxGraphiteShaper::CallbackData*
63 gfxGraphiteShaper::tl_GrGetAdvanceData = nullptr;
65 /*static*/
66 tainted_opaque_gr<float> gfxGraphiteShaper::GrGetAdvance(
67 rlbox_sandbox_gr& sandbox,
68 tainted_opaque_gr<const void*> /* appFontHandle */,
69 tainted_opaque_gr<uint16_t> t_glyphid) {
70 CallbackData* cb = tl_GrGetAdvanceData;
71 if (!cb) {
72 // GrGetAdvance callback called unexpectedly. Just return safe value.
73 tainted_gr<float> ret = 0;
74 return ret.to_opaque();
76 auto glyphid = rlbox::from_opaque(t_glyphid).unverified_safe_because(
77 "Here the only use of a glyphid is for lookup to get a width. "
78 "Implementations of GetGlyphWidth in this code base use a hashtable "
79 "which is robust to unknown keys. So no validation is required.");
80 tainted_gr<float> ret = FixedToFloat(cb->mFont->GetGlyphWidth(glyphid));
81 return ret.to_opaque();
84 static inline uint32_t MakeGraphiteLangTag(uint32_t aTag) {
85 uint32_t grLangTag = aTag;
86 // replace trailing space-padding with NULs for graphite
87 uint32_t mask = 0x000000FF;
88 while ((grLangTag & mask) == ' ') {
89 grLangTag &= ~mask;
90 mask <<= 8;
92 return grLangTag;
95 struct GrFontFeatures {
96 tainted_gr<gr_face*> mFace;
97 tainted_gr<gr_feature_val*> mFeatures;
98 rlbox_sandbox_gr* mSandbox;
101 static void AddFeature(uint32_t aTag, uint32_t aValue, void* aUserArg) {
102 GrFontFeatures* f = static_cast<GrFontFeatures*>(aUserArg);
104 tainted_gr<const gr_feature_ref*> fref =
105 sandbox_invoke(*(f->mSandbox), gr_face_find_fref, f->mFace, aTag);
106 if (fref) {
107 sandbox_invoke(*(f->mSandbox), gr_fref_set_feature_value, fref, aValue,
108 f->mFeatures);
112 // Count the number of Unicode characters in a UTF-16 string (i.e. surrogate
113 // pairs are counted as 1, although they are 2 code units).
114 // (Any isolated surrogates will count 1 each, because in decoding they would
115 // be replaced by individual U+FFFD REPLACEMENT CHARACTERs.)
116 static inline size_t CountUnicodes(const char16_t* aText, uint32_t aLength) {
117 size_t total = 0;
118 const char16_t* end = aText + aLength;
119 while (aText < end) {
120 if (NS_IS_HIGH_SURROGATE(*aText) && aText + 1 < end &&
121 NS_IS_LOW_SURROGATE(*(aText + 1))) {
122 aText += 2;
123 } else {
124 aText++;
126 total++;
128 return total;
131 bool gfxGraphiteShaper::ShapeText(DrawTarget* aDrawTarget,
132 const char16_t* aText, uint32_t aOffset,
133 uint32_t aLength, Script aScript,
134 nsAtom* aLanguage, bool aVertical,
135 RoundingFlags aRounding,
136 gfxShapedText* aShapedText) {
137 const gfxFontStyle* style = mFont->GetStyle();
138 auto t_mGrFace = rlbox::from_opaque(mGrFace);
139 auto t_mGrFont = rlbox::from_opaque(mGrFont);
141 if (!t_mGrFont) {
142 if (!t_mGrFace) {
143 return false;
146 if (mFont->ProvidesGlyphWidths()) {
147 auto p_ops = mSandbox->malloc_in_sandbox<gr_font_ops>();
148 if (!p_ops) {
149 return false;
151 auto clean_ops = MakeScopeExit([&] { mSandbox->free_in_sandbox(p_ops); });
152 p_ops->size = sizeof(*p_ops);
153 p_ops->glyph_advance_x = *mCallback;
154 p_ops->glyph_advance_y = nullptr; // vertical text not yet implemented
155 t_mGrFont = sandbox_invoke(
156 *mSandbox, gr_make_font_with_ops, mFont->GetAdjustedSize(),
157 // For security, we do not pass the callback data to this arg, and use
158 // a TLS var instead. However, gr_make_font_with_ops expects this to
159 // be a non null ptr, and changes its behavior if it isn't. Therefore,
160 // we should pass some dummy non null pointer which will be passed to
161 // the GrGetAdvance callback, but never used. Let's just pass p_ops
162 // again, as this is a non-null tainted pointer.
163 p_ops /* mCallbackData */, p_ops, t_mGrFace);
164 } else {
165 t_mGrFont = sandbox_invoke(*mSandbox, gr_make_font,
166 mFont->GetAdjustedSize(), t_mGrFace);
168 mGrFont = t_mGrFont.to_opaque();
170 if (!t_mGrFont) {
171 return false;
174 // determine whether petite-caps falls back to small-caps
175 if (style->variantCaps != NS_FONT_VARIANT_CAPS_NORMAL) {
176 switch (style->variantCaps) {
177 case NS_FONT_VARIANT_CAPS_ALLPETITE:
178 case NS_FONT_VARIANT_CAPS_PETITECAPS:
179 bool synLower, synUpper;
180 mFont->SupportsVariantCaps(aScript, style->variantCaps,
181 mFallbackToSmallCaps, synLower, synUpper);
182 break;
183 default:
184 break;
189 gfxFontEntry* entry = mFont->GetFontEntry();
190 uint32_t grLang = 0;
191 if (style->languageOverride) {
192 grLang = MakeGraphiteLangTag(style->languageOverride);
193 } else if (entry->mLanguageOverride) {
194 grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
195 } else if (aLanguage) {
196 nsAutoCString langString;
197 aLanguage->ToUTF8String(langString);
198 grLang = GetGraphiteTagForLang(langString);
200 tainted_gr<gr_feature_val*> grFeatures =
201 sandbox_invoke(*mSandbox, gr_face_featureval_for_lang, t_mGrFace, grLang);
203 // insert any merged features into Graphite feature list
204 GrFontFeatures f = {t_mGrFace, grFeatures, mSandbox};
205 MergeFontFeatures(style, mFont->GetFontEntry()->mFeatureSettings,
206 aShapedText->DisableLigatures(),
207 mFont->GetFontEntry()->FamilyName(), mFallbackToSmallCaps,
208 AddFeature, &f);
210 // Graphite shaping doesn't map U+00a0 (nbsp) to space if it is missing
211 // from the font, so check for that possibility. (Most fonts double-map
212 // the space glyph to both 0x20 and 0xA0, so this won't often be needed;
213 // so we don't copy the text until we know it's required.)
214 nsAutoString transformed;
215 const char16_t NO_BREAK_SPACE = 0x00a0;
216 if (!entry->HasCharacter(NO_BREAK_SPACE)) {
217 nsDependentSubstring src(aText, aLength);
218 if (src.FindChar(NO_BREAK_SPACE) != kNotFound) {
219 transformed = src;
220 transformed.ReplaceChar(NO_BREAK_SPACE, ' ');
221 aText = transformed.BeginReading();
225 size_t numChars = CountUnicodes(aText, aLength);
226 gr_bidirtl grBidi = gr_bidirtl(
227 aShapedText->IsRightToLeft() ? (gr_rtl | gr_nobidi) : gr_nobidi);
229 tainted_gr<char16_t*> t_aText =
230 mSandbox->malloc_in_sandbox<char16_t>(aLength);
231 if (!t_aText) {
232 return false;
234 auto clean_txt = MakeScopeExit([&] { mSandbox->free_in_sandbox(t_aText); });
236 rlbox::memcpy(*mSandbox, t_aText, aText, aLength * sizeof(char16_t));
238 tl_GrGetAdvanceData = &mCallbackData;
239 auto clean_adv_data = MakeScopeExit([&] { tl_GrGetAdvanceData = nullptr; });
241 tainted_gr<gr_segment*> seg =
242 sandbox_invoke(*mSandbox, gr_make_seg, mGrFont, t_mGrFace, 0, grFeatures,
243 gr_utf16, t_aText, numChars, grBidi);
245 sandbox_invoke(*mSandbox, gr_featureval_destroy, grFeatures);
247 if (!seg) {
248 return false;
251 nsresult rv =
252 SetGlyphsFromSegment(aShapedText, aOffset, aLength, aText,
253 t_aText.to_opaque(), seg.to_opaque(), aRounding);
255 sandbox_invoke(*mSandbox, gr_seg_destroy, seg);
257 return NS_SUCCEEDED(rv);
260 nsresult gfxGraphiteShaper::SetGlyphsFromSegment(
261 gfxShapedText* aShapedText, uint32_t aOffset, uint32_t aLength,
262 const char16_t* aText, tainted_opaque_gr<char16_t*> t_aText,
263 tainted_opaque_gr<gr_segment*> aSegment, RoundingFlags aRounding) {
264 typedef gfxShapedText::CompressedGlyph CompressedGlyph;
266 int32_t dev2appUnits = aShapedText->GetAppUnitsPerDevUnit();
267 bool rtl = aShapedText->IsRightToLeft();
269 // identify clusters; graphite may have reordered/expanded/ligated glyphs.
270 tainted_gr<gr_glyph_to_char_association*> data =
271 sandbox_invoke(*mSandbox, gr_get_glyph_to_char_association, aSegment,
272 aLength, rlbox::from_opaque(t_aText));
274 if (!data) {
275 return NS_ERROR_FAILURE;
278 tainted_gr<gr_glyph_to_char_cluster*> clusters = data->clusters;
279 tainted_gr<uint16_t*> gids = data->gids;
280 tainted_gr<float*> xLocs = data->xLocs;
281 tainted_gr<float*> yLocs = data->yLocs;
283 CompressedGlyph* charGlyphs = aShapedText->GetCharacterGlyphs() + aOffset;
285 bool roundX = bool(aRounding & RoundingFlags::kRoundX);
286 bool roundY = bool(aRounding & RoundingFlags::kRoundY);
288 bool failedVerify = false;
290 // cIndex is primarily used to index into the clusters array which has size
291 // aLength below. As cIndex is not changing anymore, let's just verify it
292 // and remove the tainted wrapper.
293 uint32_t cIndex =
294 CopyAndVerifyOrFail(data->cIndex, val < aLength, &failedVerify);
295 if (failedVerify) {
296 return NS_ERROR_ILLEGAL_VALUE;
298 // now put glyphs into the textrun, one cluster at a time
299 for (uint32_t i = 0; i <= cIndex; ++i) {
300 // We makes a local copy of "clusters[i]" which is of type
301 // tainted_gr<gr_glyph_to_char_cluster> below. We do this intentionally
302 // rather than taking a reference. Taking a reference with the code
304 // tainted_volatile_gr<gr_glyph_to_char_cluster>& c = clusters[i];
306 // produces a tainted_volatile which means the value can change at any
307 // moment allowing for possible time-of-check-time-of-use vuln. We thus
308 // make a local copy to simplify the verification.
309 tainted_gr<gr_glyph_to_char_cluster> c = clusters[i];
311 tainted_gr<float> t_adv; // total advance of the cluster
312 if (rtl) {
313 if (i == 0) {
314 t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) -
315 xLocs[c.baseGlyph];
316 } else {
317 t_adv = xLocs[clusters[i - 1].baseGlyph] - xLocs[c.baseGlyph];
319 } else {
320 if (i == cIndex) {
321 t_adv = sandbox_invoke(*mSandbox, gr_seg_advance_X, aSegment) -
322 xLocs[c.baseGlyph];
323 } else {
324 t_adv = xLocs[clusters[i + 1].baseGlyph] - xLocs[c.baseGlyph];
328 float adv = t_adv.unverified_safe_because(
329 "Per Bug 1569464 - this is the advance width of a glyph or cluster of "
330 "glyphs. There are no a-priori limits on what that might be. Incorrect "
331 "values will tend to result in bad layout or missing text, or bad "
332 "nscoord values. But, these will not result in safety issues.");
334 // check unexpected offset - offs used to index into aText
335 uint32_t offs =
336 CopyAndVerifyOrFail(c.baseChar, val < aLength, &failedVerify);
337 if (failedVerify) {
338 return NS_ERROR_ILLEGAL_VALUE;
341 // Check for default-ignorable char that didn't get filtered, combined,
342 // etc by the shaping process, and skip it.
343 auto one_glyph = c.nGlyphs == static_cast<uint32_t>(1);
344 auto one_char = c.nChars == static_cast<uint32_t>(1);
346 if ((one_glyph && one_char)
347 .unverified_safe_because(
348 "using this boolean check to decide whether to ignore a "
349 "character or not. The worst that can happen is a bad "
350 "rendering.")) {
351 if (aShapedText->FilterIfIgnorable(aOffset + offs, aText[offs])) {
352 continue;
356 uint32_t appAdvance = roundX ? NSToIntRound(adv) * dev2appUnits
357 : NSToIntRound(adv * dev2appUnits);
359 const char gid_simple_value[] =
360 "Per Bug 1569464 - these are glyph IDs that can range from 0 to the "
361 "maximum glyph ID supported by the font. However, out-of-range values "
362 "here should not lead to safety issues; they would simply result in "
363 "blank rendering, although this depends on the platform back-end.";
365 // gids[c.baseGlyph] is checked and used below. Since this is a
366 // tainted_volatile, which can change at any moment, we make a local copy
367 // first to prevent a time-of-check-time-of-use vuln.
368 uint16_t gid_of_base_glyph =
369 gids[c.baseGlyph].unverified_safe_because(gid_simple_value);
371 const char fast_path[] =
372 "Even if the number of glyphs set is an incorrect value, the else "
373 "branch is a more general purpose algorithm which can handle other "
374 "values of nGlyphs";
376 if (one_glyph.unverified_safe_because(fast_path) &&
377 CompressedGlyph::IsSimpleGlyphID(gid_of_base_glyph) &&
378 CompressedGlyph::IsSimpleAdvance(appAdvance) &&
379 charGlyphs[offs].IsClusterStart() &&
380 (yLocs[c.baseGlyph] == 0).unverified_safe_because(fast_path)) {
381 charGlyphs[offs].SetSimpleGlyph(appAdvance, gid_of_base_glyph);
383 } else {
384 // not a one-to-one mapping with simple metrics: use DetailedGlyph
385 AutoTArray<gfxShapedText::DetailedGlyph, 8> details;
386 float clusterLoc;
388 uint32_t glyph_end =
389 (c.baseGlyph + c.nGlyphs)
390 .unverified_safe_because(
391 "This only controls the total number of glyphs set for this "
392 "particular text. Worst that can happen is a bad rendering");
394 // check overflow - ensure loop start is before the end
395 uint32_t glyph_start =
396 CopyAndVerifyOrFail(c.baseGlyph, val <= glyph_end, &failedVerify);
397 if (failedVerify) {
398 return NS_ERROR_ILLEGAL_VALUE;
401 for (uint32_t j = glyph_start; j < glyph_end; ++j) {
402 gfxShapedText::DetailedGlyph* d = details.AppendElement();
403 d->mGlyphID = gids[j].unverified_safe_because(gid_simple_value);
405 const char safe_coordinates[] =
406 "There are no limits on coordinates. Worst case, bad values would "
407 "force rendering off-screen, but there are no memory safety "
408 "issues.";
410 float yLocs_j = yLocs[j].unverified_safe_because(safe_coordinates);
411 float xLocs_j = xLocs[j].unverified_safe_because(safe_coordinates);
413 d->mOffset.y = roundY ? NSToIntRound(-yLocs_j) * dev2appUnits
414 : -yLocs_j * dev2appUnits;
415 if (j == glyph_start) {
416 d->mAdvance = appAdvance;
417 clusterLoc = xLocs_j;
418 } else {
419 float dx =
420 rtl ? (xLocs_j - clusterLoc) : (xLocs_j - clusterLoc - adv);
421 d->mOffset.x =
422 roundX ? NSToIntRound(dx) * dev2appUnits : dx * dev2appUnits;
423 d->mAdvance = 0;
426 aShapedText->SetDetailedGlyphs(aOffset + offs, details.Length(),
427 details.Elements());
430 // check unexpected offset
431 uint32_t char_end = CopyAndVerifyOrFail(c.baseChar + c.nChars,
432 val <= aLength, &failedVerify);
433 // check overflow - ensure loop start is before the end
434 uint32_t char_start =
435 CopyAndVerifyOrFail(c.baseChar + 1, val <= char_end, &failedVerify);
436 if (failedVerify) {
437 return NS_ERROR_ILLEGAL_VALUE;
440 for (uint32_t j = char_start; j < char_end; ++j) {
441 CompressedGlyph& g = charGlyphs[j];
442 NS_ASSERTION(!g.IsSimpleGlyph(), "overwriting a simple glyph");
443 g.SetComplex(g.IsClusterStart(), false);
447 sandbox_invoke(*mSandbox, gr_free_char_association, data);
448 return NS_OK;
451 // for language tag validation - include list of tags from the IANA registry
452 #include "gfxLanguageTagList.cpp"
454 nsTHashSet<uint32_t>* gfxGraphiteShaper::sLanguageTags;
456 /*static*/
457 uint32_t gfxGraphiteShaper::GetGraphiteTagForLang(const nsCString& aLang) {
458 int len = aLang.Length();
459 if (len < 2) {
460 return 0;
463 // convert primary language subtag to a left-packed, NUL-padded integer
464 // for the Graphite API
465 uint32_t grLang = 0;
466 for (int i = 0; i < 4; ++i) {
467 grLang <<= 8;
468 if (i < len) {
469 uint8_t ch = aLang[i];
470 if (ch == '-') {
471 // found end of primary language subtag, truncate here
472 len = i;
473 continue;
475 if (ch < 'a' || ch > 'z') {
476 // invalid character in tag, so ignore it completely
477 return 0;
479 grLang += ch;
483 // valid tags must have length = 2 or 3
484 if (len < 2 || len > 3) {
485 return 0;
488 if (!sLanguageTags) {
489 // store the registered IANA tags in a hash for convenient validation
490 sLanguageTags = new nsTHashSet<uint32_t>(ArrayLength(sLanguageTagList));
491 for (const uint32_t* tag = sLanguageTagList; *tag != 0; ++tag) {
492 sLanguageTags->Insert(*tag);
496 // only accept tags known in the IANA registry
497 if (sLanguageTags->Contains(grLang)) {
498 return grLang;
501 return 0;
504 /*static*/
505 void gfxGraphiteShaper::Shutdown() {
506 #ifdef NS_FREE_PERMANENT_DATA
507 if (sLanguageTags) {
508 sLanguageTags->Clear();
509 delete sLanguageTags;
510 sLanguageTags = nullptr;
512 #endif