/* -*- 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 "gfxMacPlatformFontList.h" #include "gfxMacFont.h" #include "gfxCoreTextShaper.h" #include "gfxTextRun.h" #include "gfxUserFontSet.h" #include "gfxConfig.h" #include "nsTArray.h" #include "mozilla/Preferences.h" #include "mozilla/VsyncDispatcher.h" #include "nsCocoaFeatures.h" #include "nsUnicodeProperties.h" #include "qcms.h" #include "gfx2DGlue.h" #include #include #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::SystemFontListEntry; // cribbed from CTFontManager.h enum { kAutoActivationDisabled = 1 }; typedef uint32_t AutoActivationSetting; // bug 567552 - disable auto-activation of fonts static void DisableFontActivation() { // get the main bundle identifier CFBundleRef mainBundle = ::CFBundleGetMainBundle(); CFStringRef mainBundleID = nullptr; if (mainBundle) { mainBundleID = ::CFBundleGetIdentifier(mainBundle); } // bug 969388 and bug 922590 - mainBundlID as null is sometimes problematic if (!mainBundleID) { NS_WARNING("missing bundle ID, packaging set up incorrectly"); return; } // if possible, fetch CTFontManagerSetAutoActivationSetting void (*CTFontManagerSetAutoActivationSettingPtr)(CFStringRef, AutoActivationSetting); CTFontManagerSetAutoActivationSettingPtr = (void (*)(CFStringRef, AutoActivationSetting))dlsym( RTLD_DEFAULT, "CTFontManagerSetAutoActivationSetting"); // bug 567552 - disable auto-activation of fonts if (CTFontManagerSetAutoActivationSettingPtr) { CTFontManagerSetAutoActivationSettingPtr(mainBundleID, kAutoActivationDisabled); } } gfxPlatformMac::gfxPlatformMac() { DisableFontActivation(); mFontAntiAliasingThreshold = ReadAntiAliasingThreshold(); InitBackendPrefs(GetBackendPrefs()); if (nsCocoaFeatures::OnHighSierraOrLater()) { mHasNativeColrFontSupport = true; } } 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::UsesTiling() const { // The non-tiling ContentClient requires CrossProcessSemaphore which // isn't implemented for OSX. return true; } bool gfxPlatformMac::ContentUsesTiling() const { return UsesTiling(); } gfxPlatformFontList* gfxPlatformMac::CreatePlatformFontList() { gfxPlatformFontList* list = new gfxMacPlatformFontList(); if (NS_SUCCEEDED(list->InitFontList())) { return list; } gfxPlatformFontList::Shutdown(); return nullptr; } void gfxPlatformMac::ReadSystemFontList( nsTArray* aFontList) { gfxMacPlatformFontList::PlatformFontList()->ReadSystemFontList(aFontList); } already_AddRefed gfxPlatformMac::CreateOffscreenSurface( const IntSize& aSize, gfxImageFormat aFormat) { if (!Factory::AllowedSurfaceSize(aSize)) { return nullptr; } RefPtr newSurface = new gfxQuartzSurface(aSize, aFormat); return newSurface.forget(); } bool gfxPlatformMac::IsFontFormatSupported(uint32_t aFormatFlags) { if (gfxPlatform::IsFontFormatSupported(aFormatFlags)) { return true; } // If the generic method rejected the format hint, then check for any // platform-specific format we know about. if (aFormatFlags & gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT) { return true; } return false; } static const char kFontArialUnicodeMS[] = "Arial Unicode MS"; static const char kFontAppleBraille[] = "Apple Braille"; static const char kFontAppleColorEmoji[] = "Apple Color Emoji"; static const char kFontAppleSymbols[] = "Apple Symbols"; static const char kFontDevanagariSangamMN[] = "Devanagari Sangam MN"; static const char kFontEuphemiaUCAS[] = "Euphemia UCAS"; static const char kFontGeneva[] = "Geneva"; static const char kFontGeezaPro[] = "Geeza Pro"; static const char kFontGujaratiSangamMN[] = "Gujarati Sangam MN"; static const char kFontGurmukhiMN[] = "Gurmukhi MN"; static const char kFontHelvetica[] = "Helvetica"; static const char kFontHiraginoKakuGothic[] = "Hiragino Kaku Gothic ProN"; static const char kFontHiraginoSansGB[] = "Hiragino Sans GB"; static const char kFontKefa[] = "Kefa"; static const char kFontKhmerMN[] = "Khmer MN"; static const char kFontLaoMN[] = "Lao MN"; static const char kFontLucidaGrande[] = "Lucida Grande"; static const char kFontMenlo[] = "Menlo"; static const char kFontMicrosoftTaiLe[] = "Microsoft Tai Le"; static const char kFontMingLiUExtB[] = "MingLiU-ExtB"; static const char kFontMyanmarMN[] = "Myanmar MN"; static const char kFontNotoSansMongolian[] = "Noto Sans Mongolian"; static const char kFontPlantagenetCherokee[] = "Plantagenet Cherokee"; static const char kFontSimSunExtB[] = "SimSun-ExtB"; static const char kFontSongtiSC[] = "Songti SC"; static const char kFontSTHeiti[] = "STHeiti"; static const char kFontSTIXGeneral[] = "STIXGeneral"; static const char kFontTamilMN[] = "Tamil MN"; static const char kFontZapfDingbats[] = "Zapf Dingbats"; void gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, Script aRunScript, eFontPresentation aPresentation, nsTArray& aFontList) { if (PrefersColor(aPresentation)) { aFontList.AppendElement(kFontAppleColorEmoji); } aFontList.AppendElement(kFontLucidaGrande); if (!IS_IN_BMP(aCh)) { uint32_t p = aCh >> 16; if (p == 1) { aFontList.AppendElement(kFontAppleSymbols); aFontList.AppendElement(kFontSTIXGeneral); aFontList.AppendElement(kFontGeneva); } else if (p == 2) { // OSX installations with MS Office may have these fonts aFontList.AppendElement(kFontMingLiUExtB); aFontList.AppendElement(kFontSimSunExtB); } } else { uint32_t b = (aCh >> 8) & 0xff; switch (b) { case 0x03: case 0x05: aFontList.AppendElement(kFontGeneva); break; case 0x07: aFontList.AppendElement(kFontGeezaPro); break; case 0x09: aFontList.AppendElement(kFontDevanagariSangamMN); break; case 0x0a: aFontList.AppendElement(kFontGurmukhiMN); aFontList.AppendElement(kFontGujaratiSangamMN); break; case 0x0b: aFontList.AppendElement(kFontTamilMN); break; case 0x0e: aFontList.AppendElement(kFontLaoMN); break; case 0x0f: aFontList.AppendElement(kFontSongtiSC); break; case 0x10: aFontList.AppendElement(kFontHelvetica); aFontList.AppendElement(kFontMenlo); aFontList.AppendElement(kFontMyanmarMN); break; case 0x13: // Cherokee aFontList.AppendElement(kFontPlantagenetCherokee); aFontList.AppendElement(kFontKefa); break; case 0x14: // Unified Canadian Aboriginal Syllabics case 0x15: case 0x16: aFontList.AppendElement(kFontEuphemiaUCAS); aFontList.AppendElement(kFontGeneva); break; case 0x18: // Mongolian, UCAS aFontList.AppendElement(kFontNotoSansMongolian); aFontList.AppendElement(kFontEuphemiaUCAS); break; case 0x19: // Khmer aFontList.AppendElement(kFontKhmerMN); aFontList.AppendElement(kFontMicrosoftTaiLe); break; case 0x1d: case 0x1e: aFontList.AppendElement(kFontGeneva); break; case 0x27: // For Dingbats block 2700-27BF, prefer Zapf Dingbats aFontList.AppendElement(kFontZapfDingbats); [[fallthrough]]; case 0x20: // Symbol ranges case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x29: case 0x2a: case 0x2b: case 0x2e: aFontList.AppendElement(kFontHiraginoKakuGothic); aFontList.AppendElement(kFontAppleSymbols); aFontList.AppendElement(kFontMenlo); aFontList.AppendElement(kFontSTIXGeneral); aFontList.AppendElement(kFontGeneva); aFontList.AppendElement(kFontAppleColorEmoji); break; case 0x2c: aFontList.AppendElement(kFontGeneva); break; case 0x2d: aFontList.AppendElement(kFontKefa); aFontList.AppendElement(kFontGeneva); break; case 0x28: // Braille aFontList.AppendElement(kFontAppleBraille); break; case 0x31: aFontList.AppendElement(kFontHiraginoSansGB); break; case 0x4d: aFontList.AppendElement(kFontAppleSymbols); break; case 0xa0: // Yi case 0xa1: case 0xa2: case 0xa3: case 0xa4: aFontList.AppendElement(kFontSTHeiti); break; case 0xa6: case 0xa7: aFontList.AppendElement(kFontGeneva); aFontList.AppendElement(kFontAppleSymbols); break; case 0xab: aFontList.AppendElement(kFontKefa); break; case 0xfc: case 0xff: aFontList.AppendElement(kFontAppleSymbols); break; default: break; } } // Arial Unicode MS has lots of glyphs for obscure, use it as a last resort aFontList.AppendElement(kFontArialUnicodeMS); } /*static*/ void gfxPlatformMac::LookupSystemFont( mozilla::LookAndFeel::FontID aSystemFontID, nsACString& aSystemFontName, gfxFontStyle& aFontStyle) { gfxMacPlatformFontList* pfl = gfxMacPlatformFontList::PlatformFontList(); return pfl->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; } // 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() : mGlobalDisplay(new OSXDisplay()) {} Display& GetGlobalDisplay() override { return *mGlobalDisplay; } class OSXDisplay final : public VsyncSource::Display { public: OSXDisplay() : mDisplayLink(nullptr, "OSXVsyncSource::OSXDisplay::mDisplayLink") { MOZ_ASSERT(NS_IsMainThread()); mTimer = NS_NewTimer(); CGDisplayRegisterReconfigurationCallback(DisplayReconfigurationCallback, this); } virtual ~OSXDisplay() { MOZ_ASSERT(NS_IsMainThread()); CGDisplayRemoveReconfigurationCallback(DisplayReconfigurationCallback, this); } static void RetryEnableVsync(nsITimer* aTimer, void* aOsxDisplay) { MOZ_ASSERT(NS_IsMainThread()); OSXDisplay* osxDisplay = static_cast(aOsxDisplay); MOZ_ASSERT(osxDisplay); osxDisplay->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(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 mDisplayLink; // Accessed only from the main thread. RefPtr mTimer; TimeDuration mVsyncRate; }; // OSXDisplay private: virtual ~OSXVsyncSource() = default; RefPtr mGlobalDisplay; }; // 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::OSXDisplay* display = (OSXVsyncSource::OSXDisplay*)aDisplayLinkContext; mozilla::TimeStamp outputTime = mozilla::TimeStamp::FromSystemTime(aOutputTime->hostTime); mozilla::TimeStamp nextVsync = outputTime; mozilla::TimeStamp previousVsync = display->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 // OSXDisplay::mPreviousTimestamp previousVsync = now; } display->mPreviousTimestamp = nextVsync; display->NotifyVsync(previousVsync, outputTime); return kCVReturnSuccess; } already_AddRefed gfxPlatformMac::CreateHardwareVsyncSource() { RefPtr osxVsyncSource = new OSXVsyncSource(); VsyncSource::Display& primaryDisplay = osxVsyncSource->GetGlobalDisplay(); primaryDisplay.EnableVsync(); if (!primaryDisplay.IsVsyncEnabled()) { NS_WARNING( "OS X Vsync source not enabled. Falling back to software vsync."); return gfxPlatform::CreateHardwareVsyncSource(); } primaryDisplay.DisableVsync(); return osxVsyncSource.forget(); } nsTArray gfxPlatformMac::GetPlatformCMSOutputProfileData() { nsTArray prefProfileData = GetPrefCMSOutputProfileData(); if (!prefProfileData.IsEmpty()) { return prefProfileData; } CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID()); if (!cspace) { cspace = ::CGColorSpaceCreateDeviceRGB(); } if (!cspace) { return nsTArray(); } CFDataRef iccp = ::CGColorSpaceCopyICCProfile(cspace); ::CFRelease(cspace); if (!iccp) { return nsTArray(); } // copy to external buffer size_t size = static_cast(::CFDataGetLength(iccp)); nsTArray result; if (size > 0) { result.AppendElements(::CFDataGetBytePtr(iccp), size); } ::CFRelease(iccp); return result; } bool gfxPlatformMac::CheckVariationFontSupport() { // We don't allow variation fonts to be enabled before 10.13, // as although the Core Text APIs existed, they are known to be // fairly buggy. // (Note that Safari also requires 10.13 for variation-font support.) return nsCocoaFeatures::OnHighSierraOrLater(); } 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); }