Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gfxPlatformMac.h"
#include "gfxQuartzSurface.h"
#include "mozilla/DataMutex.h"
#include "mozilla/gfx/2D.h"
#include "gfxMacFont.h"
#include "gfxCoreTextShaper.h"
#include "gfxTextRun.h"
#include "gfxUserFontSet.h"
#include "gfxConfig.h"
#include "AppleUtils.h"
#include "nsTArray.h"
#include "mozilla/Preferences.h"
#include "mozilla/VsyncDispatcher.h"
#ifdef MOZ_WIDGET_COCOA
# include "nsCocoaFeatures.h"
#endif
#include "nsComponentManagerUtils.h"
#include "nsIFile.h"
#include "nsUnicodeProperties.h"
#include "qcms.h"
#include "gfx2DGlue.h"
#include "GeckoProfiler.h"
#include "nsThreadUtils.h"
#ifdef MOZ_BUNDLED_FONTS
# include "mozilla/Telemetry.h"
# include "nsDirectoryServiceDefs.h"
# include "mozilla/StaticPrefs_gfx.h"
#endif
#include <dlfcn.h>
#include <CoreVideo/CoreVideo.h>
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/SurfacePool.h"
#include "VsyncSource.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::unicode;
using mozilla::dom::SystemFontList;
#ifdef MOZ_WIDGET_COCOA
# include "gfxMacPlatformFontList.h"
using PlatformFontListClass = gfxMacPlatformFontList;
#else
# include "IOSPlatformFontList.h"
using PlatformFontListClass = IOSPlatformFontList;
#endif
// A bunch of fonts for "additional language support" are shipped in a
// "Language Support" directory, and don't show up in the standard font
// list returned by CTFontManagerCopyAvailableFontFamilyNames unless
// we explicitly activate them.
static const nsLiteralCString kLangFontsDirs[] = {
"/Library/Application Support/Apple/Fonts/Language Support"_ns,
"/System/Library/Fonts/Supplemental"_ns};
/* static */
void gfxPlatformMac::FontRegistrationCallback(void* aUnused) {
AUTO_PROFILER_REGISTER_THREAD("RegisterFonts");
PR_SetCurrentThreadName("RegisterFonts");
for (const auto& dir : kLangFontsDirs) {
PlatformFontListClass::ActivateFontsFromDir(dir);
}
}
PRThread* gfxPlatformMac::sFontRegistrationThread = nullptr;
/* This is called from XPCOM_Init during startup (before gfxPlatform has been
initialized), so that it can kick off the font activation on a secondary
thread, and hope that it'll be finished by the time we're ready to build
our font list. */
/* static */
void gfxPlatformMac::RegisterSupplementalFonts() {
if (XRE_GetProcessType() == GeckoProcessType_Default) {
// We activate the fonts on a separate thread, to minimize the startup-
// time cost.
sFontRegistrationThread = PR_CreateThread(
PR_USER_THREAD, FontRegistrationCallback, nullptr, PR_PRIORITY_NORMAL,
PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
}
}
/* static */
void gfxPlatformMac::WaitForFontRegistration() {
if (sFontRegistrationThread) {
PR_JoinThread(sFontRegistrationThread);
sFontRegistrationThread = nullptr;
}
}
gfxPlatformMac::gfxPlatformMac() {
mFontAntiAliasingThreshold = ReadAntiAliasingThreshold();
InitBackendPrefs(GetBackendPrefs());
}
gfxPlatformMac::~gfxPlatformMac() { gfxCoreTextShaper::Shutdown(); }
BackendPrefsData gfxPlatformMac::GetBackendPrefs() const {
BackendPrefsData data;
data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA);
data.mContentBitmask = BackendTypeBit(BackendType::SKIA);
data.mCanvasDefault = BackendType::SKIA;
data.mContentDefault = BackendType::SKIA;
return data;
}
bool gfxPlatformMac::CreatePlatformFontList() {
return gfxPlatformFontList::Initialize(new PlatformFontListClass);
}
void gfxPlatformMac::ReadSystemFontList(SystemFontList* aFontList) {
PlatformFontListClass::PlatformFontList()->ReadSystemFontList(aFontList);
}
already_AddRefed<gfxASurface> gfxPlatformMac::CreateOffscreenSurface(
const IntSize& aSize, gfxImageFormat aFormat) {
if (!Factory::AllowedSurfaceSize(aSize)) {
return nullptr;
}
RefPtr<gfxASurface> newSurface = new gfxQuartzSurface(aSize, aFormat);
return newSurface.forget();
}
void gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript,
eFontPresentation aPresentation,
nsTArray<const char*>& aFontList) {
if (PrefersColor(aPresentation)) {
aFontList.AppendElement("Apple Color Emoji");
}
switch (aRunScript) {
case Script::INVALID:
case Script::NUM_SCRIPT_CODES:
// Ensure the switch covers all the Script enum values.
MOZ_ASSERT_UNREACHABLE("bad script code");
break;
case Script::COMMON:
case Script::INHERITED:
// In most cases, COMMON and INHERITED characters will be merged into
// their context, but if they occur without any specific script context
// we'll just try common default fonts here.
case Script::LATIN:
case Script::CYRILLIC:
case Script::GREEK:
aFontList.AppendElement("Lucida Grande");
break;
case Script::MATHEMATICAL_NOTATION:
case Script::SYMBOLS:
case Script::SYMBOLS_EMOJI:
// Not currently returned by script run resolution (but see below, after
// the switch).
break;
// CJK-related script codes are a bit troublesome because of unification;
// we'll probably just get HAN much of the time, so the choice of which
// language font to try for fallback is rather arbitrary. Usually, though,
// we hope that font prefs will have handled this earlier.
case Script::BOPOMOFO:
case Script::HAN_WITH_BOPOMOFO:
case Script::SIMPLIFIED_HAN:
case Script::HAN:
aFontList.AppendElement("Songti SC");
if (aCh > 0x10000) {
// macOS installations with MS Office may have these -ExtB fonts
aFontList.AppendElement("SimSun-ExtB");
}
break;
// Currently, we don't resolve script runs to this value, but we may do so
// in future if we get better at handling things like `lang=zh-Hant`, not
// just resolving based on the Unicode text.
case Script::TRADITIONAL_HAN:
aFontList.AppendElement("Songti TC");
if (aCh > 0x10000) {
// macOS installations with MS Office may have these -ExtB fonts
aFontList.AppendElement("MingLiU-ExtB");
}
break;
case Script::HIRAGANA:
case Script::KATAKANA:
case Script::KATAKANA_OR_HIRAGANA:
case Script::JAPANESE:
aFontList.AppendElement("Hiragino Sans");
aFontList.AppendElement("Hiragino Kaku Gothic ProN");
break;
case Script::JAMO:
case Script::KOREAN:
case Script::HANGUL:
aFontList.AppendElement("Nanum Gothic");
aFontList.AppendElement("Apple SD Gothic Neo");
break;
// For most other scripts, macOS comes with a default font we can use.
case Script::ARABIC:
aFontList.AppendElement("Geeza Pro");
break;
case Script::ARMENIAN:
aFontList.AppendElement("Mshtakan");
break;
case Script::BENGALI:
aFontList.AppendElement("Bangla Sangam MN");
break;
case Script::CHEROKEE:
aFontList.AppendElement("Plantagenet Cherokee");
break;
case Script::COPTIC:
aFontList.AppendElement("Noto Sans Coptic");
break;
case Script::DESERET:
aFontList.AppendElement("Baskerville");
break;
case Script::DEVANAGARI:
aFontList.AppendElement("Devanagari Sangam MN");
break;
case Script::ETHIOPIC:
aFontList.AppendElement("Kefa");
break;
case Script::GEORGIAN:
aFontList.AppendElement("Helvetica");
break;
case Script::GOTHIC:
aFontList.AppendElement("Noto Sans Gothic");
break;
case Script::GUJARATI:
aFontList.AppendElement("Gujarati Sangam MN");
break;
case Script::GURMUKHI:
aFontList.AppendElement("Gurmukhi MN");
break;
case Script::HEBREW:
aFontList.AppendElement("Lucida Grande");
break;
case Script::KANNADA:
aFontList.AppendElement("Kannada MN");
break;
case Script::KHMER:
aFontList.AppendElement("Khmer MN");
break;
case Script::LAO:
aFontList.AppendElement("Lao MN");
break;
case Script::MALAYALAM:
aFontList.AppendElement("Malayalam Sangam MN");
break;
case Script::MONGOLIAN:
aFontList.AppendElement("Noto Sans Mongolian");
break;
case Script::MYANMAR:
aFontList.AppendElement("Myanmar MN");
break;
case Script::OGHAM:
aFontList.AppendElement("Noto Sans Ogham");
break;
case Script::OLD_ITALIC:
aFontList.AppendElement("Noto Sans Old Italic");
break;
case Script::ORIYA:
aFontList.AppendElement("Oriya Sangam MN");
break;
case Script::RUNIC:
aFontList.AppendElement("Noto Sans Runic");
break;
case Script::SINHALA:
aFontList.AppendElement("Sinhala Sangam MN");
break;
case Script::SYRIAC:
aFontList.AppendElement("Noto Sans Syriac");
break;
case Script::TAMIL:
aFontList.AppendElement("Tamil MN");
break;
case Script::TELUGU:
aFontList.AppendElement("Telugu MN");
break;
case Script::THAANA:
aFontList.AppendElement("Noto Sans Thaana");
break;
case Script::THAI:
aFontList.AppendElement("Thonburi");
break;
case Script::TIBETAN:
aFontList.AppendElement("Kailasa");
break;
case Script::CANADIAN_ABORIGINAL:
aFontList.AppendElement("Euphemia UCAS");
break;
case Script::YI:
aFontList.AppendElement("Noto Sans Yi");
aFontList.AppendElement("STHeiti");
break;
case Script::TAGALOG:
aFontList.AppendElement("Noto Sans Tagalog");
break;
case Script::HANUNOO:
aFontList.AppendElement("Noto Sans Hanunoo");
break;
case Script::BUHID:
aFontList.AppendElement("Noto Sans Buhid");
break;
case Script::TAGBANWA:
aFontList.AppendElement("Noto Sans Tagbanwa");
break;
case Script::BRAILLE:
aFontList.AppendElement("Apple Braille");
break;
case Script::CYPRIOT:
aFontList.AppendElement("Noto Sans Cypriot");
break;
case Script::LIMBU:
aFontList.AppendElement("Noto Sans Limbu");
break;
case Script::LINEAR_B:
aFontList.AppendElement("Noto Sans Linear B");
break;
case Script::OSMANYA:
aFontList.AppendElement("Noto Sans Osmanya");
break;
case Script::SHAVIAN:
aFontList.AppendElement("Noto Sans Shavian");
break;
case Script::TAI_LE:
aFontList.AppendElement("Noto Sans Tai Le");
break;
case Script::UGARITIC:
aFontList.AppendElement("Noto Sans Ugaritic");
break;
case Script::BUGINESE:
aFontList.AppendElement("Noto Sans Buginese");
break;
case Script::GLAGOLITIC:
aFontList.AppendElement("Noto Sans Glagolitic");
break;
case Script::KHAROSHTHI:
aFontList.AppendElement("Noto Sans Kharoshthi");
break;
case Script::SYLOTI_NAGRI:
aFontList.AppendElement("Noto Sans Syloti Nagri");
break;
case Script::NEW_TAI_LUE:
aFontList.AppendElement("Noto Sans New Tai Lue");
break;
case Script::TIFINAGH:
aFontList.AppendElement("Noto Sans Tifinagh");
break;
case Script::OLD_PERSIAN:
aFontList.AppendElement("Noto Sans Old Persian");
break;
case Script::BALINESE:
aFontList.AppendElement("Noto Sans Balinese");
break;
case Script::BATAK:
aFontList.AppendElement("Noto Sans Batak");
break;
case Script::BRAHMI:
aFontList.AppendElement("Noto Sans Brahmi");
break;
case Script::CHAM:
aFontList.AppendElement("Noto Sans Cham");
break;
case Script::EGYPTIAN_HIEROGLYPHS:
aFontList.AppendElement("Noto Sans Egyptian Hieroglyphs");
break;
case Script::PAHAWH_HMONG:
aFontList.AppendElement("Noto Sans Pahawh Hmong");
break;
case Script::OLD_HUNGARIAN:
aFontList.AppendElement("Noto Sans Old Hungarian");
break;
case Script::JAVANESE:
aFontList.AppendElement("Noto Sans Javanese");
break;
case Script::KAYAH_LI:
aFontList.AppendElement("Noto Sans Kayah Li");
break;
case Script::LEPCHA:
aFontList.AppendElement("Noto Sans Lepcha");
break;
case Script::LINEAR_A:
aFontList.AppendElement("Noto Sans Linear A");
break;
case Script::MANDAIC:
aFontList.AppendElement("Noto Sans Mandaic");
break;
case Script::NKO:
aFontList.AppendElement("Noto Sans NKo");
break;
case Script::OLD_TURKIC:
aFontList.AppendElement("Noto Sans Old Turkic");
break;
case Script::OLD_PERMIC:
aFontList.AppendElement("Noto Sans Old Permic");
break;
case Script::PHAGS_PA:
aFontList.AppendElement("Noto Sans PhagsPa");
break;
case Script::PHOENICIAN:
aFontList.AppendElement("Noto Sans Phoenician");
break;
case Script::MIAO:
aFontList.AppendElement("Noto Sans Miao");
break;
case Script::VAI:
aFontList.AppendElement("Noto Sans Vai");
break;
case Script::CUNEIFORM:
aFontList.AppendElement("Noto Sans Cuneiform");
break;
case Script::CARIAN:
aFontList.AppendElement("Noto Sans Carian");
break;
case Script::TAI_THAM:
aFontList.AppendElement("Noto Sans Tai Tham");
break;
case Script::LYCIAN:
aFontList.AppendElement("Noto Sans Lycian");
break;
case Script::LYDIAN:
aFontList.AppendElement("Noto Sans Lydian");
break;
case Script::OL_CHIKI:
aFontList.AppendElement("Noto Sans Ol Chiki");
break;
case Script::REJANG:
aFontList.AppendElement("Noto Sans Rejang");
break;
case Script::SAURASHTRA:
aFontList.AppendElement("Noto Sans Saurashtra");
break;
case Script::SUNDANESE:
aFontList.AppendElement("Noto Sans Sundanese");
break;
case Script::MEETEI_MAYEK:
aFontList.AppendElement("Noto Sans Meetei Mayek");
break;
case Script::IMPERIAL_ARAMAIC:
aFontList.AppendElement("Noto Sans Imperial Aramaic");
break;
case Script::AVESTAN:
aFontList.AppendElement("Noto Sans Avestan");
break;
case Script::CHAKMA:
aFontList.AppendElement("Noto Sans Chakma");
break;
case Script::KAITHI:
aFontList.AppendElement("Noto Sans Kaithi");
break;
case Script::MANICHAEAN:
aFontList.AppendElement("Noto Sans Manichaean");
break;
case Script::INSCRIPTIONAL_PAHLAVI:
aFontList.AppendElement("Noto Sans Inscriptional Pahlavi");
break;
case Script::PSALTER_PAHLAVI:
aFontList.AppendElement("Noto Sans Psalter Pahlavi");
break;
case Script::INSCRIPTIONAL_PARTHIAN:
aFontList.AppendElement("Noto Sans Inscriptional Parthian");
break;
case Script::SAMARITAN:
aFontList.AppendElement("Noto Sans Samaritan");
break;
case Script::TAI_VIET:
aFontList.AppendElement("Noto Sans Tai Viet");
break;
case Script::BAMUM:
aFontList.AppendElement("Noto Sans Bamum");
break;
case Script::LISU:
aFontList.AppendElement("Noto Sans Lisu");
break;
case Script::OLD_SOUTH_ARABIAN:
aFontList.AppendElement("Noto Sans Old South Arabian");
break;
case Script::BASSA_VAH:
aFontList.AppendElement("Noto Sans Bassa Vah");
break;
case Script::DUPLOYAN:
aFontList.AppendElement("Noto Sans Duployan");
break;
case Script::ELBASAN:
aFontList.AppendElement("Noto Sans Elbasan");
break;
case Script::GRANTHA:
aFontList.AppendElement("Noto Sans Grantha");
break;
case Script::MENDE_KIKAKUI:
aFontList.AppendElement("Noto Sans Mende Kikakui");
break;
case Script::MEROITIC_CURSIVE:
case Script::MEROITIC_HIEROGLYPHS:
aFontList.AppendElement("Noto Sans Meroitic");
break;
case Script::OLD_NORTH_ARABIAN:
aFontList.AppendElement("Noto Sans Old North Arabian");
break;
case Script::NABATAEAN:
aFontList.AppendElement("Noto Sans Nabataean");
break;
case Script::PALMYRENE:
aFontList.AppendElement("Noto Sans Palmyrene");
break;
case Script::KHUDAWADI:
aFontList.AppendElement("Noto Sans Khudawadi");
break;
case Script::WARANG_CITI:
aFontList.AppendElement("Noto Sans Warang Citi");
break;
case Script::MRO:
aFontList.AppendElement("Noto Sans Mro");
break;
case Script::SHARADA:
aFontList.AppendElement("Noto Sans Sharada");
break;
case Script::SORA_SOMPENG:
aFontList.AppendElement("Noto Sans Sora Sompeng");
break;
case Script::TAKRI:
aFontList.AppendElement("Noto Sans Takri");
break;
case Script::KHOJKI:
aFontList.AppendElement("Noto Sans Khojki");
break;
case Script::TIRHUTA:
aFontList.AppendElement("Noto Sans Tirhuta");
break;
case Script::CAUCASIAN_ALBANIAN:
aFontList.AppendElement("Noto Sans Caucasian Albanian");
break;
case Script::MAHAJANI:
aFontList.AppendElement("Noto Sans Mahajani");
break;
case Script::AHOM:
aFontList.AppendElement("Noto Serif Ahom");
break;
case Script::HATRAN:
aFontList.AppendElement("Noto Sans Hatran");
break;
case Script::MODI:
aFontList.AppendElement("Noto Sans Modi");
break;
case Script::MULTANI:
aFontList.AppendElement("Noto Sans Multani");
break;
case Script::PAU_CIN_HAU:
aFontList.AppendElement("Noto Sans Pau Cin Hau");
break;
case Script::SIDDHAM:
aFontList.AppendElement("Noto Sans Siddham");
break;
case Script::ADLAM:
aFontList.AppendElement("Noto Sans Adlam");
break;
case Script::BHAIKSUKI:
aFontList.AppendElement("Noto Sans Bhaiksuki");
break;
case Script::MARCHEN:
aFontList.AppendElement("Noto Sans Marchen");
break;
case Script::NEWA:
aFontList.AppendElement("Noto Sans Newa");
break;
case Script::OSAGE:
aFontList.AppendElement("Noto Sans Osage");
break;
case Script::HANIFI_ROHINGYA:
aFontList.AppendElement("Noto Sans Hanifi Rohingya");
break;
case Script::WANCHO:
aFontList.AppendElement("Noto Sans Wancho");
break;
case Script::ARABIC_NASTALIQ:
aFontList.AppendElement("Noto Nastaliq Urdu");
break;
// Script codes for which no commonly-installed font is currently known.
// Probably future macOS versions will add Noto fonts for many of these,
// so we should watch for updates.
case Script::OLD_CHURCH_SLAVONIC_CYRILLIC:
case Script::DEMOTIC_EGYPTIAN:
case Script::HIERATIC_EGYPTIAN:
case Script::BLISSYMBOLS:
case Script::CIRTH:
case Script::KHUTSURI:
case Script::HARAPPAN_INDUS:
case Script::LATIN_FRAKTUR:
case Script::LATIN_GAELIC:
case Script::MAYAN_HIEROGLYPHS:
case Script::RONGORONGO:
case Script::SARATI:
case Script::ESTRANGELO_SYRIAC:
case Script::WESTERN_SYRIAC:
case Script::EASTERN_SYRIAC:
case Script::TENGWAR:
case Script::VISIBLE_SPEECH:
case Script::UNWRITTEN_LANGUAGES:
case Script::UNKNOWN:
case Script::SIGNWRITING:
case Script::MOON:
case Script::BOOK_PAHLAVI:
case Script::NAKHI_GEBA:
case Script::KPELLE:
case Script::LOMA:
case Script::AFAKA:
case Script::JURCHEN:
case Script::NUSHU:
case Script::TANGUT:
case Script::WOLEAI:
case Script::ANATOLIAN_HIEROGLYPHS:
case Script::MASARAM_GONDI:
case Script::SOYOMBO:
case Script::ZANABAZAR_SQUARE:
case Script::DOGRA:
case Script::GUNJALA_GONDI:
case Script::MAKASAR:
case Script::MEDEFAIDRIN:
case Script::SOGDIAN:
case Script::OLD_SOGDIAN:
case Script::ELYMAIC:
case Script::NYIAKENG_PUACHUE_HMONG:
case Script::NANDINAGARI:
case Script::CHORASMIAN:
case Script::DIVES_AKURU:
case Script::KHITAN_SMALL_SCRIPT:
case Script::YEZIDI:
case Script::CYPRO_MINOAN:
case Script::OLD_UYGHUR:
case Script::TANGSA:
case Script::TOTO:
case Script::VITHKUQI:
case Script::KAWI:
case Script::NAG_MUNDARI:
case Script::GARAY:
case Script::GURUNG_KHEMA:
case Script::KIRAT_RAI:
case Script::OL_ONAL:
case Script::SUNUWAR:
case Script::TODHRI:
case Script::TULU_TIGALARI:
break;
}
// Symbols/dingbats are generally Script=COMMON but may be resolved to any
// surrounding script run. So we'll always append a couple of likely fonts
// for such characters.
const uint32_t b = aCh >> 8;
if (aRunScript == Script::COMMON || // Stray COMMON chars not resolved
(b >= 0x20 && b <= 0x2b) || b == 0x2e || // BMP symbols/punctuation/etc
GetGenCategory(aCh) == nsUGenCategory::kSymbol ||
GetGenCategory(aCh) == nsUGenCategory::kPunctuation) {
if (b == 0x27) {
aFontList.AppendElement("Zapf Dingbats");
}
aFontList.AppendElement("Geneva");
aFontList.AppendElement("STIXGeneral");
aFontList.AppendElement("Apple Symbols");
// Japanese fonts also cover a lot of miscellaneous symbols
aFontList.AppendElement("Hiragino Sans");
aFontList.AppendElement("Hiragino Kaku Gothic ProN");
}
// Arial Unicode MS has lots of glyphs for obscure characters; try it as a
// last resort.
aFontList.AppendElement("Arial Unicode MS");
}
/*static*/
void gfxPlatformMac::LookupSystemFont(
mozilla::LookAndFeel::FontID aSystemFontID, nsACString& aSystemFontName,
gfxFontStyle& aFontStyle) {
return PlatformFontListClass::LookupSystemFont(aSystemFontID, aSystemFontName,
aFontStyle);
}
uint32_t gfxPlatformMac::ReadAntiAliasingThreshold() {
uint32_t threshold = 0; // default == no threshold
// first read prefs flag to determine whether to use the setting or not
bool useAntiAliasingThreshold =
Preferences::GetBool("gfx.use_text_smoothing_setting", false);
// if the pref setting is disabled, return 0 which effectively disables this
// feature
if (!useAntiAliasingThreshold) return threshold;
// value set via Appearance pref panel, "Turn off text smoothing for font
// sizes xxx and smaller"
CFNumberRef prefValue = (CFNumberRef)CFPreferencesCopyAppValue(
CFSTR("AppleAntiAliasingThreshold"), kCFPreferencesCurrentApplication);
if (prefValue) {
if (!CFNumberGetValue(prefValue, kCFNumberIntType, &threshold)) {
threshold = 0;
}
CFRelease(prefValue);
}
return threshold;
}
bool gfxPlatformMac::AccelerateLayersByDefault() { return true; }
#ifdef MOZ_WIDGET_COCOA
// This is the renderer output callback function, called on the vsync thread
static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink,
const CVTimeStamp* aNow,
const CVTimeStamp* aOutputTime,
CVOptionFlags aFlagsIn, CVOptionFlags* aFlagsOut,
void* aDisplayLinkContext);
class OSXVsyncSource final : public VsyncSource {
public:
OSXVsyncSource()
: mDisplayLink(nullptr, "OSXVsyncSource::OSXDisplay::mDisplayLink") {
MOZ_ASSERT(NS_IsMainThread());
mTimer = NS_NewTimer();
CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback,
this);
}
virtual ~OSXVsyncSource() {
MOZ_ASSERT(NS_IsMainThread());
CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback,
this);
}
static void RetryEnableVsync(nsITimer* aTimer, void* aOsxVsyncSource) {
MOZ_ASSERT(NS_IsMainThread());
OSXVsyncSource* osxVsyncSource =
static_cast<OSXVsyncSource*>(aOsxVsyncSource);
MOZ_ASSERT(osxVsyncSource);
osxVsyncSource->EnableVsync();
}
void EnableVsync() override {
MOZ_ASSERT(NS_IsMainThread());
if (IsVsyncEnabled()) {
return;
}
auto displayLink = mDisplayLink.Lock();
// Create a display link capable of being used with all active displays
// TODO: See if we need to create an active DisplayLink for each monitor
// in multi-monitor situations. According to the docs, it is compatible
// with all displays running on the computer But if we have different
// monitors at different display rates, we may hit issues.
CVReturn retval = CVDisplayLinkCreateWithActiveCGDisplays(&*displayLink);
// Workaround for bug 1201401: CVDisplayLinkCreateWithCGDisplays()
// (called by CVDisplayLinkCreateWithActiveCGDisplays()) sometimes
// creates a CVDisplayLinkRef with an uninitialized (nulled) internal
// pointer. If we continue to use this CVDisplayLinkRef, we will
// eventually crash in CVCGDisplayLink::getDisplayTimes(), where the
// internal pointer is dereferenced. Fortunately, when this happens
// another internal variable is also left uninitialized (zeroed),
// which is accessible via CVDisplayLinkGetCurrentCGDisplay(). In
// normal conditions the current display is never zero.
if ((retval == kCVReturnSuccess) &&
(CVDisplayLinkGetCurrentCGDisplay(*displayLink) == 0)) {
retval = kCVReturnInvalidDisplay;
}
if (retval != kCVReturnSuccess) {
NS_WARNING(
"Could not create a display link with all active displays. "
"Retrying");
CVDisplayLinkRelease(*displayLink);
*displayLink = nullptr;
// bug 1142708 - When coming back from sleep,
// or when changing displays, active displays may not be ready yet,
// even if listening for the kIOMessageSystemHasPoweredOn event
// from OS X sleep notifications.
// Active displays are those that are drawable.
// bug 1144638 - When changing display configurations and getting
// notifications from CGDisplayReconfigurationCallBack, the
// callback gets called twice for each active display
// so it's difficult to know when all displays are active.
// Instead, try again soon. The delay is arbitrary. 100ms chosen
// because on a late 2013 15" retina, it takes about that
// long to come back up from sleep.
uint32_t delay = 100;
mTimer->InitWithNamedFuncCallback(RetryEnableVsync, this, delay,
nsITimer::TYPE_ONE_SHOT,
"RetryEnableVsync");
return;
}
if (CVDisplayLinkSetOutputCallback(*displayLink, &VsyncCallback, this) !=
kCVReturnSuccess) {
NS_WARNING("Could not set displaylink output callback");
CVDisplayLinkRelease(*displayLink);
*displayLink = nullptr;
return;
}
mPreviousTimestamp = TimeStamp::Now();
if (CVDisplayLinkStart(*displayLink) != kCVReturnSuccess) {
NS_WARNING("Could not activate the display link");
CVDisplayLinkRelease(*displayLink);
*displayLink = nullptr;
}
CVTime vsyncRate =
CVDisplayLinkGetNominalOutputVideoRefreshPeriod(*displayLink);
if (vsyncRate.flags & kCVTimeIsIndefinite) {
NS_WARNING("Could not get vsync rate, setting to 60.");
mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
} else {
int64_t timeValue = vsyncRate.timeValue;
int64_t timeScale = vsyncRate.timeScale;
const int milliseconds = 1000;
float rateInMs = ((double)timeValue / (double)timeScale) * milliseconds;
mVsyncRate = TimeDuration::FromMilliseconds(rateInMs);
}
}
void DisableVsync() override {
MOZ_ASSERT(NS_IsMainThread());
if (!IsVsyncEnabled()) {
return;
}
// Release the display link
auto displayLink = mDisplayLink.Lock();
if (*displayLink) {
CVDisplayLinkRelease(*displayLink);
*displayLink = nullptr;
}
}
bool IsVsyncEnabled() override {
MOZ_ASSERT(NS_IsMainThread());
auto displayLink = mDisplayLink.Lock();
return *displayLink != nullptr;
}
TimeDuration GetVsyncRate() override { return mVsyncRate; }
void Shutdown() override {
MOZ_ASSERT(NS_IsMainThread());
mTimer->Cancel();
mTimer = nullptr;
DisableVsync();
}
// The vsync timestamps given by the CVDisplayLinkCallback are
// in the future for the NEXT frame. Large parts of Gecko, such
// as animations assume a timestamp at either now or in the past.
// Normalize the timestamps given to the VsyncDispatchers to the vsync
// that just occured, not the vsync that is upcoming.
TimeStamp mPreviousTimestamp;
private:
static void DisplayReconfigurationCallback(CGDirectDisplayID aDisplay,
CGDisplayChangeSummaryFlags aFlags,
void* aUserInfo) {
static_cast<OSXVsyncSource*>(aUserInfo)->OnDisplayReconfiguration(aDisplay,
aFlags);
}
void OnDisplayReconfiguration(CGDirectDisplayID aDisplay,
CGDisplayChangeSummaryFlags aFlags) {
// Display reconfiguration notifications are fired in two phases: Before
// the reconfiguration and after the reconfiguration.
// All displays are notified before (with a "BeginConfiguration" flag),
// and the reconfigured displays are notified again after the
// configuration.
if (aFlags & kCGDisplayBeginConfigurationFlag) {
// We're only interested in the "after" notification, for the display
// link's current display.
return;
}
if (!NS_IsMainThread()) {
return;
}
bool didReconfigureCurrentDisplayLinkDisplay = false;
{ // scope for lock
auto displayLink = mDisplayLink.Lock();
didReconfigureCurrentDisplayLinkDisplay =
*displayLink &&
CVDisplayLinkGetCurrentCGDisplay(*displayLink) == aDisplay;
}
if (didReconfigureCurrentDisplayLinkDisplay) {
// The link's current display has been reconfigured.
// Recreate the display link, because otherwise it may be stuck with a
// "removed" display forever and never notify us again.
DisableVsync();
EnableVsync();
}
}
// Accessed from main thread and from display reconfiguration callback
// thread... which also happens to be the main thread.
DataMutex<CVDisplayLinkRef> mDisplayLink;
// Accessed only from the main thread.
RefPtr<nsITimer> mTimer;
TimeDuration mVsyncRate;
}; // OSXVsyncSource
static CVReturn VsyncCallback(CVDisplayLinkRef aDisplayLink,
const CVTimeStamp* aNow,
const CVTimeStamp* aOutputTime,
CVOptionFlags aFlagsIn, CVOptionFlags* aFlagsOut,
void* aDisplayLinkContext) {
// Executed on OS X hardware vsync thread
OSXVsyncSource* vsyncSource = (OSXVsyncSource*)aDisplayLinkContext;
mozilla::TimeStamp outputTime =
mozilla::TimeStamp::FromSystemTime(aOutputTime->hostTime);
mozilla::TimeStamp nextVsync = outputTime;
mozilla::TimeStamp previousVsync = vsyncSource->mPreviousTimestamp;
mozilla::TimeStamp now = TimeStamp::Now();
// Snow leopard sometimes sends vsync timestamps very far in the past.
// Normalize the vsync timestamps to now.
if (nextVsync <= previousVsync) {
nextVsync = now;
previousVsync = now;
} else if (now < previousVsync) {
// Bug 1158321 - The VsyncCallback can sometimes execute before the reported
// vsync time. In those cases, normalize the timestamp to Now() as sending
// timestamps in the future has undefined behavior. See the comment above
// OSXVsyncSource::mPreviousTimestamp
previousVsync = now;
}
vsyncSource->mPreviousTimestamp = nextVsync;
vsyncSource->NotifyVsync(previousVsync, outputTime);
return kCVReturnSuccess;
}
#endif
already_AddRefed<mozilla::gfx::VsyncSource>
gfxPlatformMac::CreateGlobalHardwareVsyncSource() {
#ifdef MOZ_WIDGET_COCOA
RefPtr<VsyncSource> osxVsyncSource = new OSXVsyncSource();
osxVsyncSource->EnableVsync();
if (!osxVsyncSource->IsVsyncEnabled()) {
NS_WARNING(
"OS X Vsync source not enabled. Falling back to software vsync.");
return GetSoftwareVsyncSource();
}
osxVsyncSource->DisableVsync();
return osxVsyncSource.forget();
#else
// TODO: CADisplayLink
return GetSoftwareVsyncSource();
#endif
}
nsTArray<uint8_t> gfxPlatformMac::GetPlatformCMSOutputProfileData() {
nsTArray<uint8_t> prefProfileData = GetPrefCMSOutputProfileData();
if (!prefProfileData.IsEmpty()) {
return prefProfileData;
}
CGColorSpaceRef cspace = nil;
#ifdef MOZ_WIDGET_COCOA
cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
#endif
if (!cspace) {
cspace = ::CGColorSpaceCreateDeviceRGB();
}
if (!cspace) {
return nsTArray<uint8_t>();
}
CFDataRef iccp = ::CGColorSpaceCopyICCData(cspace);
::CFRelease(cspace);
if (!iccp) {
return nsTArray<uint8_t>();
}
// copy to external buffer
size_t size = static_cast<size_t>(::CFDataGetLength(iccp));
nsTArray<uint8_t> result;
if (size > 0) {
result.AppendElements(::CFDataGetBytePtr(iccp), size);
}
::CFRelease(iccp);
return result;
}
bool gfxPlatformMac::CheckVariationFontSupport() { return true; }
void gfxPlatformMac::InitPlatformGPUProcessPrefs() {
FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
gpuProc.ForceDisable(FeatureStatus::Blocked,
"GPU process does not work on Mac",
"FEATURE_FAILURE_MAC_GPU_PROC"_ns);
}