summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--SECURITY.md5
-rw-r--r--accessible/base/Platform.h4
-rw-r--r--accessible/base/moz.build4
-rw-r--r--accessible/generic/moz.build4
-rw-r--r--accessible/html/moz.build4
-rw-r--r--accessible/ipc/moz.build4
-rw-r--r--accessible/ipc/other/moz.build4
-rw-r--r--accessible/mac/ARIAGridAccessibleWrap.h22
-rw-r--r--accessible/mac/AccessibleWrap.h103
-rw-r--r--accessible/mac/AccessibleWrap.mm256
-rw-r--r--accessible/mac/ApplicationAccessibleWrap.h22
-rw-r--r--accessible/mac/DocAccessibleWrap.h25
-rw-r--r--accessible/mac/DocAccessibleWrap.mm21
-rw-r--r--accessible/mac/HTMLTableAccessibleWrap.h24
-rw-r--r--accessible/mac/HyperTextAccessibleWrap.h20
-rw-r--r--accessible/mac/ImageAccessibleWrap.h22
-rw-r--r--accessible/mac/MacUtils.h26
-rw-r--r--accessible/mac/MacUtils.mm32
-rw-r--r--accessible/mac/Platform.mm174
-rw-r--r--accessible/mac/RootAccessibleWrap.h34
-rw-r--r--accessible/mac/RootAccessibleWrap.mm53
-rw-r--r--accessible/mac/TextLeafAccessibleWrap.h19
-rw-r--r--accessible/mac/XULListboxAccessibleWrap.h20
-rw-r--r--accessible/mac/XULMenuAccessibleWrap.h19
-rw-r--r--accessible/mac/XULTreeGridAccessibleWrap.h20
-rw-r--r--accessible/mac/moz.build44
-rw-r--r--accessible/mac/mozAccessible.h181
-rw-r--r--accessible/mac/mozAccessible.mm1197
-rw-r--r--accessible/mac/mozAccessibleProtocol.h69
-rw-r--r--accessible/mac/mozActionElements.h37
-rw-r--r--accessible/mac/mozActionElements.mm340
-rw-r--r--accessible/mac/mozDocAccessible.h31
-rw-r--r--accessible/mac/mozDocAccessible.mm111
-rw-r--r--accessible/mac/mozHTMLAccessible.h16
-rw-r--r--accessible/mac/mozHTMLAccessible.mm141
-rw-r--r--accessible/mac/mozTableAccessible.h28
-rw-r--r--accessible/mac/mozTableAccessible.mm281
-rw-r--r--accessible/mac/mozTextAccessible.h17
-rw-r--r--accessible/mac/mozTextAccessible.mm627
-rw-r--r--accessible/moz.build2
-rw-r--r--accessible/xpcom/moz.build4
-rw-r--r--accessible/xul/moz.build4
-rwxr-xr-xbuild/macosx/build-cctools.sh26
-rw-r--r--build/macosx/cross-mozconfig.common47
-rw-r--r--build/macosx/local-mozconfig.common46
-rw-r--r--build/macosx/mozconfig.common5
-rw-r--r--build/macosx/permissions/chown_revert.c20
-rw-r--r--build/macosx/permissions/chown_root.c14
-rw-r--r--build/macosx/universal/mozconfig11
-rw-r--r--build/macosx/universal/mozconfig.common55
-rwxr-xr-xbuild/macosx/universal/unify1525
-rw-r--r--build/moz.configure/init.configure14
-rw-r--r--build/moz.configure/toolchain.configure3
-rw-r--r--chrome/nsChromeRegistry.cpp2
-rw-r--r--chrome/nsChromeRegistryChrome.cpp2
-rw-r--r--config/external/nspr/prcpucfg.h4
-rw-r--r--config/rules.mk5
-rw-r--r--db/mork/src/morkConfig.h29
-rw-r--r--db/sqlite3/src/moz.build4
-rw-r--r--devtools/client/framework/dev-edition-promo/dev-edition-promo.css4
-rw-r--r--devtools/client/framework/toolbox-process-window.js10
-rw-r--r--devtools/client/jar.mn2
-rw-r--r--dom/base/Navigator.cpp6
-rw-r--r--dom/base/nsContentUtils.cpp5
-rw-r--r--dom/base/nsFocusManager.cpp13
-rw-r--r--dom/base/nsGlobalWindow.cpp21
-rw-r--r--dom/base/nsJSEnvironment.cpp4
-rw-r--r--dom/base/nsObjectLoadingContent.cpp19
-rw-r--r--dom/base/nsWindowRoot.cpp6
-rw-r--r--dom/canvas/WebGL2Context.cpp7
-rw-r--r--dom/canvas/WebGLBuffer.cpp2
-rw-r--r--dom/canvas/WebGLContext.h13
-rw-r--r--dom/canvas/WebGLContextBuffers.cpp16
-rw-r--r--dom/canvas/WebGLContextDraw.cpp7
-rw-r--r--dom/canvas/WebGLContextValidate.cpp12
-rw-r--r--dom/canvas/WebGLProgram.cpp34
-rw-r--r--dom/canvas/WebGLShaderValidator.cpp37
-rw-r--r--dom/events/EventStateManager.cpp52
-rw-r--r--dom/events/TextComposition.cpp13
-rw-r--r--dom/gamepad/cocoa/CocoaGamepad.cpp590
-rw-r--r--dom/gamepad/moz.build4
-rw-r--r--dom/geolocation/moz.build6
-rw-r--r--dom/html/HTMLButtonElement.cpp6
-rw-r--r--dom/html/HTMLImageElement.cpp3
-rw-r--r--dom/html/HTMLInputElement.cpp10
-rw-r--r--dom/html/HTMLInputElement.h2
-rw-r--r--dom/html/HTMLObjectElement.cpp141
-rw-r--r--dom/html/HTMLObjectElement.h12
-rw-r--r--dom/html/HTMLSharedObjectElement.cpp26
-rw-r--r--dom/html/HTMLSharedObjectElement.h5
-rw-r--r--dom/html/HTMLSummaryElement.cpp9
-rw-r--r--dom/html/nsGenericHTMLElement.cpp4
-rw-r--r--dom/media/GraphDriver.cpp82
-rw-r--r--dom/media/GraphDriver.h5
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.cpp3
-rw-r--r--dom/media/gmp/GMPChild.cpp6
-rw-r--r--dom/media/gmp/rlz/GMPDeviceBinding.cpp48
-rw-r--r--dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp9
-rw-r--r--dom/media/standalone/moz.build3
-rw-r--r--dom/media/systemservices/LoadMonitor.cpp27
-rw-r--r--dom/media/systemservices/OSXRunLoopSingleton.cpp44
-rw-r--r--dom/media/systemservices/OSXRunLoopSingleton.h25
-rw-r--r--dom/media/systemservices/moz.build4
-rwxr-xr-xdom/media/webaudio/AudioContext.cpp2
-rw-r--r--dom/media/webrtc/MediaEngineCameraVideoSource.cpp7
-rw-r--r--dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp56
-rw-r--r--dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h43
-rw-r--r--dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm498
-rw-r--r--dom/media/webspeech/synth/cocoa/moz.build11
-rw-r--r--dom/media/webspeech/synth/moz.build3
-rw-r--r--dom/moz.build2
-rw-r--r--dom/plugins/base/PluginPRLibrary.cpp18
-rw-r--r--dom/plugins/base/PluginPRLibrary.h25
-rw-r--r--dom/plugins/base/moz.build6
-rw-r--r--dom/plugins/base/npapi.h209
-rw-r--r--dom/plugins/base/npfunctions.h32
-rw-r--r--dom/plugins/base/nsNPAPIPlugin.cpp113
-rw-r--r--dom/plugins/base/nsNPAPIPlugin.h4
-rw-r--r--dom/plugins/base/nsNPAPIPluginInstance.cpp41
-rw-r--r--dom/plugins/base/nsNPAPIPluginInstance.h18
-rw-r--r--dom/plugins/base/nsPluginHost.cpp15
-rw-r--r--dom/plugins/base/nsPluginInstanceOwner.cpp896
-rw-r--r--dom/plugins/base/nsPluginInstanceOwner.h63
-rw-r--r--dom/plugins/base/nsPluginNativeWindow.cpp2
-rw-r--r--dom/plugins/base/nsPluginTags.cpp4
-rw-r--r--dom/plugins/base/nsPluginsDirDarwin.cpp572
-rw-r--r--dom/plugins/ipc/NPEventOSX.h193
-rw-r--r--dom/plugins/ipc/PluginInstanceChild.cpp389
-rw-r--r--dom/plugins/ipc/PluginInstanceChild.h17
-rw-r--r--dom/plugins/ipc/PluginInstanceParent.cpp258
-rw-r--r--dom/plugins/ipc/PluginInstanceParent.h5
-rw-r--r--dom/plugins/ipc/PluginInterposeOSX.h137
-rw-r--r--dom/plugins/ipc/PluginInterposeOSX.mm1158
-rw-r--r--dom/plugins/ipc/PluginLibrary.h9
-rw-r--r--dom/plugins/ipc/PluginMessageUtils.cpp5
-rw-r--r--dom/plugins/ipc/PluginMessageUtils.h206
-rw-r--r--dom/plugins/ipc/PluginModuleChild.cpp14
-rwxr-xr-xdom/plugins/ipc/PluginModuleParent.cpp108
-rw-r--r--dom/plugins/ipc/PluginModuleParent.h15
-rw-r--r--dom/plugins/ipc/PluginProcessChild.cpp55
-rw-r--r--dom/plugins/ipc/PluginProcessParent.cpp7
-rw-r--r--dom/plugins/ipc/PluginQuirks.cpp10
-rw-r--r--dom/plugins/ipc/PluginUtilsOSX.h92
-rw-r--r--dom/plugins/ipc/PluginUtilsOSX.mm462
-rw-r--r--dom/plugins/ipc/interpose/moz.build12
-rw-r--r--dom/plugins/ipc/interpose/plugin_child_interpose.mm134
-rw-r--r--dom/plugins/ipc/moz.build16
-rw-r--r--dom/system/OSFileConstants.cpp56
-rw-r--r--dom/system/mac/CoreLocationLocationProvider.h59
-rw-r--r--dom/system/mac/CoreLocationLocationProvider.mm268
-rw-r--r--dom/system/mac/moz.build14
-rw-r--r--dom/system/moz.build2
-rw-r--r--dom/xbl/builtin/mac/jar.mn6
-rw-r--r--dom/xbl/builtin/mac/moz.build6
-rw-r--r--dom/xbl/builtin/mac/platformHTMLBindings.xml72
-rw-r--r--dom/xbl/builtin/moz.build2
-rw-r--r--dom/xbl/nsXBLPrototypeHandler.cpp7
-rw-r--r--dom/xul/nsXULElement.cpp12
-rw-r--r--embedding/components/build/moz.build5
-rw-r--r--embedding/components/printingui/mac/moz.build15
-rw-r--r--embedding/components/printingui/mac/nsPrintProgress.cpp213
-rw-r--r--embedding/components/printingui/mac/nsPrintProgress.h44
-rw-r--r--embedding/components/printingui/mac/nsPrintProgressParams.cpp47
-rw-r--r--embedding/components/printingui/mac/nsPrintProgressParams.h28
-rw-r--r--embedding/components/printingui/mac/nsPrintingPromptService.h43
-rw-r--r--embedding/components/printingui/mac/nsPrintingPromptServiceX.mm128
-rw-r--r--embedding/components/printingui/moz.build2
-rw-r--r--extensions/auth/gssapi.h9
-rw-r--r--extensions/auth/nsAuthGSSAPI.cpp47
-rw-r--r--gfx/2d/2D.h5
-rw-r--r--gfx/2d/Factory.cpp30
-rw-r--r--gfx/2d/JobScheduler_posix.cpp4
-rw-r--r--gfx/2d/MacIOSurface.cpp615
-rw-r--r--gfx/2d/MacIOSurface.h216
-rw-r--r--gfx/2d/NativeFontResourceMac.cpp67
-rw-r--r--gfx/2d/NativeFontResourceMac.h42
-rw-r--r--gfx/2d/PathCG.cpp435
-rw-r--r--gfx/2d/PathCG.h114
-rw-r--r--gfx/2d/QuartzSupport.h98
-rw-r--r--gfx/2d/QuartzSupport.mm625
-rw-r--r--gfx/2d/SFNTNameTable.cpp96
-rw-r--r--gfx/2d/SFNTNameTable.h5
-rw-r--r--gfx/2d/ScaledFontMac.cpp247
-rw-r--r--gfx/2d/ScaledFontMac.h79
-rw-r--r--gfx/2d/moz.build20
-rw-r--r--gfx/cairo/cairo/src/moz.build4
-rw-r--r--gfx/cairo/libpixman/src/moz.build19
-rw-r--r--gfx/gl/ForceDiscreteGPUHelperCGL.h36
-rw-r--r--gfx/gl/GLBlitHelper.cpp111
-rw-r--r--gfx/gl/GLBlitHelper.h3
-rw-r--r--gfx/gl/GLContext.cpp78
-rw-r--r--gfx/gl/GLContextCGL.h67
-rw-r--r--gfx/gl/GLContextEAGL.h80
-rw-r--r--gfx/gl/GLContextProvider.h7
-rw-r--r--gfx/gl/GLContextProviderCGL.mm397
-rw-r--r--gfx/gl/GLContextProviderEAGL.mm275
-rwxr-xr-xgfx/gl/GLScreenBuffer.cpp8
-rw-r--r--gfx/gl/SharedSurfaceIO.cpp248
-rw-r--r--gfx/gl/SharedSurfaceIO.h100
-rw-r--r--gfx/gl/moz.build32
-rw-r--r--gfx/layers/ImageContainer.cpp4
-rw-r--r--gfx/layers/ImageContainer.h6
-rw-r--r--gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp107
-rw-r--r--gfx/layers/basic/MacIOSurfaceTextureHostBasic.h97
-rw-r--r--gfx/layers/basic/TextureHostBasic.cpp10
-rw-r--r--gfx/layers/client/TextureClient.cpp10
-rw-r--r--gfx/layers/composite/LayerManagerComposite.cpp6
-rw-r--r--gfx/layers/composite/TextureHost.cpp4
-rw-r--r--gfx/layers/ipc/ShadowLayerUtils.h4
-rw-r--r--gfx/layers/ipc/ShadowLayerUtilsMac.cpp40
-rw-r--r--gfx/layers/moz.build25
-rw-r--r--gfx/layers/opengl/GLManager.cpp70
-rw-r--r--gfx/layers/opengl/GLManager.h44
-rw-r--r--gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp140
-rw-r--r--gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h58
-rw-r--r--gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp180
-rw-r--r--gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h114
-rw-r--r--gfx/layers/opengl/TextureHostOGL.cpp13
-rwxr-xr-xgfx/skia/generate_mozbuild.py3
-rw-r--r--gfx/skia/moz.build3
-rw-r--r--gfx/src/nsDeviceContext.cpp38
-rw-r--r--gfx/src/nsDeviceContext.h3
-rw-r--r--gfx/thebes/PrintTargetCG.cpp120
-rw-r--r--gfx/thebes/PrintTargetCG.h42
-rw-r--r--gfx/thebes/gfxCoreTextShaper.cpp800
-rw-r--r--gfx/thebes/gfxCoreTextShaper.h71
-rw-r--r--gfx/thebes/gfxFontUtils.cpp41
-rw-r--r--gfx/thebes/gfxMacFont.cpp475
-rw-r--r--gfx/thebes/gfxMacFont.h102
-rw-r--r--gfx/thebes/gfxMacPlatformFontList.h182
-rw-r--r--gfx/thebes/gfxMacPlatformFontList.mm1444
-rw-r--r--gfx/thebes/gfxPlatform.cpp7
-rw-r--r--gfx/thebes/gfxPlatformMac.cpp617
-rw-r--r--gfx/thebes/gfxPlatformMac.h93
-rw-r--r--gfx/thebes/gfxPrefs.h3
-rw-r--r--gfx/thebes/gfxQuartzNativeDrawing.cpp74
-rw-r--r--gfx/thebes/gfxQuartzNativeDrawing.h71
-rw-r--r--gfx/thebes/gfxQuartzSurface.cpp137
-rw-r--r--gfx/thebes/gfxQuartzSurface.h43
-rw-r--r--gfx/thebes/gfxTextRun.cpp5
-rw-r--r--gfx/thebes/moz.build25
-rw-r--r--gfx/ycbcr/convert.patch (renamed from gfx/ycbcr/convert.patch.outdated)0
-rw-r--r--gfx/ycbcr/yuv_row_posix.cpp31
-rw-r--r--hal/cocoa/CocoaSensor.mm149
-rw-r--r--hal/cocoa/smslib.h159
-rw-r--r--hal/cocoa/smslib.mm938
-rw-r--r--hal/moz.build13
-rw-r--r--image/decoders/icon/mac/moz.build10
-rw-r--r--image/decoders/icon/mac/nsIconChannel.h59
-rw-r--r--image/decoders/icon/mac/nsIconChannelCocoa.mm565
-rw-r--r--image/decoders/icon/moz.build3
-rw-r--r--image/decoders/moz.build3
-rw-r--r--image/imgFrame.cpp10
-rw-r--r--intl/locale/mac/moz.build15
-rw-r--r--intl/locale/mac/nsCollationMacUC.cpp253
-rw-r--r--intl/locale/mac/nsCollationMacUC.h44
-rw-r--r--intl/locale/mac/nsDateTimeFormatMac.cpp266
-rw-r--r--intl/locale/mac/nsDateTimeFormatMac.h61
-rw-r--r--intl/locale/mac/nsMacCharset.cpp59
-rw-r--r--intl/locale/moz.build2
-rw-r--r--intl/locale/nsIDateTimeFormat.cpp7
-rw-r--r--intl/locale/nsLocaleConstructors.h11
-rw-r--r--intl/locale/nsLocaleService.cpp37
-rw-r--r--intl/lwbrk/moz.build4
-rw-r--r--intl/lwbrk/nsCarbonBreaker.cpp44
-rw-r--r--ipc/glue/GeckoChildProcessHost.h6
-rw-r--r--ipc/glue/SharedMemoryBasic.h4
-rw-r--r--ipc/glue/SharedMemoryBasic_mach.h83
-rw-r--r--ipc/glue/SharedMemoryBasic_mach.mm664
-rw-r--r--ipc/glue/moz.build8
-rw-r--r--js/src/ds/MemoryProtectionExceptionHandler.cpp466
-rw-r--r--js/src/jit/JitOptions.cpp8
-rw-r--r--js/src/jsmath.cpp2
-rw-r--r--js/src/jsnativestack.cpp7
-rw-r--r--js/src/jsstr.cpp2
-rw-r--r--js/src/old-configure.in5
-rw-r--r--js/src/threading/posix/Thread.cpp4
-rw-r--r--js/src/vm/Runtime.cpp6
-rw-r--r--js/src/vm/Runtime.h7
-rw-r--r--js/src/wasm/WasmSignalHandlers.cpp367
-rw-r--r--js/src/wasm/WasmSignalHandlers.h29
-rw-r--r--js/xpconnect/shell/moz.build5
-rw-r--r--js/xpconnect/shell/xpcshell.cpp11
-rw-r--r--js/xpconnect/shell/xpcshellMacUtils.h8
-rw-r--r--js/xpconnect/shell/xpcshellMacUtils.mm18
-rw-r--r--js/xpconnect/src/Sandbox.cpp11
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp7
-rw-r--r--js/xpconnect/src/XPCShellImpl.cpp24
-rw-r--r--layout/base/nsCSSFrameConstructor.cpp32
-rw-r--r--layout/base/nsCSSFrameConstructor.h4
-rw-r--r--layout/base/nsPresContext.cpp14
-rw-r--r--layout/base/nsPresShell.cpp2
-rw-r--r--layout/build/moz.build4
-rw-r--r--layout/forms/nsListControlFrame.cpp14
-rw-r--r--layout/generic/nsFrame.cpp10
-rw-r--r--layout/generic/nsPluginFrame.cpp127
-rw-r--r--layout/generic/nsSelection.cpp18
-rw-r--r--layout/printing/nsPrintEngine.cpp5
-rw-r--r--layout/reftests/reftest.list3
-rw-r--r--layout/style/nsComputedDOMStyle.cpp9
-rw-r--r--layout/style/res/forms.css6
-rw-r--r--layout/xul/nsButtonBoxFrame.cpp3
-rw-r--r--layout/xul/nsMenuBarListener.cpp12
-rw-r--r--layout/xul/nsMenuFrame.cpp16
-rw-r--r--layout/xul/nsMenuPopupFrame.cpp11
-rw-r--r--layout/xul/nsRepeatService.h4
-rw-r--r--layout/xul/nsSliderFrame.cpp12
-rw-r--r--layout/xul/nsXULPopupManager.cpp28
-rw-r--r--layout/xul/tree/nsTreeBodyFrame.cpp53
-rw-r--r--mailnews/addrbook/public/nsAbBaseCID.h41
-rw-r--r--mailnews/base/ispdata/moz.build4
-rw-r--r--mailnews/base/prefs/content/AccountWizard.xul4
-rw-r--r--mailnews/base/search/src/nsMsgFilter.cpp5
-rw-r--r--mailnews/base/src/moz.build2
-rw-r--r--mailnews/base/src/nsMessenger.cpp6
-rw-r--r--mailnews/base/src/nsMessengerOSXIntegration.h63
-rw-r--r--mailnews/base/src/nsMessengerOSXIntegration.mm700
-rw-r--r--mailnews/base/src/nsMsgMailSession.cpp8
-rw-r--r--mailnews/base/src/nsStatusBarBiffManager.cpp2
-rw-r--r--mailnews/base/util/nsMsgUtils.cpp4
-rw-r--r--mailnews/build/moz.build4
-rw-r--r--mailnews/build/nsMailModule.cpp46
-rw-r--r--mailnews/compose/src/moz.build10
-rw-r--r--mailnews/compose/src/nsMsgAppleCodes.h106
-rw-r--r--mailnews/compose/src/nsMsgAppleDouble.h207
-rw-r--r--mailnews/compose/src/nsMsgAppleDoubleEncode.cpp266
-rw-r--r--mailnews/compose/src/nsMsgAppleEncode.cpp703
-rw-r--r--mailnews/compose/src/nsMsgAttachmentHandler.cpp351
-rw-r--r--mailnews/compose/src/nsMsgAttachmentHandler.h50
-rw-r--r--mailnews/compose/src/nsMsgSend.cpp4
-rw-r--r--mailnews/imap/src/nsIMAPBodyShell.cpp19
-rw-r--r--mailnews/import/applemail/src/moz.build14
-rw-r--r--mailnews/import/applemail/src/nsAppleMailImport.cpp623
-rw-r--r--mailnews/import/applemail/src/nsAppleMailImport.h78
-rw-r--r--mailnews/import/applemail/src/nsEmlxHelperUtils.h55
-rw-r--r--mailnews/import/applemail/src/nsEmlxHelperUtils.mm240
-rw-r--r--mailnews/import/build/moz.build7
-rw-r--r--mailnews/import/build/nsImportModule.cpp31
-rw-r--r--mailnews/import/content/importDialog.xul4
-rw-r--r--mailnews/jar.mn2
-rw-r--r--mailnews/mailnews.js22
-rw-r--r--mailnews/mime/src/mimeebod.cpp14
-rw-r--r--mailnews/mime/src/mimemapl.cpp24
-rw-r--r--mailnews/mime/src/mimemult.cpp17
-rw-r--r--mailnews/moz.build5
-rw-r--r--media/ffvpx/config.h2
-rw-r--r--media/ffvpx/config_darwin64.asm642
-rw-r--r--media/ffvpx/config_darwin64.h658
-rw-r--r--media/libav/config.h2
-rw-r--r--media/libav/config_darwin.asm249
-rw-r--r--media/libav/config_darwin.h259
-rw-r--r--media/libcubeb/src/cubeb_osx_run_loop.c11
-rw-r--r--media/libcubeb/src/cubeb_osx_run_loop.h22
-rw-r--r--media/libcubeb/src/moz.build4
-rw-r--r--memory/build/mozmemory.h15
-rw-r--r--memory/build/mozmemory_wrap.c2
-rw-r--r--memory/build/mozmemory_wrap.h2
-rw-r--r--memory/build/replace_malloc.c111
-rw-r--r--memory/mozalloc/mozalloc.cpp19
-rw-r--r--memory/mozalloc/mozalloc.h10
-rw-r--r--memory/volatile/VolatileBuffer.h4
-rw-r--r--mfbt/ThreadLocal.h4
-rw-r--r--modules/libmar/tool/mar.c21
-rw-r--r--modules/libpref/Preferences.cpp4
-rw-r--r--modules/libpref/init/all.js410
-rw-r--r--mozglue/misc/StackWalk.cpp122
-rw-r--r--mozglue/misc/TimeStamp.h9
-rw-r--r--netwerk/base/NetworkInfoServiceCocoa.cpp103
-rw-r--r--netwerk/base/moz.build9
-rw-r--r--netwerk/base/nsSocketTransport2.cpp11
-rw-r--r--netwerk/base/nsURLHelperOSX.cpp216
-rw-r--r--netwerk/build/moz.build7
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp779
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderOperator.h152
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp302
-rw-r--r--netwerk/dns/mdns/libmdns/MDNSResponderReply.h164
-rw-r--r--netwerk/dns/mdns/libmdns/moz.build38
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp262
-rw-r--r--netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h49
-rw-r--r--netwerk/streamconv/converters/moz.build6
-rw-r--r--netwerk/system/mac/moz.build13
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.h54
-rw-r--r--netwerk/system/mac/nsNetworkLinkService.mm526
-rw-r--r--netwerk/system/moz.build2
-rw-r--r--nsprpub/pr/src/linking/prlink.c41
-rw-r--r--old-configure.in222
-rw-r--r--python/mozbuild/mozbuild/artifacts.py4
-rw-r--r--python/psutil/psutil/_psutil_posix.c1
-rw-r--r--testing/crashtest/crashtests.list1
-rw-r--r--toolkit/components/apppicker/content/appPicker.js8
-rw-r--r--toolkit/components/blocklist/nsBlocklistService.js9
-rw-r--r--toolkit/components/downloads/nsDownloadManager.cpp42
-rw-r--r--toolkit/components/jsdownloads/src/DownloadIntegration.jsm8
-rw-r--r--toolkit/components/jsdownloads/src/DownloadPlatform.cpp80
-rw-r--r--toolkit/components/jsdownloads/src/DownloadUIHelper.jsm7
-rw-r--r--toolkit/components/jsdownloads/src/moz.build2
-rw-r--r--toolkit/components/parentalcontrols/moz.build2
-rw-r--r--toolkit/components/parentalcontrols/nsParentalControlsServiceCocoa.mm79
-rw-r--r--toolkit/components/passwordmgr/content/passwordManager.js5
-rw-r--r--toolkit/components/passwordmgr/content/passwordManager.xul2
-rw-r--r--toolkit/components/perfmonitoring/nsPerformanceStats.cpp36
-rw-r--r--toolkit/components/places/PlacesUtils.jsm8
-rw-r--r--toolkit/components/places/moz.build3
-rw-r--r--toolkit/components/printing/jar.mn2
-rw-r--r--toolkit/components/prompts/content/commonDialog.xul12
-rw-r--r--toolkit/components/prompts/content/tabprompts.xml17
-rw-r--r--toolkit/components/prompts/jar.mn2
-rw-r--r--toolkit/components/satchel/nsFormFillController.cpp16
-rw-r--r--toolkit/components/startup/moz.build2
-rw-r--r--toolkit/components/startup/nsAppStartup.cpp43
-rw-r--r--toolkit/components/startup/nsUserInfoMac.h25
-rw-r--r--toolkit/components/startup/nsUserInfoMac.mm84
-rw-r--r--toolkit/components/thumbnails/PageThumbUtils.jsm12
-rw-r--r--toolkit/components/thumbnails/moz.build2
-rw-r--r--toolkit/components/viewsource/content/viewPartialSource.xul2
-rw-r--r--toolkit/components/viewsource/content/viewSource.xul14
-rw-r--r--toolkit/components/viewsource/jar.mn2
-rw-r--r--toolkit/content/aboutProfiles.js2
-rw-r--r--toolkit/content/aboutSupport.xhtml8
-rw-r--r--toolkit/content/customizeToolbar.js4
-rw-r--r--toolkit/content/dialogOverlay.xul44
-rw-r--r--toolkit/content/globalOverlay.js7
-rw-r--r--toolkit/content/jar.mn15
-rw-r--r--toolkit/content/license.html2
-rw-r--r--toolkit/content/macWindowMenu.inc40
-rw-r--r--toolkit/content/macWindowMenu.js51
-rw-r--r--toolkit/content/widgets/dialog.xml4
-rw-r--r--toolkit/content/widgets/findbar.xml33
-rw-r--r--toolkit/content/widgets/optionsDialog.xml11
-rw-r--r--toolkit/content/widgets/preferences.xml4
-rw-r--r--toolkit/content/widgets/tree.xml2
-rw-r--r--toolkit/content/widgets/wizard.xml49
-rw-r--r--toolkit/content/xul.css21
-rw-r--r--toolkit/library/moz.build42
-rw-r--r--toolkit/modules/AppConstants.jsm2
-rw-r--r--toolkit/modules/LightweightThemeConsumer.jsm23
-rw-r--r--toolkit/modules/UpdateUtils.jsm11
-rw-r--r--toolkit/modules/WindowDraggingUtils.jsm2
-rw-r--r--toolkit/modules/moz.build4
-rw-r--r--toolkit/moz.build2
-rw-r--r--toolkit/moz.configure8
-rw-r--r--toolkit/mozapps/downloads/content/downloads.xul10
-rw-r--r--toolkit/mozapps/downloads/content/unknownContentType.xul4
-rw-r--r--toolkit/mozapps/downloads/nsHelperAppDlg.js10
-rw-r--r--toolkit/mozapps/extensions/GMPUtils.jsm15
-rw-r--r--toolkit/mozapps/extensions/content/extensions.js4
-rw-r--r--toolkit/mozapps/extensions/content/update.xul16
-rw-r--r--toolkit/mozapps/extensions/jar.mn2
-rw-r--r--toolkit/mozapps/update/common/updatecommon.cpp4
-rw-r--r--toolkit/mozapps/update/common/updatedefines.h4
-rw-r--r--toolkit/mozapps/update/nsUpdateService.js137
-rw-r--r--toolkit/mozapps/update/updater/Makefile.in13
-rw-r--r--toolkit/mozapps/update/updater/launchchild_osx.mm384
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in39
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo1
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in7
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib19
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib22
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nibbin0 -> 5567 bytes
-rw-r--r--toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icnsbin0 -> 55969 bytes
-rw-r--r--toolkit/mozapps/update/updater/moz.build15
-rw-r--r--toolkit/mozapps/update/updater/progressui.h3
-rw-r--r--toolkit/mozapps/update/updater/progressui_osx.mm144
-rw-r--r--toolkit/mozapps/update/updater/updater-common.build18
-rw-r--r--toolkit/mozapps/update/updater/updater.cpp277
-rw-r--r--toolkit/profile/content/createProfileWizard.js4
-rw-r--r--toolkit/profile/content/createProfileWizard.xul4
-rw-r--r--toolkit/profile/content/profileSelection.js2
-rw-r--r--toolkit/profile/jar.mn4
-rw-r--r--toolkit/profile/nsProfileLock.cpp72
-rw-r--r--toolkit/profile/nsToolkitProfileService.cpp33
-rw-r--r--toolkit/system/osxproxy/ProxyUtils.h21
-rw-r--r--toolkit/system/osxproxy/ProxyUtils.mm182
-rw-r--r--toolkit/system/osxproxy/moz.build13
-rw-r--r--toolkit/system/osxproxy/nsOSXSystemProxySettings.mm326
-rw-r--r--toolkit/system/osxproxy/tests/gtest/TestProxyBypassRules.cpp41
-rw-r--r--toolkit/system/osxproxy/tests/gtest/moz.build17
-rw-r--r--toolkit/themes/moz.build4
-rw-r--r--toolkit/themes/osx/global/10pct_transparent_grey.pngbin0 -> 123 bytes
-rw-r--r--toolkit/themes/osx/global/50pct_transparent_grey.pngbin0 -> 107 bytes
-rw-r--r--toolkit/themes/osx/global/alerts/alert.css30
-rw-r--r--toolkit/themes/osx/global/arrow.css38
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-dn-dis.gifbin0 -> 65 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-dn-dis.pngbin0 -> 185 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-dn-sharp.gifbin0 -> 51 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-dn.gifbin0 -> 56 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-dn.pngbin0 -> 191 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-lft-dis.gifbin0 -> 105 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-lft-hov.gifbin0 -> 57 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-lft-sharp-end.gifbin0 -> 56 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-lft-sharp.gifbin0 -> 53 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-lft.gifbin0 -> 57 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-rit-dis.gifbin0 -> 105 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-rit-hov.gifbin0 -> 57 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-rit-sharp-end.gifbin0 -> 56 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-rit-sharp.gifbin0 -> 53 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-rit.gifbin0 -> 57 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-up-dis.gifbin0 -> 65 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-up-sharp.gifbin0 -> 52 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/arrow-up.gifbin0 -> 56 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/panelarrow-horizontal.pngbin0 -> 117 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/panelarrow-horizontal@2x.pngbin0 -> 267 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/panelarrow-vertical.pngbin0 -> 133 bytes
-rw-r--r--toolkit/themes/osx/global/arrow/panelarrow-vertical@2x.pngbin0 -> 227 bytes
-rw-r--r--toolkit/themes/osx/global/autocomplete.css174
-rw-r--r--toolkit/themes/osx/global/button.css85
-rw-r--r--toolkit/themes/osx/global/checkbox.css39
-rw-r--r--toolkit/themes/osx/global/checkbox/cbox-check-dis.gifbin0 -> 60 bytes
-rw-r--r--toolkit/themes/osx/global/checkbox/cbox-check.gifbin0 -> 54 bytes
-rw-r--r--toolkit/themes/osx/global/colorpicker.css41
-rw-r--r--toolkit/themes/osx/global/commonDialog.css35
-rw-r--r--toolkit/themes/osx/global/console/console-error-caret.gifbin0 -> 55 bytes
-rw-r--r--toolkit/themes/osx/global/console/console-error-dash.gifbin0 -> 48 bytes
-rw-r--r--toolkit/themes/osx/global/console/console.css165
-rw-r--r--toolkit/themes/osx/global/customizeToolbar.css38
-rw-r--r--toolkit/themes/osx/global/datetimepicker.css126
-rw-r--r--toolkit/themes/osx/global/dialog.css77
-rw-r--r--toolkit/themes/osx/global/dirListing/dirListing.css104
-rw-r--r--toolkit/themes/osx/global/dirListing/folder.pngbin0 -> 325 bytes
-rw-r--r--toolkit/themes/osx/global/dirListing/remote.pngbin0 -> 563 bytes
-rw-r--r--toolkit/themes/osx/global/dirListing/up.pngbin0 -> 617 bytes
-rw-r--r--toolkit/themes/osx/global/dropmarker.css31
-rw-r--r--toolkit/themes/osx/global/filefield.css38
-rw-r--r--toolkit/themes/osx/global/filters.svg14
-rw-r--r--toolkit/themes/osx/global/findBar.css270
-rw-r--r--toolkit/themes/osx/global/global.css378
-rw-r--r--toolkit/themes/osx/global/groupbox.css30
-rw-r--r--toolkit/themes/osx/global/icons/Error.pngbin0 -> 1439 bytes
-rw-r--r--toolkit/themes/osx/global/icons/autocomplete-dropmarker.pngbin0 -> 234 bytes
-rw-r--r--toolkit/themes/osx/global/icons/autocomplete-search.svg22
-rw-r--r--toolkit/themes/osx/global/icons/autoscroll.pngbin0 -> 2983 bytes
-rw-r--r--toolkit/themes/osx/global/icons/blacklist_64.pngbin0 -> 3771 bytes
-rw-r--r--toolkit/themes/osx/global/icons/blacklist_favicon.pngbin0 -> 543 bytes
-rw-r--r--toolkit/themes/osx/global/icons/checkbox.pngbin0 -> 1737 bytes
-rw-r--r--toolkit/themes/osx/global/icons/checkbox@2x.pngbin0 -> 1824 bytes
-rw-r--r--toolkit/themes/osx/global/icons/chevron-inverted.pngbin0 -> 247 bytes
-rw-r--r--toolkit/themes/osx/global/icons/chevron-inverted@2x.pngbin0 -> 481 bytes
-rw-r--r--toolkit/themes/osx/global/icons/chevron.pngbin0 -> 251 bytes
-rw-r--r--toolkit/themes/osx/global/icons/chevron@2x.pngbin0 -> 462 bytes
-rw-r--r--toolkit/themes/osx/global/icons/close.pngbin0 -> 1240 bytes
-rwxr-xr-xtoolkit/themes/osx/global/icons/close@2x.pngbin0 -> 2768 bytes
-rw-r--r--toolkit/themes/osx/global/icons/error-16.pngbin0 -> 677 bytes
-rw-r--r--toolkit/themes/osx/global/icons/error-64.pngbin0 -> 2533 bytes
-rw-r--r--toolkit/themes/osx/global/icons/error-large.pngbin0 -> 1996 bytes
-rw-r--r--toolkit/themes/osx/global/icons/glyph-dropdown.pngbin0 -> 99 bytes
-rw-r--r--toolkit/themes/osx/global/icons/glyph-dropdown@2x.pngbin0 -> 130 bytes
-rw-r--r--toolkit/themes/osx/global/icons/information-16.pngbin0 -> 818 bytes
-rw-r--r--toolkit/themes/osx/global/icons/information-24.pngbin0 -> 1289 bytes
-rw-r--r--toolkit/themes/osx/global/icons/information-32.pngbin0 -> 1773 bytes
-rw-r--r--toolkit/themes/osx/global/icons/information-64.pngbin0 -> 3687 bytes
-rw-r--r--toolkit/themes/osx/global/icons/information-large.pngbin0 -> 2592 bytes
-rw-r--r--toolkit/themes/osx/global/icons/loading_16.pngbin0 -> 9025 bytes
-rw-r--r--toolkit/themes/osx/global/icons/menulist-dropmarker.pngbin0 -> 158 bytes
-rw-r--r--toolkit/themes/osx/global/icons/notfound.pngbin0 -> 597 bytes
-rw-r--r--toolkit/themes/osx/global/icons/notloading_16.pngbin0 -> 686 bytes
-rw-r--r--toolkit/themes/osx/global/icons/panebutton-active.pngbin0 -> 400 bytes
-rw-r--r--toolkit/themes/osx/global/icons/panebutton-inactive.pngbin0 -> 257 bytes
-rw-r--r--toolkit/themes/osx/global/icons/panel-dropmarker.pngbin0 -> 161 bytes
-rw-r--r--toolkit/themes/osx/global/icons/question-16.pngbin0 -> 866 bytes
-rw-r--r--toolkit/themes/osx/global/icons/question-32.pngbin0 -> 1962 bytes
-rw-r--r--toolkit/themes/osx/global/icons/question-64.pngbin0 -> 3970 bytes
-rw-r--r--toolkit/themes/osx/global/icons/question-large.pngbin0 -> 2851 bytes
-rw-r--r--toolkit/themes/osx/global/icons/resizer-rtl.pngbin0 -> 192 bytes
-rw-r--r--toolkit/themes/osx/global/icons/resizer-rtl@2x.pngbin0 -> 284 bytes
-rw-r--r--toolkit/themes/osx/global/icons/resizer.pngbin0 -> 196 bytes
-rw-r--r--toolkit/themes/osx/global/icons/resizer@2x.pngbin0 -> 288 bytes
-rw-r--r--toolkit/themes/osx/global/icons/search-textbox.svg13
-rw-r--r--toolkit/themes/osx/global/icons/searchfield-cancel.svg20
-rw-r--r--toolkit/themes/osx/global/icons/sslWarning.pngbin0 -> 4120 bytes
-rw-r--r--toolkit/themes/osx/global/icons/tabprompts-bgtexture.pngbin0 -> 5940 bytes
-rw-r--r--toolkit/themes/osx/global/icons/warning-16.pngbin0 -> 690 bytes
-rw-r--r--toolkit/themes/osx/global/icons/warning-32.pngbin0 -> 1483 bytes
-rw-r--r--toolkit/themes/osx/global/icons/warning-64.pngbin0 -> 3308 bytes
-rw-r--r--toolkit/themes/osx/global/icons/warning-large.pngbin0 -> 2281 bytes
-rw-r--r--toolkit/themes/osx/global/in-content/common.css121
-rw-r--r--toolkit/themes/osx/global/in-content/info-pages.css1
-rw-r--r--toolkit/themes/osx/global/inContentUI.css144
-rw-r--r--toolkit/themes/osx/global/jar.mn156
-rw-r--r--toolkit/themes/osx/global/linkTree.css32
-rw-r--r--toolkit/themes/osx/global/listbox.css113
-rw-r--r--toolkit/themes/osx/global/menu.css187
-rw-r--r--toolkit/themes/osx/global/menulist.css65
-rw-r--r--toolkit/themes/osx/global/moz.build6
-rw-r--r--toolkit/themes/osx/global/nativescrollbars.css89
-rw-r--r--toolkit/themes/osx/global/netError.css145
-rw-r--r--toolkit/themes/osx/global/notification.css206
-rw-r--r--toolkit/themes/osx/global/notification/close.pngbin0 -> 795 bytes
-rw-r--r--toolkit/themes/osx/global/notification/error-icon.pngbin0 -> 518 bytes
-rw-r--r--toolkit/themes/osx/global/notification/info-icon.pngbin0 -> 533 bytes
-rw-r--r--toolkit/themes/osx/global/notification/warning-icon.pngbin0 -> 626 bytes
-rw-r--r--toolkit/themes/osx/global/numberbox.css33
-rw-r--r--toolkit/themes/osx/global/popup.css141
-rw-r--r--toolkit/themes/osx/global/preferences.css64
-rw-r--r--toolkit/themes/osx/global/progressmeter.css22
-rw-r--r--toolkit/themes/osx/global/radio.css43
-rw-r--r--toolkit/themes/osx/global/resizer.css69
-rw-r--r--toolkit/themes/osx/global/richlistbox.css27
-rw-r--r--toolkit/themes/osx/global/scale.css46
-rw-r--r--toolkit/themes/osx/global/scale/scale-tray-horiz.gifbin0 -> 50 bytes
-rw-r--r--toolkit/themes/osx/global/scale/scale-tray-vert.gifbin0 -> 50 bytes
-rw-r--r--toolkit/themes/osx/global/scrollbox.css62
-rw-r--r--toolkit/themes/osx/global/shared.inc20
-rw-r--r--toolkit/themes/osx/global/spinbuttons.css31
-rw-r--r--toolkit/themes/osx/global/splitter.css124
-rw-r--r--toolkit/themes/osx/global/splitter/dimple.pngbin0 -> 155 bytes
-rw-r--r--toolkit/themes/osx/global/splitter/grip-bottom.gifbin0 -> 145 bytes
-rw-r--r--toolkit/themes/osx/global/splitter/grip-left.gifbin0 -> 157 bytes
-rw-r--r--toolkit/themes/osx/global/splitter/grip-right.gifbin0 -> 157 bytes
-rw-r--r--toolkit/themes/osx/global/splitter/grip-top.gifbin0 -> 144 bytes
-rw-r--r--toolkit/themes/osx/global/tabbox.css148
-rw-r--r--toolkit/themes/osx/global/tabprompts.css67
-rw-r--r--toolkit/themes/osx/global/textbox.css102
-rw-r--r--toolkit/themes/osx/global/toolbar.css127
-rw-r--r--toolkit/themes/osx/global/toolbar/spring.pngbin0 -> 239 bytes
-rw-r--r--toolkit/themes/osx/global/toolbar/toolbar-separator.pngbin0 -> 115 bytes
-rw-r--r--toolkit/themes/osx/global/toolbarbutton.css124
-rw-r--r--toolkit/themes/osx/global/tree.css296
-rw-r--r--toolkit/themes/osx/global/tree/arrow-disclosure.svg28
-rw-r--r--toolkit/themes/osx/global/tree/columnpicker.gifbin0 -> 68 bytes
-rw-r--r--toolkit/themes/osx/global/tree/folder.pngbin0 -> 320 bytes
-rw-r--r--toolkit/themes/osx/global/tree/folder@2x.pngbin0 -> 589 bytes
-rw-r--r--toolkit/themes/osx/global/viewbuttons.css36
-rw-r--r--toolkit/themes/osx/global/wizard.css62
-rw-r--r--toolkit/themes/osx/mochitests/.eslintrc.js7
-rw-r--r--toolkit/themes/osx/mochitests/chrome.ini3
-rw-r--r--toolkit/themes/osx/mochitests/test_bug510426.xul54
-rw-r--r--toolkit/themes/osx/moz.build8
-rw-r--r--toolkit/themes/osx/mozapps/downloads/buttons.pngbin0 -> 2288 bytes
-rw-r--r--toolkit/themes/osx/mozapps/downloads/downloadIcon.pngbin0 -> 1301 bytes
-rw-r--r--toolkit/themes/osx/mozapps/downloads/downloads.css123
-rw-r--r--toolkit/themes/osx/mozapps/downloads/unknownContentType.css30
-rw-r--r--toolkit/themes/osx/mozapps/extensions/about.css78
-rw-r--r--toolkit/themes/osx/mozapps/extensions/alerticon-error.pngbin0 -> 3402 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/alerticon-info-negative.pngbin0 -> 1564 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/alerticon-info-positive.pngbin0 -> 1338 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/alerticon-warning.pngbin0 -> 1567 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/blocklist.css20
-rw-r--r--toolkit/themes/osx/mozapps/extensions/cancel.pngbin0 -> 115 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-available.pngbin0 -> 1671 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-dictionaries.pngbin0 -> 1769 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-discover.pngbin0 -> 1324 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-experiments.pngbin0 -> 822 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-plugins.pngbin0 -> 886 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-recent.pngbin0 -> 1642 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-search.pngbin0 -> 2600 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-searchengines.pngbin0 -> 2814 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/category-service.pngbin0 -> 2063 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.pngbin0 -> 742 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.pngbin0 -> 1769 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/discover-logo.pngbin0 -> 12007 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/eula.css47
-rw-r--r--toolkit/themes/osx/mozapps/extensions/experimentGeneric.pngbin0 -> 822 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.pngbin0 -> 554 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/extensionGeneric.pngbin0 -> 1302 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/extensions.css1206
-rw-r--r--toolkit/themes/osx/mozapps/extensions/heart.pngbin0 -> 2949 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/localeGeneric.pngbin0 -> 2410 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/navigation.pngbin0 -> 586 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/newaddon.css112
-rw-r--r--toolkit/themes/osx/mozapps/extensions/rating-not-won.pngbin0 -> 1559 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/rating-won.pngbin0 -> 1662 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/search.pngbin0 -> 423 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/selectAddons.css163
-rw-r--r--toolkit/themes/osx/mozapps/extensions/stripes-compatibility.pngbin0 -> 1041 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/stripes-error.pngbin0 -> 1979 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/stripes-info-negative.pngbin0 -> 2027 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/stripes-info-positive.pngbin0 -> 1852 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/stripes-warning.pngbin0 -> 2177 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/themeGeneric-16.pngbin0 -> 710 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/themeGeneric.pngbin0 -> 2185 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.pngbin0 -> 147 bytes
-rw-r--r--toolkit/themes/osx/mozapps/extensions/update.css26
-rw-r--r--toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css90
-rw-r--r--toolkit/themes/osx/mozapps/handling/handling.css30
-rw-r--r--toolkit/themes/osx/mozapps/jar.mn79
-rw-r--r--toolkit/themes/osx/mozapps/moz.build6
-rw-r--r--toolkit/themes/osx/mozapps/passwordmgr/key-16.pngbin0 -> 773 bytes
-rw-r--r--toolkit/themes/osx/mozapps/passwordmgr/key-64.pngbin0 -> 6142 bytes
-rw-r--r--toolkit/themes/osx/mozapps/passwordmgr/key.pngbin0 -> 658 bytes
-rw-r--r--toolkit/themes/osx/mozapps/plugins/notifyPluginGeneric.pngbin0 -> 313 bytes
-rw-r--r--toolkit/themes/osx/mozapps/plugins/pluginBlocked-64.pngbin0 -> 4563 bytes
-rw-r--r--toolkit/themes/osx/mozapps/plugins/pluginBlocked.pngbin0 -> 2152 bytes
-rw-r--r--toolkit/themes/osx/mozapps/plugins/pluginGeneric-16.pngbin0 -> 759 bytes
-rw-r--r--toolkit/themes/osx/mozapps/plugins/pluginGeneric.pngbin0 -> 1939 bytes
-rw-r--r--toolkit/themes/osx/mozapps/plugins/pluginHelp-16.pngbin0 -> 620 bytes
-rw-r--r--toolkit/themes/osx/mozapps/profile/profileSelection.css29
-rw-r--r--toolkit/themes/osx/mozapps/profile/profileicon-selected.pngbin0 -> 502 bytes
-rw-r--r--toolkit/themes/osx/mozapps/profile/profileicon.pngbin0 -> 588 bytes
-rw-r--r--toolkit/themes/osx/mozapps/update/buttons.pngbin0 -> 2288 bytes
-rw-r--r--toolkit/themes/osx/mozapps/update/updates.css171
-rw-r--r--toolkit/themes/osx/mozapps/viewsource/viewsource.css5
-rw-r--r--toolkit/themes/osx/reftests/482681-ref.xul21
-rw-r--r--toolkit/themes/osx/reftests/482681.xul22
-rw-r--r--toolkit/themes/osx/reftests/baseline.xul175
-rw-r--r--toolkit/themes/osx/reftests/checkboxsize-ref.xul32
-rw-r--r--toolkit/themes/osx/reftests/checkboxsize.xul31
-rw-r--r--toolkit/themes/osx/reftests/nostretch-ref.xul107
-rw-r--r--toolkit/themes/osx/reftests/nostretch.xul120
-rw-r--r--toolkit/themes/osx/reftests/radiosize-ref.xul32
-rw-r--r--toolkit/themes/osx/reftests/radiosize.xul31
-rw-r--r--toolkit/themes/osx/reftests/reftest-stylo.list6
-rw-r--r--toolkit/themes/osx/reftests/reftest.list5
-rw-r--r--toolkit/themes/shared/aboutReader.css14
-rw-r--r--toolkit/themes/shared/jar.inc.mn2
-rw-r--r--toolkit/themes/shared/media/videocontrols.css12
-rw-r--r--toolkit/xre/MacApplicationDelegate.h16
-rw-r--r--toolkit/xre/MacApplicationDelegate.mm396
-rw-r--r--toolkit/xre/MacAutoreleasePool.h31
-rw-r--r--toolkit/xre/MacAutoreleasePool.mm20
-rw-r--r--toolkit/xre/MacLaunchHelper.h23
-rw-r--r--toolkit/xre/MacLaunchHelper.mm137
-rw-r--r--toolkit/xre/moz.build15
-rw-r--r--toolkit/xre/nsAppRunner.cpp100
-rw-r--r--toolkit/xre/nsEmbedFunctions.cpp140
-rw-r--r--toolkit/xre/nsSigHandlers.cpp16
-rw-r--r--toolkit/xre/nsUpdateDriver.cpp143
-rw-r--r--toolkit/xre/nsXREDirProvider.cpp153
-rw-r--r--toolkit/xre/nsXREDirProvider.h2
-rw-r--r--tools/fuzzing/libfuzzer/harness/LibFuzzerTestHarness.h8
-rw-r--r--tools/profiler/tasktracer/GeckoTaskTracer.cpp5
-rw-r--r--uriloader/exthandler/mac/nsDecodeAppleFile.cpp389
-rw-r--r--uriloader/exthandler/mac/nsDecodeAppleFile.h118
-rw-r--r--uriloader/exthandler/mac/nsLocalHandlerAppMac.h26
-rw-r--r--uriloader/exthandler/mac/nsLocalHandlerAppMac.mm84
-rw-r--r--uriloader/exthandler/mac/nsMIMEInfoMac.h34
-rw-r--r--uriloader/exthandler/mac/nsMIMEInfoMac.mm114
-rw-r--r--uriloader/exthandler/mac/nsOSHelperAppService.h48
-rw-r--r--uriloader/exthandler/mac/nsOSHelperAppService.mm569
-rw-r--r--uriloader/exthandler/moz.build10
-rw-r--r--uriloader/exthandler/nsExternalHelperAppService.cpp58
-rw-r--r--uriloader/exthandler/nsLocalHandlerApp.h9
-rw-r--r--view/nsView.cpp4
-rw-r--r--view/nsViewManager.cpp6
-rw-r--r--widget/CompositorWidget.h6
-rw-r--r--widget/GfxInfoBase.cpp8
-rw-r--r--widget/NativeKeyToDOMCodeName.h6
-rw-r--r--widget/NativeKeyToDOMKeyName.h4
-rw-r--r--widget/TextEvents.h31
-rw-r--r--widget/WidgetEventImpl.cpp4
-rw-r--r--widget/cocoa/ComplexTextInputPanel.h48
-rw-r--r--widget/cocoa/ComplexTextInputPanel.mm261
-rw-r--r--widget/cocoa/CustomCocoaEvents.h18
-rw-r--r--widget/cocoa/GfxInfo.h95
-rw-r--r--widget/cocoa/GfxInfo.mm426
-rw-r--r--widget/cocoa/NativeKeyBindings.h48
-rw-r--r--widget/cocoa/NativeKeyBindings.mm292
-rw-r--r--widget/cocoa/OSXNotificationCenter.h55
-rw-r--r--widget/cocoa/OSXNotificationCenter.mm589
-rw-r--r--widget/cocoa/RectTextureImage.h80
-rw-r--r--widget/cocoa/RectTextureImage.mm171
-rw-r--r--widget/cocoa/SwipeTracker.h95
-rw-r--r--widget/cocoa/SwipeTracker.mm219
-rw-r--r--widget/cocoa/TextInputHandler.h1194
-rw-r--r--widget/cocoa/TextInputHandler.mm4533
-rw-r--r--widget/cocoa/VibrancyManager.h120
-rw-r--r--widget/cocoa/VibrancyManager.mm244
-rw-r--r--widget/cocoa/ViewRegion.h52
-rw-r--r--widget/cocoa/ViewRegion.mm70
-rw-r--r--widget/cocoa/WidgetTraceEvent.mm85
-rw-r--r--widget/cocoa/crashtests/373122-1-inner.html39
-rw-r--r--widget/cocoa/crashtests/373122-1.html9
-rw-r--r--widget/cocoa/crashtests/397209-1.html7
-rw-r--r--widget/cocoa/crashtests/403296-1.xhtml10
-rw-r--r--widget/cocoa/crashtests/419737-1.html8
-rw-r--r--widget/cocoa/crashtests/435223-1.html8
-rw-r--r--widget/cocoa/crashtests/444260-1.xul3
-rw-r--r--widget/cocoa/crashtests/444864-1.html6
-rw-r--r--widget/cocoa/crashtests/449111-1.html4
-rw-r--r--widget/cocoa/crashtests/460349-1.xhtml4
-rw-r--r--widget/cocoa/crashtests/460387-1.html2
-rw-r--r--widget/cocoa/crashtests/464589-1.html20
-rw-r--r--widget/cocoa/crashtests/crashtests.list11
-rw-r--r--widget/cocoa/cursors/arrowN.pngbin0 -> 256 bytes
-rw-r--r--widget/cocoa/cursors/arrowN@2x.pngbin0 -> 655 bytes
-rw-r--r--widget/cocoa/cursors/arrowS.pngbin0 -> 256 bytes
-rw-r--r--widget/cocoa/cursors/arrowS@2x.pngbin0 -> 649 bytes
-rw-r--r--widget/cocoa/cursors/cell.pngbin0 -> 268 bytes
-rw-r--r--widget/cocoa/cursors/cell@2x.pngbin0 -> 647 bytes
-rw-r--r--widget/cocoa/cursors/colResize.pngbin0 -> 321 bytes
-rw-r--r--widget/cocoa/cursors/colResize@2x.pngbin0 -> 836 bytes
-rw-r--r--widget/cocoa/cursors/help.pngbin0 -> 723 bytes
-rw-r--r--widget/cocoa/cursors/help@2x.pngbin0 -> 1693 bytes
-rw-r--r--widget/cocoa/cursors/move.pngbin0 -> 284 bytes
-rw-r--r--widget/cocoa/cursors/move@2x.pngbin0 -> 621 bytes
-rw-r--r--widget/cocoa/cursors/rowResize.pngbin0 -> 331 bytes
-rw-r--r--widget/cocoa/cursors/rowResize@2x.pngbin0 -> 852 bytes
-rw-r--r--widget/cocoa/cursors/sizeNE.pngbin0 -> 279 bytes
-rw-r--r--widget/cocoa/cursors/sizeNE@2x.pngbin0 -> 794 bytes
-rw-r--r--widget/cocoa/cursors/sizeNESW.pngbin0 -> 300 bytes
-rw-r--r--widget/cocoa/cursors/sizeNESW@2x.pngbin0 -> 975 bytes
-rw-r--r--widget/cocoa/cursors/sizeNS.pngbin0 -> 282 bytes
-rw-r--r--widget/cocoa/cursors/sizeNS@2x.pngbin0 -> 660 bytes
-rw-r--r--widget/cocoa/cursors/sizeNW.pngbin0 -> 278 bytes
-rw-r--r--widget/cocoa/cursors/sizeNW@2x.pngbin0 -> 790 bytes
-rw-r--r--widget/cocoa/cursors/sizeNWSE.pngbin0 -> 295 bytes
-rw-r--r--widget/cocoa/cursors/sizeNWSE@2x.pngbin0 -> 976 bytes
-rw-r--r--widget/cocoa/cursors/sizeSE.pngbin0 -> 270 bytes
-rw-r--r--widget/cocoa/cursors/sizeSE@2x.pngbin0 -> 802 bytes
-rw-r--r--widget/cocoa/cursors/sizeSW.pngbin0 -> 271 bytes
-rw-r--r--widget/cocoa/cursors/sizeSW@2x.pngbin0 -> 806 bytes
-rw-r--r--widget/cocoa/cursors/vtIBeam.pngbin0 -> 120 bytes
-rw-r--r--widget/cocoa/cursors/vtIBeam@2x.pngbin0 -> 336 bytes
-rw-r--r--widget/cocoa/cursors/zoomIn.pngbin0 -> 655 bytes
-rw-r--r--widget/cocoa/cursors/zoomIn@2x.pngbin0 -> 1717 bytes
-rw-r--r--widget/cocoa/cursors/zoomOut.pngbin0 -> 650 bytes
-rw-r--r--widget/cocoa/cursors/zoomOut@2x.pngbin0 -> 1714 bytes
-rw-r--r--widget/cocoa/moz.build140
-rw-r--r--widget/cocoa/mozView.h62
-rw-r--r--widget/cocoa/nsAppShell.h71
-rw-r--r--widget/cocoa/nsAppShell.mm907
-rw-r--r--widget/cocoa/nsBidiKeyboard.h24
-rw-r--r--widget/cocoa/nsBidiKeyboard.mm42
-rw-r--r--widget/cocoa/nsChangeObserver.h44
-rw-r--r--widget/cocoa/nsChildView.h666
-rw-r--r--widget/cocoa/nsChildView.mm6151
-rw-r--r--widget/cocoa/nsClipboard.h55
-rw-r--r--widget/cocoa/nsClipboard.mm775
-rw-r--r--widget/cocoa/nsCocoaDebugUtils.h136
-rw-r--r--widget/cocoa/nsCocoaDebugUtils.mm284
-rw-r--r--widget/cocoa/nsCocoaFeatures.h46
-rw-r--r--widget/cocoa/nsCocoaFeatures.mm209
-rw-r--r--widget/cocoa/nsCocoaUtils.h389
-rw-r--r--widget/cocoa/nsCocoaUtils.mm1022
-rw-r--r--widget/cocoa/nsCocoaWindow.h426
-rw-r--r--widget/cocoa/nsCocoaWindow.mm3881
-rw-r--r--widget/cocoa/nsColorPicker.h50
-rw-r--r--widget/cocoa/nsColorPicker.mm188
-rw-r--r--widget/cocoa/nsCursorManager.h65
-rw-r--r--widget/cocoa/nsCursorManager.mm308
-rw-r--r--widget/cocoa/nsDeviceContextSpecX.h41
-rw-r--r--widget/cocoa/nsDeviceContextSpecX.mm165
-rw-r--r--widget/cocoa/nsDragService.h55
-rw-r--r--widget/cocoa/nsDragService.mm669
-rw-r--r--widget/cocoa/nsFilePicker.h74
-rw-r--r--widget/cocoa/nsFilePicker.mm676
-rw-r--r--widget/cocoa/nsIdleServiceX.h33
-rw-r--r--widget/cocoa/nsIdleServiceX.mm77
-rw-r--r--widget/cocoa/nsLookAndFeel.h46
-rw-r--r--widget/cocoa/nsLookAndFeel.mm584
-rw-r--r--widget/cocoa/nsMacCursor.h105
-rw-r--r--widget/cocoa/nsMacCursor.mm382
-rw-r--r--widget/cocoa/nsMacDockSupport.h41
-rw-r--r--widget/cocoa/nsMacDockSupport.mm174
-rw-r--r--widget/cocoa/nsMacWebAppUtils.h22
-rw-r--r--widget/cocoa/nsMacWebAppUtils.mm82
-rw-r--r--widget/cocoa/nsMenuBarX.h128
-rw-r--r--widget/cocoa/nsMenuBarX.mm979
-rw-r--r--widget/cocoa/nsMenuBaseX.h79
-rw-r--r--widget/cocoa/nsMenuGroupOwnerX.h61
-rw-r--r--widget/cocoa/nsMenuGroupOwnerX.mm261
-rw-r--r--widget/cocoa/nsMenuItemIconX.h66
-rw-r--r--widget/cocoa/nsMenuItemIconX.mm466
-rw-r--r--widget/cocoa/nsMenuItemX.h75
-rw-r--r--widget/cocoa/nsMenuItemX.mm369
-rw-r--r--widget/cocoa/nsMenuUtilsX.h31
-rw-r--r--widget/cocoa/nsMenuUtilsX.mm223
-rw-r--r--widget/cocoa/nsMenuX.h101
-rw-r--r--widget/cocoa/nsMenuX.mm1051
-rw-r--r--widget/cocoa/nsNativeThemeCocoa.h178
-rw-r--r--widget/cocoa/nsNativeThemeCocoa.mm3961
-rw-r--r--widget/cocoa/nsNativeThemeColors.h57
-rw-r--r--widget/cocoa/nsPIWidgetCocoa.idl37
-rw-r--r--widget/cocoa/nsPrintDialogX.h68
-rw-r--r--widget/cocoa/nsPrintDialogX.mm682
-rw-r--r--widget/cocoa/nsPrintOptionsX.h44
-rw-r--r--widget/cocoa/nsPrintOptionsX.mm349
-rw-r--r--widget/cocoa/nsPrintSettingsX.h82
-rw-r--r--widget/cocoa/nsPrintSettingsX.mm272
-rw-r--r--widget/cocoa/nsSandboxViolationSink.h36
-rw-r--r--widget/cocoa/nsSandboxViolationSink.mm115
-rw-r--r--widget/cocoa/nsScreenCocoa.h41
-rw-r--r--widget/cocoa/nsScreenCocoa.mm147
-rw-r--r--widget/cocoa/nsScreenManagerCocoa.h33
-rw-r--r--widget/cocoa/nsScreenManagerCocoa.mm152
-rw-r--r--widget/cocoa/nsSound.h27
-rw-r--r--widget/cocoa/nsSound.mm108
-rw-r--r--widget/cocoa/nsStandaloneNativeMenu.h40
-rw-r--r--widget/cocoa/nsStandaloneNativeMenu.mm213
-rw-r--r--widget/cocoa/nsSystemStatusBarCocoa.h40
-rw-r--r--widget/cocoa/nsSystemStatusBarCocoa.mm74
-rw-r--r--widget/cocoa/nsToolkit.h53
-rw-r--r--widget/cocoa/nsToolkit.mm326
-rw-r--r--widget/cocoa/nsWidgetFactory.mm219
-rw-r--r--widget/cocoa/nsWindowMap.h62
-rw-r--r--widget/cocoa/nsWindowMap.mm311
-rw-r--r--widget/cocoa/resources/MainMenu.nib/classes.nib4
-rw-r--r--widget/cocoa/resources/MainMenu.nib/info.nib21
-rw-r--r--widget/cocoa/resources/MainMenu.nib/keyedobjects.nibbin0 -> 1877 bytes
-rw-r--r--widget/moz.build22
-rw-r--r--widget/nsBaseWidget.cpp23
-rw-r--r--widget/nsBaseWidget.h2
-rw-r--r--widget/nsGUIEventIPC.h14
-rw-r--r--widget/nsIMacDockSupport.idl39
-rw-r--r--widget/nsIMacWebAppUtils.idl35
-rw-r--r--widget/nsISystemStatusBar.idl36
-rw-r--r--widget/nsIWidget.h4
-rw-r--r--widget/nsNativeTheme.cpp14
-rw-r--r--widget/uikit/GfxInfo.cpp222
-rw-r--r--widget/uikit/GfxInfo.h78
-rw-r--r--widget/uikit/moz.build18
-rw-r--r--widget/uikit/nsAppShell.h57
-rw-r--r--widget/uikit/nsAppShell.mm271
-rw-r--r--widget/uikit/nsLookAndFeel.h35
-rw-r--r--widget/uikit/nsLookAndFeel.mm401
-rw-r--r--widget/uikit/nsScreenManager.h60
-rw-r--r--widget/uikit/nsScreenManager.mm146
-rw-r--r--widget/uikit/nsWidgetFactory.mm71
-rw-r--r--widget/uikit/nsWindow.h125
-rw-r--r--widget/uikit/nsWindow.mm862
-rw-r--r--xpcom/base/MacHelpers.h17
-rw-r--r--xpcom/base/MacHelpers.mm40
-rw-r--r--xpcom/base/moz.build17
-rw-r--r--xpcom/base/nsDebugImpl.cpp16
-rw-r--r--xpcom/base/nsIMacUtils.idl32
-rw-r--r--xpcom/base/nsMacUtilsImpl.cpp146
-rw-r--r--xpcom/base/nsMacUtilsImpl.h41
-rw-r--r--xpcom/base/nsMemoryReporterManager.cpp166
-rw-r--r--xpcom/base/nsSystemInfo.cpp77
-rw-r--r--xpcom/base/nsUUIDGenerator.cpp18
-rw-r--r--xpcom/base/nsUUIDGenerator.h2
-rw-r--r--xpcom/build/BinaryPath.h42
-rw-r--r--xpcom/build/PoisonIOInterposer.h19
-rw-r--r--xpcom/build/PoisonIOInterposerMac.cpp383
-rw-r--r--xpcom/build/XPCOMInit.cpp3
-rw-r--r--xpcom/build/mach_override.c789
-rw-r--r--xpcom/build/mach_override.h121
-rw-r--r--xpcom/build/moz.build10
-rw-r--r--xpcom/build/nsXPCOMPrivate.h7
-rw-r--r--xpcom/build/nsXULAppAPI.h2
-rw-r--r--xpcom/components/nsNativeModuleLoader.cpp4
-rw-r--r--xpcom/ds/nsMathUtils.h4
-rw-r--r--xpcom/glue/FileUtils.cpp153
-rw-r--r--xpcom/glue/nsCRTGlue.h4
-rw-r--r--xpcom/glue/nsThreadUtils.cpp12
-rw-r--r--xpcom/glue/nsThreadUtils.h3
-rw-r--r--xpcom/glue/standalone/nsXPCOMGlue.cpp38
-rw-r--r--xpcom/io/CocoaFileUtils.h34
-rw-r--r--xpcom/io/CocoaFileUtils.mm267
-rw-r--r--xpcom/io/moz.build10
-rw-r--r--xpcom/io/nsILocalFileMac.idl179
-rw-r--r--xpcom/io/nsLocalFileUnix.h4
-rw-r--r--xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S15
-rw-r--r--xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp4
-rw-r--r--xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp4
-rw-r--r--xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp13
-rw-r--r--xpcom/threads/SharedThreadPool.h2
-rw-r--r--xpcom/threads/nsProcess.h4
-rw-r--r--xpcom/threads/nsProcessCommon.cpp71
-rw-r--r--xpcom/threads/nsThread.cpp15
-rw-r--r--xpfe/appshell/nsAppShellService.cpp43
-rw-r--r--xpfe/appshell/nsContentTreeOwner.cpp37
-rw-r--r--xpfe/appshell/nsWebShellWindow.cpp2
-rw-r--r--xpfe/appshell/nsXULWindow.cpp3
954 files changed, 95666 insertions, 432 deletions
diff --git a/README.md b/README.md
index e24a7476ce..4f849359de 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,6 @@ selective cherry-picking of directly-applicable patches, this repository has its
own development and holds the base for a maintained platform to be used by XUL
applications.
-For a list of active projects making use of the Unified XUL Platform, checkout http://thereisonlyxul.org/.
-
## Additional documentation
Additional documentation relevant to this source code can be found in the `/docs`
@@ -23,7 +21,7 @@ You are also always welcome to get in touch with our community on the [Pale Moon
### A note about trademarks and branding
-Although this repository is licensed under Mozilla Public License v2.0, the
+Although this repository is primarily licensed under Mozilla Public License v2.0, the
trademarks and brands contained herein remain the property of their respective
owners. For more details, please see the notifications in the respective directories.
diff --git a/SECURITY.md b/SECURITY.md
index 8ef8316746..8da448d59c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -15,14 +15,14 @@ What should be considered vulnerabilities or security hazards by default:
Generally not security vulnerabilities:
- Null dereferencing crashes;
-- Malware extensions (but please do report those on the forum in the extensions board!);
+- Malware extensions (but please do report those on the forum in the add-ons board!);
- Denial-of-service (AKA "evil trap sites")
- Browser hangs
- Issues with non-standard manual configuration (either at build time or by manipulating about:config directly)
## Reporting a Vulnerability
-If you find an issue in UXP or the applications it builds on that could impact the security or safety of users please **do not**
+If you find an issue in UXP, or the applications building on it, that could impact the security or safety of users please **do not**
make an issue on Gitea about it. Gitea does not support restricted viewability for security sensitive bugs.
If you want to report a security-sensitive issue then please go to the [forum](https://forum.palemoon.org) and report the issue
@@ -33,4 +33,3 @@ except the recipient (not even moderators!).
You will be informed via private message if the vulnerability report is accepted or declined, with reasoning.
Security updates occur regularly and are given priority over most other development tasks. In general, they can be solved
relatively quickly and will be included in the next point release (third digit if not rolled into a more major one).
-
diff --git a/accessible/base/Platform.h b/accessible/base/Platform.h
index 264aa438f6..64f7ff7ee2 100644
--- a/accessible/base/Platform.h
+++ b/accessible/base/Platform.h
@@ -35,10 +35,10 @@ EPlatformDisabledState PlatformDisabledState();
void PreInit();
#endif
-#if defined(MOZ_ACCESSIBILITY_ATK)
+#if defined(MOZ_ACCESSIBILITY_ATK) || defined(XP_MACOSX)
/**
* Is platform accessibility enabled.
- * Only used on linux with atk.
+ * Only used on linux with atk and MacOS for now.
*/
bool ShouldA11yBeEnabled();
#endif
diff --git a/accessible/base/moz.build b/accessible/base/moz.build
index 54627ca50c..ea9b67aee9 100644
--- a/accessible/base/moz.build
+++ b/accessible/base/moz.build
@@ -96,6 +96,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'/accessible/windows/ia2',
'/accessible/windows/msaa',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
else:
LOCAL_INCLUDES += [
'/accessible/other',
diff --git a/accessible/generic/moz.build b/accessible/generic/moz.build
index 720d9bf01b..6855daf909 100644
--- a/accessible/generic/moz.build
+++ b/accessible/generic/moz.build
@@ -52,6 +52,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'/accessible/windows/ia2',
'/accessible/windows/msaa',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
else:
LOCAL_INCLUDES += [
'/accessible/other',
diff --git a/accessible/html/moz.build b/accessible/html/moz.build
index a18c4e59b1..e486f10456 100644
--- a/accessible/html/moz.build
+++ b/accessible/html/moz.build
@@ -32,6 +32,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'/accessible/windows/ia2',
'/accessible/windows/msaa',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
else:
LOCAL_INCLUDES += [
'/accessible/other',
diff --git a/accessible/ipc/moz.build b/accessible/ipc/moz.build
index 91fd1fa4d3..cb852de271 100644
--- a/accessible/ipc/moz.build
+++ b/accessible/ipc/moz.build
@@ -19,6 +19,10 @@ else:
LOCAL_INCLUDES += [
'/accessible/atk',
]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
else:
LOCAL_INCLUDES += [
'/accessible/other',
diff --git a/accessible/ipc/other/moz.build b/accessible/ipc/other/moz.build
index 489520cef6..50f96de040 100644
--- a/accessible/ipc/other/moz.build
+++ b/accessible/ipc/other/moz.build
@@ -28,6 +28,10 @@ if CONFIG['ACCESSIBILITY']:
LOCAL_INCLUDES += [
'/accessible/atk',
]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
else:
LOCAL_INCLUDES += [
'/accessible/other',
diff --git a/accessible/mac/ARIAGridAccessibleWrap.h b/accessible/mac/ARIAGridAccessibleWrap.h
new file mode 100644
index 0000000000..5d397e915c
--- /dev/null
+++ b/accessible/mac/ARIAGridAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#ifndef MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+
+#include "ARIAGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ARIAGridAccessible ARIAGridAccessibleWrap;
+typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/AccessibleWrap.h b/accessible/mac/AccessibleWrap.h
new file mode 100644
index 0000000000..6c746ff0dc
--- /dev/null
+++ b/accessible/mac/AccessibleWrap.h
@@ -0,0 +1,103 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+/* For documentation of the accessibility architecture,
+ * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
+ */
+
+#ifndef _AccessibleWrap_H_
+#define _AccessibleWrap_H_
+
+#include <objc/objc.h>
+
+#include "Accessible.h"
+#include "States.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsTArray.h"
+
+#if defined(__OBJC__)
+@class mozAccessible;
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap : public Accessible
+{
+public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~AccessibleWrap();
+
+ /**
+ * Get the native Obj-C object (mozAccessible).
+ */
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+
+ /**
+ * The objective-c |Class| type that this accessible's native object
+ * should be instantied with. used on runtime to determine the
+ * right type for this accessible's associated native object.
+ */
+ virtual Class GetNativeType ();
+
+ virtual void Shutdown () override;
+
+ virtual bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override;
+ virtual bool RemoveChild(Accessible* aAccessible) override;
+
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+
+protected:
+
+ /**
+ * Return true if the parent doesn't have children to expose to AT.
+ */
+ bool AncestorIsFlat();
+
+ /**
+ * Get the native object. Create it if needed.
+ */
+#if defined(__OBJC__)
+ mozAccessible* GetNativeObject();
+#else
+ id GetNativeObject();
+#endif
+
+private:
+
+ /**
+ * Our native object. Private because its creation is done lazily.
+ * Don't access it directly. Ever. Unless you are GetNativeObject() or
+ * Shutdown()
+ */
+#if defined(__OBJC__)
+ // if we are in Objective-C, we use the actual Obj-C class.
+ mozAccessible* mNativeObject;
+#else
+ id mNativeObject;
+#endif
+
+ /**
+ * We have created our native. This does not mean there is one.
+ * This can never go back to false.
+ * We need it because checking whether we need a native object cost time.
+ */
+ bool mNativeInited;
+};
+
+#if defined(__OBJC__)
+ void FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType);
+#else
+ void FireNativeEvent(id aNativeAcc, uint32_t aEventType);
+#endif
+
+Class GetTypeFromRole(roles::Role aRole);
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm
new file mode 100644
index 0000000000..65f2e1db42
--- /dev/null
+++ b/accessible/mac/AccessibleWrap.mm
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 2; 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 "DocAccessible.h"
+#include "nsObjCExceptions.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+
+#import "mozAccessible.h"
+#import "mozActionElements.h"
+#import "mozHTMLAccessible.h"
+#import "mozTableAccessible.h"
+#import "mozTextAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+AccessibleWrap::
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) :
+ Accessible(aContent, aDoc), mNativeObject(nil),
+ mNativeInited(false)
+{
+}
+
+AccessibleWrap::~AccessibleWrap()
+{
+}
+
+mozAccessible*
+AccessibleWrap::GetNativeObject()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mNativeInited && !mNativeObject && !IsDefunct() && !AncestorIsFlat()) {
+ uintptr_t accWrap = reinterpret_cast<uintptr_t>(this);
+ mNativeObject = [[GetNativeType() alloc] initWithAccessible:accWrap];
+ }
+
+ mNativeInited = true;
+
+ return mNativeObject;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void
+AccessibleWrap::GetNativeInterface(void** aOutInterface)
+{
+ *aOutInterface = static_cast<void*>(GetNativeObject());
+}
+
+// overridden in subclasses to create the right kind of object. by default we create a generic
+// 'mozAccessible' node.
+Class
+AccessibleWrap::GetNativeType ()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (IsXULTabpanels())
+ return [mozPaneAccessible class];
+
+ if (IsTable())
+ return [mozTableAccessible class];
+
+ if (IsTableRow())
+ return [mozTableRowAccessible class];
+
+ if (IsTableCell())
+ return [mozTableCellAccessible class];
+
+ return GetTypeFromRole(Role());
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// this method is very important. it is fired when an accessible object "dies". after this point
+// the object might still be around (because some 3rd party still has a ref to it), but it is
+// in fact 'dead'.
+void
+AccessibleWrap::Shutdown ()
+{
+ // this ensure we will not try to re-create the native object.
+ mNativeInited = true;
+
+ // we really intend to access the member directly.
+ if (mNativeObject) {
+ [mNativeObject expire];
+ [mNativeObject release];
+ mNativeObject = nil;
+ }
+
+ Accessible::Shutdown();
+}
+
+nsresult
+AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv = Accessible::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (IPCAccessibilityActive()) {
+ return NS_OK;
+ }
+
+ uint32_t eventType = aEvent->GetEventType();
+
+ // ignore everything but focus-changed, value-changed, caret, selection
+ // and document load complete events for now.
+ if (eventType != nsIAccessibleEvent::EVENT_FOCUS &&
+ eventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
+ eventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
+ eventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
+ eventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED &&
+ eventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE)
+ return NS_OK;
+
+ Accessible* accessible = aEvent->GetAccessible();
+ NS_ENSURE_STATE(accessible);
+
+ mozAccessible *nativeAcc = nil;
+ accessible->GetNativeInterface((void**)&nativeAcc);
+ if (!nativeAcc)
+ return NS_ERROR_FAILURE;
+
+ FireNativeEvent(nativeAcc, eventType);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+AccessibleWrap::InsertChildAt(uint32_t aIdx, Accessible* aAccessible)
+{
+ bool inserted = Accessible::InsertChildAt(aIdx, aAccessible);
+ if (inserted && mNativeObject)
+ [mNativeObject appendChild:aAccessible];
+
+ return inserted;
+}
+
+bool
+AccessibleWrap::RemoveChild(Accessible* aAccessible)
+{
+ bool removed = Accessible::RemoveChild(aAccessible);
+
+ if (removed && mNativeObject)
+ [mNativeObject invalidateChildren];
+
+ return removed;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccessibleWrap protected
+
+bool
+AccessibleWrap::AncestorIsFlat()
+{
+ // We don't create a native object if we're child of a "flat" accessible;
+ // for example, on OS X buttons shouldn't have any children, because that
+ // makes the OS confused.
+ //
+ // To maintain a scripting environment where the XPCOM accessible hierarchy
+ // look the same on all platforms, we still let the C++ objects be created
+ // though.
+
+ Accessible* parent = Parent();
+ while (parent) {
+ if (nsAccUtils::MustPrune(parent))
+ return true;
+
+ parent = parent->Parent();
+ }
+ // no parent was flat
+ return false;
+}
+
+void
+a11y::FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ switch (aEventType) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ [aNativeAcc didReceiveFocus];
+ break;
+ case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
+ case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
+ [aNativeAcc valueDidChange];
+ break;
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED:
+ case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
+ [aNativeAcc selectedTextDidChange];
+ break;
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
+ [aNativeAcc documentLoadComplete];
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+Class
+a11y::GetTypeFromRole(roles::Role aRole)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ switch (aRole) {
+ case roles::COMBOBOX:
+ case roles::PUSHBUTTON:
+ case roles::SPLITBUTTON:
+ case roles::TOGGLE_BUTTON:
+ {
+ return [mozButtonAccessible class];
+ }
+
+ case roles::PAGETAB:
+ return [mozButtonAccessible class];
+
+ case roles::CHECKBUTTON:
+ return [mozCheckboxAccessible class];
+
+ case roles::HEADING:
+ return [mozHeadingAccessible class];
+
+ case roles::PAGETABLIST:
+ return [mozTabsAccessible class];
+
+ case roles::ENTRY:
+ case roles::STATICTEXT:
+ case roles::CAPTION:
+ case roles::ACCEL_LABEL:
+ case roles::PASSWORD_TEXT:
+ // normal textfield (static or editable)
+ return [mozTextAccessible class];
+
+ case roles::TEXT_LEAF:
+ return [mozTextLeafAccessible class];
+
+ case roles::LINK:
+ return [mozLinkAccessible class];
+
+ default:
+ return [mozAccessible class];
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
diff --git a/accessible/mac/ApplicationAccessibleWrap.h b/accessible/mac/ApplicationAccessibleWrap.h
new file mode 100644
index 0000000000..9343c29ddc
--- /dev/null
+++ b/accessible/mac/ApplicationAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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/. */
+
+#ifndef mozilla_a11y_ApplicationAccessibleWrap_h__
+#define mozilla_a11y_ApplicationAccessibleWrap_h__
+
+#include "ApplicationAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef ApplicationAccessible ApplicationAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/DocAccessibleWrap.h b/accessible/mac/DocAccessibleWrap.h
new file mode 100644
index 0000000000..3e80a0d33c
--- /dev/null
+++ b/accessible/mac/DocAccessibleWrap.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_a11y_DocAccessibleWrap_h__
+#define mozilla_a11y_DocAccessibleWrap_h__
+
+#include "DocAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class DocAccessibleWrap : public DocAccessible
+{
+public:
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~DocAccessibleWrap();
+
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/DocAccessibleWrap.mm b/accessible/mac/DocAccessibleWrap.mm
new file mode 100644
index 0000000000..8a513f485a
--- /dev/null
+++ b/accessible/mac/DocAccessibleWrap.mm
@@ -0,0 +1,21 @@
+/* -*- Mode: C; tab-width: 2; 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 "DocAccessibleWrap.h"
+
+#import "mozAccessible.h"
+
+using namespace mozilla::a11y;
+
+DocAccessibleWrap::
+ DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ DocAccessible(aDocument, aPresShell)
+{
+}
+
+DocAccessibleWrap::~DocAccessibleWrap()
+{
+}
+
diff --git a/accessible/mac/HTMLTableAccessibleWrap.h b/accessible/mac/HTMLTableAccessibleWrap.h
new file mode 100644
index 0000000000..4f158e241d
--- /dev/null
+++ b/accessible/mac/HTMLTableAccessibleWrap.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#ifndef mozilla_a11y_HTMLTableAccessibleWrap_h__
+#define mozilla_a11y_HTMLTableAccessibleWrap_h__
+
+#include "HTMLTableAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HTMLTableAccessible HTMLTableAccessibleWrap;
+typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap;
+typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/HyperTextAccessibleWrap.h b/accessible/mac/HyperTextAccessibleWrap.h
new file mode 100644
index 0000000000..fb335ef0f7
--- /dev/null
+++ b/accessible/mac/HyperTextAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_a11y_HyperTextAccessibleWrap_h__
+#define mozilla_a11y_HyperTextAccessibleWrap_h__
+
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HyperTextAccessible HyperTextAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/ImageAccessibleWrap.h b/accessible/mac/ImageAccessibleWrap.h
new file mode 100644
index 0000000000..069efb6511
--- /dev/null
+++ b/accessible/mac/ImageAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#ifndef mozilla_a11y_ImageAccessibleWrap_h__
+#define mozilla_a11y_ImageAccessibleWrap_h__
+
+#include "ImageAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ImageAccessible ImageAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
+
diff --git a/accessible/mac/MacUtils.h b/accessible/mac/MacUtils.h
new file mode 100644
index 0000000000..f88a27ee58
--- /dev/null
+++ b/accessible/mac/MacUtils.h
@@ -0,0 +1,26 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+#ifndef _MacUtils_H_
+#define _MacUtils_H_
+
+@class NSString;
+class nsString;
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+/**
+ * Get a localized string from the string bundle.
+ * Return nil if not found.
+ */
+NSString* LocalizedString(const nsString& aString);
+
+}
+}
+}
+
+#endif
diff --git a/accessible/mac/MacUtils.mm b/accessible/mac/MacUtils.mm
new file mode 100644
index 0000000000..2ce03fe966
--- /dev/null
+++ b/accessible/mac/MacUtils.mm
@@ -0,0 +1,32 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+#import "MacUtils.h"
+
+#include "Accessible.h"
+
+#include "nsCocoaUtils.h"
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+/**
+ * Get a localized string from the a11y string bundle.
+ * Return nil if not found.
+ */
+NSString*
+LocalizedString(const nsString& aString)
+{
+ nsString text;
+
+ Accessible::TranslateString(aString, text);
+
+ return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
+}
+
+}
+}
+}
diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm
new file mode 100644
index 0000000000..a104bf904c
--- /dev/null
+++ b/accessible/mac/Platform.mm
@@ -0,0 +1,174 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "Platform.h"
+#include "ProxyAccessible.h"
+#include "DocAccessibleParent.h"
+#include "mozTableAccessible.h"
+
+#include "nsAppShell.h"
+
+namespace mozilla {
+namespace a11y {
+
+// Mac a11y whitelisting
+static bool sA11yShouldBeEnabled = false;
+
+bool
+ShouldA11yBeEnabled()
+{
+ EPlatformDisabledState disabledState = PlatformDisabledState();
+ return (disabledState == ePlatformIsForceEnabled) || ((disabledState == ePlatformIsEnabled) && sA11yShouldBeEnabled);
+}
+
+void
+PlatformInit()
+{
+}
+
+void
+PlatformShutdown()
+{
+}
+
+void
+ProxyCreated(ProxyAccessible* aProxy, uint32_t)
+{
+ // Pass in dummy state for now as retrieving proxy state requires IPC.
+ // Note that we can use ProxyAccessible::IsTable* functions here because they
+ // do not use IPC calls but that might change after bug 1210477.
+ Class type;
+ if (aProxy->IsTable())
+ type = [mozTableAccessible class];
+ else if (aProxy->IsTableRow())
+ type = [mozTableRowAccessible class];
+ else if (aProxy->IsTableCell())
+ type = [mozTableCellAccessible class];
+ else
+ type = GetTypeFromRole(aProxy->Role());
+
+ uintptr_t accWrap = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY;
+ mozAccessible* mozWrapper = [[type alloc] initWithAccessible:accWrap];
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
+
+ mozAccessible* nativeParent = nullptr;
+ if (aProxy->IsDoc() && aProxy->AsDoc()->IsTopLevel()) {
+ // If proxy is top level, the parent we need to invalidate the children of
+ // will be a non-remote accessible.
+ Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
+ if (outerDoc) {
+ nativeParent = GetNativeFromGeckoAccessible(outerDoc);
+ }
+ } else {
+ // Non-top level proxies need proxy parents' children invalidated.
+ ProxyAccessible* parent = aProxy->Parent();
+ nativeParent = GetNativeFromProxy(parent);
+ NS_ASSERTION(parent, "a non-top-level proxy is missing a parent?");
+ }
+
+ if (nativeParent) {
+ [nativeParent invalidateChildren];
+ }
+}
+
+void
+ProxyDestroyed(ProxyAccessible* aProxy)
+{
+ mozAccessible* nativeParent = nil;
+ if (aProxy->IsDoc() && aProxy->AsDoc()->IsTopLevel()) {
+ // Invalidate native parent in parent process's children on proxy destruction
+ Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser();
+ if (outerDoc) {
+ nativeParent = GetNativeFromGeckoAccessible(outerDoc);
+ }
+ } else {
+ if (!aProxy->Document()->IsShutdown()) {
+ // Only do if the document has not been shut down, else parent will return
+ // garbage since we don't shut down children from top down.
+ ProxyAccessible* parent = aProxy->Parent();
+ // Invalidate proxy parent's children.
+ if (parent) {
+ nativeParent = GetNativeFromProxy(parent);
+ }
+ }
+ }
+
+ mozAccessible* wrapper = GetNativeFromProxy(aProxy);
+ [wrapper expire];
+ [wrapper release];
+ aProxy->SetWrapper(0);
+
+ if (nativeParent) {
+ [nativeParent invalidateChildren];
+ }
+}
+
+void
+ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType)
+{
+ // ignore everything but focus-changed, value-changed, caret and selection
+ // events for now.
+ if (aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
+ aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED)
+ return;
+
+ mozAccessible* wrapper = GetNativeFromProxy(aProxy);
+ if (wrapper)
+ FireNativeEvent(wrapper, aEventType);
+}
+
+void
+ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t, bool)
+{
+ // mac doesn't care about state change events
+}
+
+void
+ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
+{
+ mozAccessible* wrapper = GetNativeFromProxy(aTarget);
+ if (wrapper)
+ [wrapper selectedTextDidChange];
+}
+
+void
+ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t,
+ bool, bool)
+{
+}
+
+void
+ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
+{
+}
+
+void
+ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
+{
+}
+} // namespace a11y
+} // namespace mozilla
+
+@interface GeckoNSApplication(a11y)
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+@end
+
+@implementation GeckoNSApplication(a11y)
+
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute
+{
+ if ([attribute isEqualToString:@"AXEnhancedUserInterface"])
+ mozilla::a11y::sA11yShouldBeEnabled = ([value intValue] == 1);
+
+ return [super accessibilitySetValue:value forAttribute:attribute];
+}
+
+@end
+
diff --git a/accessible/mac/RootAccessibleWrap.h b/accessible/mac/RootAccessibleWrap.h
new file mode 100644
index 0000000000..aa53e06ac0
--- /dev/null
+++ b/accessible/mac/RootAccessibleWrap.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/* For documentation of the accessibility architecture,
+ * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
+ */
+
+#ifndef mozilla_a11y_RootAccessibleWrap_h__
+#define mozilla_a11y_RootAccessibleWrap_h__
+
+#include "RootAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class RootAccessibleWrap : public RootAccessible
+{
+public:
+ RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
+ virtual ~RootAccessibleWrap();
+
+ Class GetNativeType ();
+
+ // let's our native accessible get in touch with the
+ // native cocoa view that is our accessible parent.
+ void GetNativeWidget (void **aOutView);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/RootAccessibleWrap.mm b/accessible/mac/RootAccessibleWrap.mm
new file mode 100644
index 0000000000..037545cce2
--- /dev/null
+++ b/accessible/mac/RootAccessibleWrap.mm
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; 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 "RootAccessibleWrap.h"
+
+#include "mozDocAccessible.h"
+
+#include "nsCOMPtr.h"
+#include "nsObjCExceptions.h"
+#include "nsIFrame.h"
+#include "nsView.h"
+#include "nsIWidget.h"
+
+using namespace mozilla::a11y;
+
+RootAccessibleWrap::
+ RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
+ RootAccessible(aDocument, aPresShell)
+{
+}
+
+RootAccessibleWrap::~RootAccessibleWrap()
+{
+}
+
+Class
+RootAccessibleWrap::GetNativeType()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mozRootAccessible class];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void
+RootAccessibleWrap::GetNativeWidget(void** aOutView)
+{
+ nsIFrame *frame = GetFrame();
+ if (frame) {
+ nsView *view = frame->GetView();
+ if (view) {
+ nsIWidget *widget = view->GetWidget();
+ if (widget) {
+ *aOutView = (void**)widget->GetNativeData (NS_NATIVE_WIDGET);
+ NS_ASSERTION (*aOutView,
+ "Couldn't get the native NSView parent we need to connect the accessibility hierarchy!");
+ }
+ }
+ }
+}
diff --git a/accessible/mac/TextLeafAccessibleWrap.h b/accessible/mac/TextLeafAccessibleWrap.h
new file mode 100644
index 0000000000..d07b9defec
--- /dev/null
+++ b/accessible/mac/TextLeafAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_a11y_TextLeafAccessibleWrap_h__
+#define mozilla_a11y_TextLeafAccessibleWrap_h__
+
+#include "TextLeafAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class TextLeafAccessible TextLeafAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULListboxAccessibleWrap.h b/accessible/mac/XULListboxAccessibleWrap.h
new file mode 100644
index 0000000000..f7dc6cc547
--- /dev/null
+++ b/accessible/mac/XULListboxAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_a11y_XULListboxAccessibleWrap_h__
+#define mozilla_a11y_XULListboxAccessibleWrap_h__
+
+#include "XULListboxAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULListboxAccessible XULListboxAccessibleWrap;
+typedef class XULListCellAccessible XULListCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULMenuAccessibleWrap.h b/accessible/mac/XULMenuAccessibleWrap.h
new file mode 100644
index 0000000000..6efcf007eb
--- /dev/null
+++ b/accessible/mac/XULMenuAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_a11y_XULMenuAccessibleWrap_h__
+#define mozilla_a11y_XULMenuAccessibleWrap_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULTreeGridAccessibleWrap.h b/accessible/mac/XULTreeGridAccessibleWrap.h
new file mode 100644
index 0000000000..b3631e9adb
--- /dev/null
+++ b/accessible/mac/XULTreeGridAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_a11y_XULTreeGridAccessibleWrap_h__
+#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
+
+#include "XULTreeGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap;
+typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/moz.build b/accessible/mac/moz.build
new file mode 100644
index 0000000000..8d2e7b391f
--- /dev/null
+++ b/accessible/mac/moz.build
@@ -0,0 +1,44 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+EXPORTS += [
+ 'mozAccessibleProtocol.h',
+]
+
+EXPORTS.mozilla.a11y += [
+ 'AccessibleWrap.h',
+ 'HyperTextAccessibleWrap.h',
+]
+
+SOURCES += [
+ 'AccessibleWrap.mm',
+ 'DocAccessibleWrap.mm',
+ 'MacUtils.mm',
+ 'mozAccessible.mm',
+ 'mozActionElements.mm',
+ 'mozDocAccessible.mm',
+ 'mozHTMLAccessible.mm',
+ 'mozTableAccessible.mm',
+ 'mozTextAccessible.mm',
+ 'Platform.mm',
+ 'RootAccessibleWrap.mm',
+]
+
+LOCAL_INCLUDES += [
+ '/accessible/base',
+ '/accessible/generic',
+ '/accessible/html',
+ '/accessible/ipc',
+ '/accessible/ipc/other',
+ '/accessible/xul',
+ '/layout/generic',
+ '/layout/xul',
+ '/widget',
+ '/widget/cocoa',
+]
+
+FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
diff --git a/accessible/mac/mozAccessible.h b/accessible/mac/mozAccessible.h
new file mode 100644
index 0000000000..6d7db3fe98
--- /dev/null
+++ b/accessible/mac/mozAccessible.h
@@ -0,0 +1,181 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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 "AccessibleWrap.h"
+#include "ProxyAccessible.h"
+
+#import <Cocoa/Cocoa.h>
+
+#import "mozAccessibleProtocol.h"
+
+@class mozRootAccessible;
+
+/**
+ * All mozAccessibles are either abstract objects (that correspond to XUL
+ * widgets, HTML frames, etc) or are attached to a certain view; for example
+ * a document view. When we hand an object off to an AT, we always want
+ * to give it the represented view, in the latter case.
+ */
+
+namespace mozilla {
+namespace a11y {
+
+inline id <mozAccessible>
+GetObjectOrRepresentedView(id <mozAccessible> aObject)
+{
+ return [aObject hasRepresentedView] ? [aObject representedView] : aObject;
+}
+
+inline mozAccessible*
+GetNativeFromGeckoAccessible(Accessible* aAccessible)
+{
+ mozAccessible* native = nil;
+ aAccessible->GetNativeInterface((void**)&native);
+ return native;
+}
+
+inline mozAccessible*
+GetNativeFromProxy(const ProxyAccessible* aProxy)
+{
+ return reinterpret_cast<mozAccessible*>(aProxy->GetWrapper());
+}
+
+} // a11y
+} // mozilla
+
+// This is OR'd with the Accessible owner to indicate the wrap-ee is a proxy.
+static const uintptr_t IS_PROXY = 1;
+
+@interface mozAccessible : NSObject <mozAccessible>
+{
+ /**
+ * Weak reference; it owns us.
+ */
+ uintptr_t mGeckoAccessible;
+
+ /**
+ * Strong ref to array of children
+ */
+ NSMutableArray* mChildren;
+
+ /**
+ * Weak reference to the parent
+ */
+ mozAccessible* mParent;
+
+ /**
+ * The role of our gecko accessible.
+ */
+ mozilla::a11y::role mRole;
+}
+
+// return the Accessible for this mozAccessible if it exists.
+- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible;
+
+// return the ProxyAccessible for this mozAccessible if it exists.
+- (mozilla::a11y::ProxyAccessible*)getProxyAccessible;
+
+// inits with the gecko owner.
+- (id)initWithAccessible:(uintptr_t)aGeckoObj;
+
+// our accessible parent (AXParent)
+- (id <mozAccessible>)parent;
+
+// a lazy cache of our accessible children (AXChildren). updated
+- (NSArray*)children;
+
+// returns the size of this accessible.
+- (NSValue*)size;
+
+// returns the position, in cocoa coordinates.
+- (NSValue*)position;
+
+// can be overridden to report another role name.
+- (NSString*)role;
+
+// a subrole is a more specialized variant of the role. for example,
+// the role might be "textfield", while the subrole is "password textfield".
+- (NSString*)subrole;
+
+// Return the role description, as there are a few exceptions.
+- (NSString*)roleDescription;
+
+// returns the native window we're inside.
+- (NSWindow*)window;
+
+// the value of this element.
+- (id)value;
+
+// name that is associated with this accessible (for buttons, etc)
+- (NSString*)title;
+
+// the accessible description (help text) of this particular instance.
+- (NSString*)help;
+
+- (BOOL)isEnabled;
+
+// information about focus.
+- (BOOL)isFocused;
+- (BOOL)canBeFocused;
+
+// returns NO if for some reason we were unable to focus the element.
+- (BOOL)focus;
+
+// notifications sent out to listening accessible providers.
+- (void)didReceiveFocus;
+- (void)valueDidChange;
+- (void)selectedTextDidChange;
+- (void)documentLoadComplete;
+
+// internal method to retrieve a child at a given index.
+- (id)childAt:(uint32_t)i;
+
+#pragma mark -
+
+// invalidates and removes all our children from our cached array.
+- (void)invalidateChildren;
+
+/**
+ * Append a child if they are already cached.
+ */
+- (void)appendChild:(mozilla::a11y::Accessible*)aAccessible;
+
+// makes ourselves "expired". after this point, we might be around if someone
+// has retained us (e.g., a third-party), but we really contain no information.
+- (void)expire;
+- (BOOL)isExpired;
+
+#ifdef DEBUG
+- (void)printHierarchy;
+- (void)printHierarchyWithLevel:(unsigned)numSpaces;
+
+- (void)sanityCheckChildren;
+- (void)sanityCheckChildren:(NSArray*)theChildren;
+#endif
+
+// ---- NSAccessibility methods ---- //
+
+// whether to skip this element when traversing the accessibility
+// hierarchy.
+- (BOOL)accessibilityIsIgnored;
+
+// called by third-parties to determine the deepest child element under the mouse
+- (id)accessibilityHitTest:(NSPoint)point;
+
+// returns the deepest unignored focused accessible element
+- (id)accessibilityFocusedUIElement;
+
+// a mozAccessible needs to at least provide links to its parent and
+// children.
+- (NSArray*)accessibilityAttributeNames;
+
+// value for the specified attribute
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+
+@end
+
diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm
new file mode 100644
index 0000000000..a02779ef25
--- /dev/null
+++ b/accessible/mac/mozAccessible.mm
@@ -0,0 +1,1197 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+#import "mozAccessible.h"
+
+#import "MacUtils.h"
+#import "mozView.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsIAccessibleRelation.h"
+#include "nsIAccessibleEditableText.h"
+#include "nsIPersistentProperties2.h"
+#include "Relation.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "mozilla/a11y/PDocAccessible.h"
+#include "OuterDocAccessible.h"
+
+#include "mozilla/Services.h"
+#include "nsRect.h"
+#include "nsCocoaUtils.h"
+#include "nsCoord.h"
+#include "nsObjCExceptions.h"
+#include "nsWhitespaceTokenizer.h"
+#include <prdtoa.h>
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand"
+#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex"
+#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator"
+#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator"
+#define NSAccessibilityMathBaseAttribute @"AXMathBase"
+#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript"
+#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript"
+#define NSAccessibilityMathUnderAttribute @"AXMathUnder"
+#define NSAccessibilityMathOverAttribute @"AXMathOver"
+#define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
+// XXX WebKit also defines the following attributes.
+// See bugs 1176970 and 1176983.
+// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
+// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
+// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
+// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
+
+#pragma mark -
+
+@implementation mozAccessible
+
+- (id)initWithAccessible:(uintptr_t)aGeckoAccessible
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mGeckoAccessible = aGeckoAccessible;
+ if (aGeckoAccessible & IS_PROXY)
+ mRole = [self getProxyAccessible]->Role();
+ else
+ mRole = [self getGeckoAccessible]->Role();
+ }
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mChildren release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible
+{
+ // Check if mGeckoAccessible points at a proxy
+ if (mGeckoAccessible & IS_PROXY)
+ return nil;
+
+ return reinterpret_cast<AccessibleWrap*>(mGeckoAccessible);
+}
+
+- (mozilla::a11y::ProxyAccessible*)getProxyAccessible
+{
+ // Check if mGeckoAccessible points at a proxy
+ if (!(mGeckoAccessible & IS_PROXY))
+ return nil;
+
+ return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY);
+}
+
+#pragma mark -
+
+- (BOOL)accessibilityIsIgnored
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // unknown (either unimplemented, or irrelevant) elements are marked as ignored
+ // as well as expired elements.
+
+ bool noRole = [[self role] isEqualToString:NSAccessibilityUnknownRole];
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return (noRole && !(accWrap->InteractiveState() & states::FOCUSABLE));
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return (noRole && !(proxy->State() & states::FOCUSABLE));
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NSMutableArray* additional = [NSMutableArray array];
+ switch (mRole) {
+ case roles::MATHML_ROOT:
+ [additional addObject:NSAccessibilityMathRootIndexAttribute];
+ [additional addObject:NSAccessibilityMathRootRadicandAttribute];
+ break;
+ case roles::MATHML_SQUARE_ROOT:
+ [additional addObject:NSAccessibilityMathRootRadicandAttribute];
+ break;
+ case roles::MATHML_FRACTION:
+ [additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
+ [additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
+ [additional addObject:NSAccessibilityMathLineThicknessAttribute];
+ break;
+ case roles::MATHML_SUB:
+ case roles::MATHML_SUP:
+ case roles::MATHML_SUB_SUP:
+ [additional addObject:NSAccessibilityMathBaseAttribute];
+ [additional addObject:NSAccessibilityMathSubscriptAttribute];
+ [additional addObject:NSAccessibilityMathSuperscriptAttribute];
+ break;
+ case roles::MATHML_UNDER:
+ case roles::MATHML_OVER:
+ case roles::MATHML_UNDER_OVER:
+ [additional addObject:NSAccessibilityMathBaseAttribute];
+ [additional addObject:NSAccessibilityMathUnderAttribute];
+ [additional addObject:NSAccessibilityMathOverAttribute];
+ break;
+ // XXX bug 1176983
+ // roles::MATHML_MULTISCRIPTS should also have the following attributes:
+ // - NSAccessibilityMathPrescriptsAttribute
+ // - NSAccessibilityMathPostscriptsAttribute
+ // XXX bug 1176970
+ // roles::MATHML_FENCED should also have the following attributes:
+ // - NSAccessibilityMathFencedOpenAttribute
+ // - NSAccessibilityMathFencedCloseAttribute
+ default:
+ break;
+ }
+
+ return additional;
+}
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // if we're expired, we don't support any attributes.
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return [NSArray array];
+
+ static NSArray* generalAttributes = nil;
+
+ if (!generalAttributes) {
+ // standard attributes that are shared and supported by all generic elements.
+ generalAttributes = [[NSArray alloc] initWithObjects: NSAccessibilityChildrenAttribute,
+ NSAccessibilityParentAttribute,
+ NSAccessibilityRoleAttribute,
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityValueAttribute,
+ NSAccessibilitySubroleAttribute,
+ NSAccessibilityRoleDescriptionAttribute,
+ NSAccessibilityPositionAttribute,
+ NSAccessibilityEnabledAttribute,
+ NSAccessibilitySizeAttribute,
+ NSAccessibilityWindowAttribute,
+ NSAccessibilityFocusedAttribute,
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityTitleUIElementAttribute,
+ NSAccessibilityTopLevelUIElementAttribute,
+#if DEBUG
+ @"AXMozDescription",
+#endif
+ nil];
+ }
+
+ NSArray* objectAttributes = generalAttributes;
+
+ NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames];
+ if ([additionalAttributes count])
+ objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
+
+ return objectAttributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)childAt:(uint32_t)i
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ Accessible* child = accWrap->GetChildAt(i);
+ return child ? GetNativeFromGeckoAccessible(child) : nil;
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ ProxyAccessible* child = proxy->ChildAt(i);
+ return child ? GetNativeFromProxy(child) : nil;
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return nil;
+
+#if DEBUG
+ if ([attribute isEqualToString:@"AXMozDescription"])
+ return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]];
+#endif
+
+ if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
+ return [self children];
+ if ([attribute isEqualToString:NSAccessibilityParentAttribute])
+ return [self parent];
+
+#ifdef DEBUG_hakan
+ NSLog (@"(%@ responding to attr %@)", self, attribute);
+#endif
+
+ if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
+ return [self role];
+ if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
+ return [self position];
+ if ([attribute isEqualToString:NSAccessibilitySubroleAttribute])
+ return [self subrole];
+ if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
+ return [NSNumber numberWithBool:[self isEnabled]];
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+ return [self value];
+ if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
+ return [self roleDescription];
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
+ return [NSNumber numberWithBool:[self isFocused]];
+ if ([attribute isEqualToString:NSAccessibilitySizeAttribute])
+ return [self size];
+ if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
+ return [self window];
+ if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
+ return [self window];
+ if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+ return [self title];
+ if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
+ if (accWrap) {
+ Relation rel = accWrap->RelationByType(RelationType::LABELLED_BY);
+ Accessible* tempAcc = rel.Next();
+ return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
+ }
+ nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY);
+ ProxyAccessible* tempProxy = rel.SafeElementAt(0);
+ return tempProxy ? GetNativeFromProxy(tempProxy) : nil;
+ }
+ if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
+ return [self help];
+
+ switch (mRole) {
+ case roles::MATHML_ROOT:
+ if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathRootIndexAttribute])
+ return [self childAt:1];
+ break;
+ case roles::MATHML_SQUARE_ROOT:
+ if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
+ return [self childAt:0];
+ break;
+ case roles::MATHML_FRACTION:
+ if ([attribute isEqualToString:NSAccessibilityMathFractionNumeratorAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
+ return [self childAt:1];
+ if ([attribute isEqualToString:NSAccessibilityMathLineThicknessAttribute]) {
+ // WebKit sets line thickness to some logical value parsed in the
+ // renderer object of the <mfrac> element. It's not clear whether the
+ // exact value is relevant to assistive technologies. From a semantic
+ // point of view, the only important point is to distinguish between
+ // <mfrac> elements that have a fraction bar and those that do not.
+ // Per the MathML 3 spec, the latter happens iff the linethickness
+ // attribute is of the form [zero-float][optional-unit]. In that case we
+ // set line thickness to zero and in the other cases we set it to one.
+ nsAutoString thickness;
+ if (accWrap) {
+ nsCOMPtr<nsIPersistentProperties> attributes = accWrap->Attributes();
+ nsAccUtils::GetAccAttr(attributes, nsGkAtoms::linethickness_, thickness);
+ } else {
+ AutoTArray<Attribute, 10> attrs;
+ proxy->Attributes(&attrs);
+ for (size_t i = 0 ; i < attrs.Length() ; i++) {
+ if (attrs.ElementAt(i).Name() == "thickness") {
+ thickness = attrs.ElementAt(i).Value();
+ break;
+ }
+ }
+ }
+ double value = 1.0;
+ if (!thickness.IsEmpty())
+ value = PR_strtod(NS_LossyConvertUTF16toASCII(thickness).get(),
+ nullptr);
+ return [NSNumber numberWithInteger:(value ? 1 : 0)];
+ }
+ break;
+ case roles::MATHML_SUB:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+ return [self childAt:1];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+ return nil;
+#endif
+ break;
+ case roles::MATHML_SUP:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+ return nil;
+#endif
+ if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+ return [self childAt:1];
+ break;
+ case roles::MATHML_SUB_SUP:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
+ return [self childAt:1];
+ if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
+ return [self childAt:2];
+ break;
+ case roles::MATHML_UNDER:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+ return [self childAt:1];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+ return nil;
+#endif
+ break;
+ case roles::MATHML_OVER:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+#ifdef DEBUG
+ if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+ return nil;
+#endif
+ if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+ return [self childAt:1];
+ break;
+ case roles::MATHML_UNDER_OVER:
+ if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
+ return [self childAt:0];
+ if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
+ return [self childAt:1];
+ if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
+ return [self childAt:2];
+ break;
+ // XXX bug 1176983
+ // roles::MATHML_MULTISCRIPTS should also have the following attributes:
+ // - NSAccessibilityMathPrescriptsAttribute
+ // - NSAccessibilityMathPostscriptsAttribute
+ // XXX bug 1176970
+ // roles::MATHML_FENCED should also have the following attributes:
+ // - NSAccessibilityMathFencedOpenAttribute
+ // - NSAccessibilityMathFencedCloseAttribute
+ default:
+ break;
+ }
+
+#ifdef DEBUG
+ NSLog (@"!!! %@ can't respond to attribute %@", self, attribute);
+#endif
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
+ return [self canBeFocused];
+
+ return NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef DEBUG_hakan
+ NSLog (@"[%@] %@='%@'", self, attribute, value);
+#endif
+
+ // we only support focusing elements so far.
+ if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue])
+ [self focus];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (id)accessibilityHitTest:(NSPoint)point
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return nil;
+
+ // Convert the given screen-global point in the cocoa coordinate system (with
+ // origin in the bottom-left corner of the screen) into point in the Gecko
+ // coordinate system (with origin in a top-left screen point).
+ NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
+ NSPoint tmpPoint = NSMakePoint(point.x,
+ [mainView frame].size.height - point.y);
+ LayoutDeviceIntPoint geckoPoint = nsCocoaUtils::
+ CocoaPointsToDevPixels(tmpPoint, nsCocoaUtils::GetBackingScaleFactor(mainView));
+
+ mozAccessible* nativeChild = nil;
+ if (accWrap) {
+ Accessible* child = accWrap->ChildAtPoint(geckoPoint.x, geckoPoint.y,
+ Accessible::eDeepestChild);
+ if (child)
+ nativeChild = GetNativeFromGeckoAccessible(child);
+ } else if (proxy) {
+ ProxyAccessible* child = proxy->ChildAtPoint(geckoPoint.x, geckoPoint.y,
+ Accessible::eDeepestChild);
+ if (child)
+ nativeChild = GetNativeFromProxy(child);
+ }
+
+ if (nativeChild)
+ return nativeChild;
+
+ // if we didn't find anything, return ourself or child view.
+ return GetObjectOrRepresentedView(self);
+}
+
+- (NSArray*)accessibilityActionNames
+{
+ return nil;
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ // by default we return whatever the MacOS API know about.
+ // if you have custom actions, override.
+ return NSAccessibilityActionDescription(action);
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+}
+
+- (id)accessibilityFocusedUIElement
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return nil;
+
+ mozAccessible* focusedChild = nil;
+ if (accWrap) {
+ Accessible* focusedGeckoChild = accWrap->FocusedChild();
+ if (focusedGeckoChild)
+ focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
+ } else if (proxy) {
+ ProxyAccessible* focusedGeckoChild = proxy->FocusedChild();
+ if (focusedGeckoChild)
+ focusedChild = GetNativeFromProxy(focusedGeckoChild);
+ }
+
+ if (focusedChild)
+ return GetObjectOrRepresentedView(focusedChild);
+
+ // return ourself if we can't get a native focused child.
+ return GetObjectOrRepresentedView(self);
+}
+
+#pragma mark -
+
+- (id <mozAccessible>)parent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ id nativeParent = nil;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ Accessible* accessibleParent = accWrap->Parent();
+ if (accessibleParent)
+ nativeParent = GetNativeFromGeckoAccessible(accessibleParent);
+ if (nativeParent)
+ return GetObjectOrRepresentedView(nativeParent);
+
+ // Return native of root accessible if we have no direct parent
+ nativeParent = GetNativeFromGeckoAccessible(accWrap->RootAccessible());
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if (ProxyAccessible* proxyParent = proxy->Parent()) {
+ nativeParent = GetNativeFromProxy(proxyParent);
+ }
+
+ if (nativeParent)
+ return GetObjectOrRepresentedView(nativeParent);
+
+ Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
+ nativeParent = outerDoc ?
+ GetNativeFromGeckoAccessible(outerDoc) : nil;
+ } else {
+ return nil;
+ }
+
+ NSAssert1 (nativeParent, @"!!! we can't find a parent for %@", self);
+
+ return GetObjectOrRepresentedView(nativeParent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)hasRepresentedView
+{
+ return NO;
+}
+
+- (id)representedView
+{
+ return nil;
+}
+
+- (BOOL)isRoot
+{
+ return NO;
+}
+
+// gets our native children lazily.
+// returns nil when there are no children.
+- (NSArray*)children
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (mChildren)
+ return mChildren;
+
+ // get the array of children.
+ mChildren = [[NSMutableArray alloc] init];
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ if (accWrap) {
+ uint32_t childCount = accWrap->ChildCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ mozAccessible* nativeChild = GetNativeFromGeckoAccessible(accWrap->GetChildAt(childIdx));
+ if (nativeChild)
+ [mChildren addObject:nativeChild];
+ }
+
+ // children from child if this is an outerdoc
+ OuterDocAccessible* docOwner = accWrap->AsOuterDoc();
+ if (docOwner) {
+ if (ProxyAccessible* proxyDoc = docOwner->RemoteChildDoc()) {
+ mozAccessible* nativeRemoteChild = GetNativeFromProxy(proxyDoc);
+ [mChildren insertObject:nativeRemoteChild atIndex:0];
+ NSAssert1 (nativeRemoteChild, @"%@ found a child remote doc missing a native\n", self);
+ }
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ uint32_t childCount = proxy->ChildrenCount();
+ for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
+ mozAccessible* nativeChild = GetNativeFromProxy(proxy->ChildAt(childIdx));
+ if (nativeChild)
+ [mChildren addObject:nativeChild];
+ }
+
+ }
+
+ return mChildren;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)position
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsIntRect rect;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ rect = accWrap->Bounds();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ rect = proxy->Bounds();
+ else
+ return nil;
+
+ NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
+ NSPoint p = NSMakePoint(static_cast<CGFloat>(rect.x) / scaleFactor,
+ [mainView frame].size.height - static_cast<CGFloat>(rect.y + rect.height) / scaleFactor);
+
+ return [NSValue valueWithPoint:p];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)size
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsIntRect rect;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ rect = accWrap->Bounds();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ rect = proxy->Bounds();
+ else
+ return nil;
+
+ CGFloat scaleFactor =
+ nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]);
+ return [NSValue valueWithSize:NSMakeSize(static_cast<CGFloat>(rect.width) / scaleFactor,
+ static_cast<CGFloat>(rect.height) / scaleFactor)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSString*)role
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ if (accWrap) {
+ #ifdef DEBUG_A11Y
+ NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap),
+ "Does not support Text when it should");
+ #endif
+ } else if (![self getProxyAccessible]) {
+ return nil;
+ }
+
+#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \
+ case roles::geckoRole: \
+ return macRole;
+
+ switch (mRole) {
+#include "RoleMap.h"
+ default:
+ NS_NOTREACHED("Unknown role.");
+ return NSAccessibilityUnknownRole;
+ }
+
+#undef ROLE
+}
+
+- (NSString*)subrole
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+
+ // Deal with landmarks first
+ nsIAtom* landmark = nullptr;
+ if (accWrap)
+ landmark = accWrap->LandmarkRole();
+ else if (proxy)
+ landmark = proxy->LandmarkRole();
+
+ if (landmark) {
+ if (landmark == nsGkAtoms::application)
+ return @"AXLandmarkApplication";
+ if (landmark == nsGkAtoms::banner)
+ return @"AXLandmarkBanner";
+ if (landmark == nsGkAtoms::complementary)
+ return @"AXLandmarkComplementary";
+ if (landmark == nsGkAtoms::contentinfo)
+ return @"AXLandmarkContentInfo";
+ if (landmark == nsGkAtoms::form)
+ return @"AXLandmarkForm";
+ if (landmark == nsGkAtoms::main)
+ return @"AXLandmarkMain";
+ if (landmark == nsGkAtoms::navigation)
+ return @"AXLandmarkNavigation";
+ if (landmark == nsGkAtoms::search)
+ return @"AXLandmarkSearch";
+ if (landmark == nsGkAtoms::searchbox)
+ return @"AXSearchField";
+ }
+
+ // Now, deal with widget roles
+ nsIAtom* roleAtom = nullptr;
+ if (accWrap && accWrap->HasARIARole()) {
+ const nsRoleMapEntry* roleMap = accWrap->ARIARoleMap();
+ roleAtom = *roleMap->roleAtom;
+ }
+ if (proxy)
+ roleAtom = proxy->ARIARoleAtom();
+
+ if (roleAtom) {
+ if (roleAtom == nsGkAtoms::alert)
+ return @"AXApplicationAlert";
+ if (roleAtom == nsGkAtoms::alertdialog)
+ return @"AXApplicationAlertDialog";
+ if (roleAtom == nsGkAtoms::article)
+ return @"AXDocumentArticle";
+ if (roleAtom == nsGkAtoms::dialog)
+ return @"AXApplicationDialog";
+ if (roleAtom == nsGkAtoms::document)
+ return @"AXDocument";
+ if (roleAtom == nsGkAtoms::log_)
+ return @"AXApplicationLog";
+ if (roleAtom == nsGkAtoms::math)
+ return @"AXDocumentMath";
+ if (roleAtom == nsGkAtoms::note_)
+ return @"AXDocumentNote";
+ if (roleAtom == nsGkAtoms::region)
+ return @"AXDocumentRegion";
+ if (roleAtom == nsGkAtoms::status)
+ return @"AXApplicationStatus";
+ if (roleAtom == nsGkAtoms::tabpanel)
+ return @"AXTabPanel";
+ if (roleAtom == nsGkAtoms::timer)
+ return @"AXApplicationTimer";
+ if (roleAtom == nsGkAtoms::tooltip)
+ return @"AXUserInterfaceTooltip";
+ }
+
+ switch (mRole) {
+ case roles::LIST:
+ return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole;
+
+ case roles::ENTRY:
+ if ((accWrap && accWrap->IsSearchbox()) ||
+ (proxy && proxy->IsSearchbox()))
+ return @"AXSearchField";
+ break;
+
+ case roles::DEFINITION_LIST:
+ return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole;
+
+ case roles::TERM:
+ return @"AXTerm";
+
+ case roles::DEFINITION:
+ return @"AXDefinition";
+
+ case roles::MATHML_MATH:
+ return @"AXDocumentMath";
+
+ case roles::MATHML_FRACTION:
+ return @"AXMathFraction";
+
+ case roles::MATHML_FENCED:
+ // XXX bug 1176970
+ // This should be AXMathFence, but doing so without implementing the
+ // whole fence interface seems to make VoiceOver crash, so we present it
+ // as a row for now.
+ return @"AXMathRow";
+
+ case roles::MATHML_SUB:
+ case roles::MATHML_SUP:
+ case roles::MATHML_SUB_SUP:
+ return @"AXMathSubscriptSuperscript";
+
+ case roles::MATHML_ROW:
+ case roles::MATHML_STYLE:
+ case roles::MATHML_ERROR:
+ return @"AXMathRow";
+
+ case roles::MATHML_UNDER:
+ case roles::MATHML_OVER:
+ case roles::MATHML_UNDER_OVER:
+ return @"AXMathUnderOver";
+
+ case roles::MATHML_SQUARE_ROOT:
+ return @"AXMathSquareRoot";
+
+ case roles::MATHML_ROOT:
+ return @"AXMathRoot";
+
+ case roles::MATHML_TEXT:
+ return @"AXMathText";
+
+ case roles::MATHML_NUMBER:
+ return @"AXMathNumber";
+
+ case roles::MATHML_IDENTIFIER:
+ return @"AXMathIdentifier";
+
+ case roles::MATHML_TABLE:
+ return @"AXMathTable";
+
+ case roles::MATHML_TABLE_ROW:
+ return @"AXMathTableRow";
+
+ case roles::MATHML_CELL:
+ return @"AXMathTableCell";
+
+ // XXX: NSAccessibility also uses subroles AXMathSeparatorOperator and
+ // AXMathFenceOperator. We should use the NS_MATHML_OPERATOR_FENCE and
+ // NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they
+ // are only available from the MathML layout code. Hence we just fallback
+ // to subrole AXMathOperator for now.
+ // XXX bug 1175747 WebKit also creates anonymous operators for <mfenced>
+ // which have subroles AXMathSeparatorOperator and AXMathFenceOperator.
+ case roles::MATHML_OPERATOR:
+ return @"AXMathOperator";
+
+ case roles::MATHML_MULTISCRIPTS:
+ return @"AXMathMultiscript";
+
+ case roles::SWITCH:
+ return @"AXSwitch";
+
+ case roles::ALERT:
+ return @"AXApplicationAlert";
+
+ case roles::SEPARATOR:
+ return @"AXContentSeparator";
+
+ case roles::PROPERTYPAGE:
+ return @"AXTabPanel";
+
+ case roles::DETAILS:
+ return @"AXDetails";
+
+ case roles::SUMMARY:
+ return @"AXSummary";
+
+ default:
+ break;
+ }
+
+ return nil;
+}
+
+struct RoleDescrMap
+{
+ NSString* role;
+ const nsString description;
+};
+
+static const RoleDescrMap sRoleDescrMap[] = {
+ { @"AXApplicationAlert", NS_LITERAL_STRING("alert") },
+ { @"AXApplicationAlertDialog", NS_LITERAL_STRING("alertDialog") },
+ { @"AXApplicationLog", NS_LITERAL_STRING("log") },
+ { @"AXApplicationStatus", NS_LITERAL_STRING("status") },
+ { @"AXApplicationTimer", NS_LITERAL_STRING("timer") },
+ { @"AXContentSeparator", NS_LITERAL_STRING("separator") },
+ { @"AXDefinition", NS_LITERAL_STRING("definition") },
+ { @"AXDocument", NS_LITERAL_STRING("document") },
+ { @"AXDocumentArticle", NS_LITERAL_STRING("article") },
+ { @"AXDocumentMath", NS_LITERAL_STRING("math") },
+ { @"AXDocumentNote", NS_LITERAL_STRING("note") },
+ { @"AXDocumentRegion", NS_LITERAL_STRING("region") },
+ { @"AXLandmarkApplication", NS_LITERAL_STRING("application") },
+ { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
+ { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
+ { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
+ { @"AXLandmarkMain", NS_LITERAL_STRING("main") },
+ { @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") },
+ { @"AXLandmarkSearch", NS_LITERAL_STRING("search") },
+ { @"AXSearchField", NS_LITERAL_STRING("searchTextField") },
+ { @"AXTabPanel", NS_LITERAL_STRING("tabPanel") },
+ { @"AXTerm", NS_LITERAL_STRING("term") },
+ { @"AXUserInterfaceTooltip", NS_LITERAL_STRING("tooltip") }
+};
+
+struct RoleDescrComparator
+{
+ const NSString* mRole;
+ explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {}
+ int operator()(const RoleDescrMap& aEntry) const {
+ return [mRole compare:aEntry.role];
+ }
+};
+
+- (NSString*)roleDescription
+{
+ if (mRole == roles::DOCUMENT)
+ return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
+
+ NSString* subrole = [self subrole];
+
+ if (subrole) {
+ size_t idx = 0;
+ if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
+ RoleDescrComparator(subrole), &idx)) {
+ return utils::LocalizedString(sRoleDescrMap[idx].description);
+ }
+ }
+
+ return NSAccessibilityRoleDescription([self role], subrole);
+}
+
+- (NSString*)title
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsAutoString title;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->Name(title);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->Name(title);
+
+ return nsCocoaUtils::ToNSString(title);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)value
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsAutoString value;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->Value(value);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->Value(value);
+
+ return nsCocoaUtils::ToNSString(value);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)valueDidChange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef DEBUG_hakan
+ NSLog(@"%@'s value changed!", self);
+#endif
+ // sending out a notification is expensive, so we don't do it other than for really important objects,
+ // like mozTextAccessible.
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)selectedTextDidChange
+{
+ // Do nothing. mozTextAccessible will.
+}
+
+- (void)documentLoadComplete
+{
+ id realSelf = GetObjectOrRepresentedView(self);
+ NSAccessibilityPostNotification(realSelf, NSAccessibilityFocusedUIElementChangedNotification);
+ NSAccessibilityPostNotification(realSelf, @"AXLoadComplete");
+ NSAccessibilityPostNotification(realSelf, @"AXLayoutComplete");
+}
+
+- (NSString*)help
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // What needs to go here is actually the accDescription of an item.
+ // The MSAA acc_help method has nothing to do with this one.
+ nsAutoString helpText;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->Description(helpText);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->Description(helpText);
+
+ return nsCocoaUtils::ToNSString(helpText);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// objc-style description (from NSObject); not to be confused with the accessible description above.
+- (NSString*)description
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSString stringWithFormat:@"(%p) %@", self, [self role]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isFocused
+{
+ return FocusMgr()->IsFocused([self getGeckoAccessible]);
+}
+
+- (BOOL)canBeFocused
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return accWrap->InteractiveState() & states::FOCUSABLE;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return proxy->State() & states::FOCUSABLE;
+
+ return false;
+}
+
+- (BOOL)focus
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->TakeFocus();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->TakeFocus();
+ else
+ return NO;
+
+ return YES;
+}
+
+- (BOOL)isEnabled
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return ((accWrap->InteractiveState() & states::UNAVAILABLE) == 0);
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return ((proxy->State() & states::UNAVAILABLE) == 0);
+
+ return false;
+}
+
+// The root accessible calls this when the focused node was
+// changed to us.
+- (void)didReceiveFocus
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef DEBUG_hakan
+ NSLog (@"%@ received focus!", self);
+#endif
+ NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
+ NSAccessibilityFocusedUIElementChangedNotification);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSWindow*)window
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // Get a pointer to the native window (NSWindow) we reside in.
+ NSWindow *nativeWindow = nil;
+ DocAccessible* docAcc = nullptr;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ docAcc = accWrap->Document();
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
+ if (outerDoc)
+ docAcc = outerDoc->Document();
+ }
+
+ if (docAcc)
+ nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow());
+
+ NSAssert1(nativeWindow, @"Could not get native window for %@", self);
+ return nativeWindow;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)invalidateChildren
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // make room for new children
+ [mChildren release];
+ mChildren = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)appendChild:(Accessible*)aAccessible
+{
+ // if mChildren is nil, then we don't even need to bother
+ if (!mChildren)
+ return;
+
+ mozAccessible *curNative = GetNativeFromGeckoAccessible(aAccessible);
+ if (curNative)
+ [mChildren addObject:curNative];
+}
+
+- (void)expire
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self invalidateChildren];
+
+ mGeckoAccessible = 0;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)isExpired
+{
+ return ![self getGeckoAccessible] && ![self getProxyAccessible];
+}
+
+#pragma mark -
+#pragma mark Debug methods
+#pragma mark -
+
+#ifdef DEBUG
+
+// will check that our children actually reference us as their
+// parent.
+- (void)sanityCheckChildren:(NSArray *)children
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSEnumerator *iter = [children objectEnumerator];
+ mozAccessible *curObj = nil;
+
+ NSLog(@"sanity checking %@", self);
+
+ while ((curObj = [iter nextObject])) {
+ id realSelf = GetObjectOrRepresentedView(self);
+ NSLog(@"checking %@", realSelf);
+ NSAssert2([curObj parent] == realSelf,
+ @"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, realSelf);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)sanityCheckChildren
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self sanityCheckChildren:[self children]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)printHierarchy
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self printHierarchyWithLevel:0];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)printHierarchyWithLevel:(unsigned)level
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSAssert(![self isExpired], @"!!! trying to print hierarchy of expired object!");
+
+ // print this node
+ NSMutableString *indent = [NSMutableString stringWithCapacity:level];
+ unsigned i=0;
+ for (;i<level;i++)
+ [indent appendString:@" "];
+
+ NSLog (@"%@(#%i) %@", indent, level, self);
+
+ // use |children| method to make sure our children are lazily fetched first.
+ NSArray *children = [self children];
+ if (!children)
+ return;
+
+ [self sanityCheckChildren];
+
+ NSEnumerator *iter = [children objectEnumerator];
+ mozAccessible *object = nil;
+
+ while (iter && (object = [iter nextObject]))
+ // print every child node's subtree, increasing the indenting
+ // by two for every level.
+ [object printHierarchyWithLevel:(level+1)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#endif /* DEBUG */
+
+@end
diff --git a/accessible/mac/mozAccessibleProtocol.h b/accessible/mac/mozAccessibleProtocol.h
new file mode 100644
index 0000000000..5f67b1dcf2
--- /dev/null
+++ b/accessible/mac/mozAccessibleProtocol.h
@@ -0,0 +1,69 @@
+/* -*- Mode: Objective-C; tab-width: 2; 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "mozView.h"
+
+/* This protocol's primary use is so widget/cocoa can talk back to us
+ properly.
+
+ ChildView owns the topmost mozRootAccessible, and needs to take care of setting up
+ that parent/child relationship.
+
+ This protocol is thus used to make sure it knows it's talking to us, and not
+ just some random |id|.
+*/
+
+@protocol mozAccessible
+
+// returns whether this accessible is the root accessible. there is one
+// root accessible per window.
+- (BOOL)isRoot;
+
+// some mozAccessibles implement accessibility support in place of another object. for example,
+// ChildView gets its support from us.
+//
+// instead of returning a mozAccessible to the OS when it wants an object, we need to pass the view we represent, so the
+// OS doesn't get confused and think we return some random object.
+- (BOOL)hasRepresentedView;
+- (id)representedView;
+
+#ifdef DEBUG
+// debug utility that will print the native accessibility tree, starting
+// at this node.
+- (void)printHierarchy;
+#endif
+
+/*** general ***/
+
+// returns the accessible at the specified point.
+- (id)accessibilityHitTest:(NSPoint)point;
+
+// whether this element is flagged as ignored.
+- (BOOL)accessibilityIsIgnored;
+
+// currently focused UI element (possibly a child accessible)
+- (id)accessibilityFocusedUIElement;
+
+/*** attributes ***/
+
+// all supported attributes
+- (NSArray*)accessibilityAttributeNames;
+
+// value for given attribute.
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+
+// whether a particular attribute can be modified
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+
+/*** actions ***/
+
+- (NSArray*)accessibilityActionNames;
+- (NSString*)accessibilityActionDescription:(NSString*)action;
+- (void)accessibilityPerformAction:(NSString*)action;
+
+@end
+
diff --git a/accessible/mac/mozActionElements.h b/accessible/mac/mozActionElements.h
new file mode 100644
index 0000000000..a325921eb8
--- /dev/null
+++ b/accessible/mac/mozActionElements.h
@@ -0,0 +1,37 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import "mozAccessible.h"
+
+/* Simple subclasses for things like checkboxes, buttons, etc. */
+
+@interface mozButtonAccessible : mozAccessible
+ {
+ }
+- (BOOL)hasPopup;
+- (void)click;
+- (BOOL)isTab;
+@end
+
+@interface mozCheckboxAccessible : mozButtonAccessible
+// returns one of the constants defined in CheckboxValue
+- (int)isChecked;
+@end
+
+/* Class for tabs - not individual tabs */
+@interface mozTabsAccessible : mozAccessible
+{
+ NSMutableArray* mTabs;
+}
+-(id)tabs;
+@end
+
+/**
+ * Accessible for a PANE
+ */
+@interface mozPaneAccessible : mozAccessible
+
+@end
diff --git a/accessible/mac/mozActionElements.mm b/accessible/mac/mozActionElements.mm
new file mode 100644
index 0000000000..5decd6cccc
--- /dev/null
+++ b/accessible/mac/mozActionElements.mm
@@ -0,0 +1,340 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+#import "mozActionElements.h"
+
+#import "MacUtils.h"
+#include "Accessible-inl.h"
+#include "DocAccessible.h"
+#include "XULTabAccessible.h"
+
+#include "nsDeckFrame.h"
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::a11y;
+
+enum CheckboxValue {
+ // these constants correspond to the values in the OS
+ kUnchecked = 0,
+ kChecked = 1,
+ kMixed = 2
+};
+
+@implementation mozButtonAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ static NSArray *attributes = nil;
+ if (!attributes) {
+ attributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required
+ NSAccessibilityRoleAttribute, // required
+ NSAccessibilityRoleDescriptionAttribute,
+ NSAccessibilityPositionAttribute, // required
+ NSAccessibilitySizeAttribute, // required
+ NSAccessibilityWindowAttribute, // required
+ NSAccessibilityPositionAttribute, // required
+ NSAccessibilityTopLevelUIElementAttribute, // required
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityEnabledAttribute, // required
+ NSAccessibilityFocusedAttribute, // required
+ NSAccessibilityTitleAttribute, // required
+ NSAccessibilityChildrenAttribute,
+ NSAccessibilityDescriptionAttribute,
+#if DEBUG
+ @"AXMozDescription",
+#endif
+ nil];
+ }
+ return attributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
+ if ([self hasPopup])
+ return [self children];
+ return nil;
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
+ if ([self isTab])
+ return utils::LocalizedString(NS_LITERAL_STRING("tab"));
+
+ return NSAccessibilityRoleDescription([self role], nil);
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)accessibilityIsIgnored
+{
+ return ![self getGeckoAccessible] && ![self getProxyAccessible];
+}
+
+- (NSArray*)accessibilityActionNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([self isEnabled]) {
+ if ([self hasPopup])
+ return [NSArray arrayWithObjects:NSAccessibilityPressAction,
+ NSAccessibilityShowMenuAction,
+ nil];
+ return [NSArray arrayWithObject:NSAccessibilityPressAction];
+ }
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([action isEqualToString:NSAccessibilityPressAction]) {
+ if ([self isTab])
+ return utils::LocalizedString(NS_LITERAL_STRING("switch"));
+
+ return @"press button"; // XXX: localize this later?
+ }
+
+ if ([self hasPopup]) {
+ if ([action isEqualToString:NSAccessibilityShowMenuAction])
+ return @"show menu";
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([self isEnabled] && [action isEqualToString:NSAccessibilityPressAction]) {
+ // TODO: this should bring up the menu, but currently doesn't.
+ // once msaa and atk have merged better, they will implement
+ // the action needed to show the menu.
+ [self click];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)click
+{
+ // both buttons and checkboxes have only one action. we should really stop using arbitrary
+ // arrays with actions, and define constants for these actions.
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ accWrap->DoAction(0);
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ proxy->DoAction(0);
+}
+
+- (BOOL)isTab
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return accWrap->Role() == roles::PAGETAB;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return proxy->Role() == roles::PAGETAB;
+
+ return false;
+}
+
+- (BOOL)hasPopup
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ return accWrap->NativeState() & states::HASPOPUP;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return proxy->NativeState() & states::HASPOPUP;
+
+ return false;
+}
+
+@end
+
+@implementation mozCheckboxAccessible
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([action isEqualToString:NSAccessibilityPressAction]) {
+ if ([self isChecked] != kUnchecked)
+ return @"uncheck checkbox"; // XXX: localize this later?
+
+ return @"check checkbox"; // XXX: localize this later?
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (int)isChecked
+{
+ uint64_t state = 0;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible])
+ state = accWrap->NativeState();
+ else if (ProxyAccessible* proxy = [self getProxyAccessible])
+ state = proxy->NativeState();
+
+ // check if we're checked or in a mixed state
+ if (state & states::CHECKED) {
+ return (state & states::MIXED) ? kMixed : kChecked;
+ }
+
+ return kUnchecked;
+}
+
+- (id)value
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSNumber numberWithInt:[self isChecked]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+@end
+
+@implementation mozTabsAccessible
+
+- (void)dealloc
+{
+ [mTabs release];
+
+ [super dealloc];
+}
+
+- (NSArray*)accessibilityAttributeNames
+{
+ // standard attributes that are shared and supported by root accessible (AXMain) elements.
+ static NSMutableArray* attributes = nil;
+
+ if (!attributes) {
+ attributes = [[super accessibilityAttributeNames] mutableCopy];
+ [attributes addObject:NSAccessibilityContentsAttribute];
+ [attributes addObject:NSAccessibilityTabsAttribute];
+ }
+
+ return attributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ if ([attribute isEqualToString:NSAccessibilityContentsAttribute])
+ return [super children];
+ if ([attribute isEqualToString:NSAccessibilityTabsAttribute])
+ return [self tabs];
+
+ return [super accessibilityAttributeValue:attribute];
+}
+
+/**
+ * Returns the selected tab (the mozAccessible)
+ */
+- (id)value
+{
+ mozAccessible* nativeAcc = nil;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ if (Accessible* accTab = accWrap->GetSelectedItem(0)) {
+ accTab->GetNativeInterface((void**)&nativeAcc);
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if (ProxyAccessible* proxyTab = proxy->GetSelectedItem(0)) {
+ nativeAcc = GetNativeFromProxy(proxyTab);
+ }
+ }
+
+ return nativeAcc;
+}
+
+/**
+ * Return the mozAccessibles that are the tabs.
+ */
+- (id)tabs
+{
+ if (mTabs)
+ return mTabs;
+
+ NSArray* children = [self children];
+ NSEnumerator* enumerator = [children objectEnumerator];
+ mTabs = [[NSMutableArray alloc] init];
+
+ id obj;
+ while ((obj = [enumerator nextObject]))
+ if ([obj isTab])
+ [mTabs addObject:obj];
+
+ return mTabs;
+}
+
+- (void)invalidateChildren
+{
+ [super invalidateChildren];
+
+ [mTabs release];
+ mTabs = nil;
+}
+
+@end
+
+@implementation mozPaneAccessible
+
+- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy)
+ return 0;
+
+ // By default this calls -[[mozAccessible children] count].
+ // Since we don't cache mChildren. This is faster.
+ if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
+ if (accWrap)
+ return accWrap->ChildCount() ? 1 : 0;
+
+ return proxy->ChildrenCount() ? 1 : 0;
+ }
+
+ return [super accessibilityArrayAttributeCount:attribute];
+}
+
+- (NSArray*)children
+{
+ if (![self getGeckoAccessible])
+ return nil;
+
+ nsDeckFrame* deckFrame = do_QueryFrame([self getGeckoAccessible]->GetFrame());
+ nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nullptr;
+
+ Accessible* selectedAcc = nullptr;
+ if (selectedFrame) {
+ nsINode* node = selectedFrame->GetContent();
+ selectedAcc = [self getGeckoAccessible]->Document()->GetAccessible(node);
+ }
+
+ if (selectedAcc) {
+ mozAccessible *curNative = GetNativeFromGeckoAccessible(selectedAcc);
+ if (curNative)
+ return [NSArray arrayWithObjects:GetObjectOrRepresentedView(curNative), nil];
+ }
+
+ return nil;
+}
+
+@end
diff --git a/accessible/mac/mozDocAccessible.h b/accessible/mac/mozDocAccessible.h
new file mode 100644
index 0000000000..c381773110
--- /dev/null
+++ b/accessible/mac/mozDocAccessible.h
@@ -0,0 +1,31 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import "mozAccessible.h"
+
+// our protocol that we implement (so cocoa widgets can talk to us)
+#import "mozAccessibleProtocol.h"
+
+/*
+ The root accessible. There is one per window.
+ Created by the RootAccessibleWrap.
+*/
+@interface mozRootAccessible : mozAccessible
+{
+ // the mozView that we're representing.
+ // all outside communication goes through the mozView.
+ // in reality, it's just piping all calls to us, and we're
+ // doing its dirty work!
+ //
+ // whenever someone asks who we are (e.g., a child asking
+ // for its parent, or our parent asking for its child), we'll
+ // respond the mozView. it is absolutely necessary for third-
+ // party tools that we do this!
+ //
+ // /hwaara
+ id <mozView, mozAccessible> mParallelView; // weak ref
+}
+@end
diff --git a/accessible/mac/mozDocAccessible.mm b/accessible/mac/mozDocAccessible.mm
new file mode 100644
index 0000000000..4bae81f01c
--- /dev/null
+++ b/accessible/mac/mozDocAccessible.mm
@@ -0,0 +1,111 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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 "RootAccessibleWrap.h"
+
+#import "mozDocAccessible.h"
+
+#import "mozView.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::a11y;
+
+static id <mozAccessible, mozView>
+getNativeViewFromRootAccessible(Accessible* aAccessible)
+{
+ RootAccessibleWrap* root =
+ static_cast<RootAccessibleWrap*>(aAccessible->AsRoot());
+ id <mozAccessible, mozView> nativeView = nil;
+ root->GetNativeWidget ((void**)&nativeView);
+ return nativeView;
+}
+
+#pragma mark -
+
+@implementation mozRootAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // if we're expired, we don't support any attributes.
+ if (![self getGeckoAccessible])
+ return [NSArray array];
+
+ // standard attributes that are shared and supported by root accessible (AXMain) elements.
+ static NSMutableArray* attributes = nil;
+
+ if (!attributes) {
+ attributes = [[super accessibilityAttributeNames] mutableCopy];
+ [attributes addObject:NSAccessibilityMainAttribute];
+ [attributes addObject:NSAccessibilityMinimizedAttribute];
+ }
+
+ return attributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([attribute isEqualToString:NSAccessibilityMainAttribute])
+ return [NSNumber numberWithBool:[[self window] isMainWindow]];
+ if ([attribute isEqualToString:NSAccessibilityMinimizedAttribute])
+ return [NSNumber numberWithBool:[[self window] isMiniaturized]];
+
+ return [super accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+
+// return the AXParent that our parallell NSView tells us about.
+- (id)parent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mParallelView)
+ mParallelView = (id<mozView, mozAccessible>)[self representedView];
+
+ if (mParallelView)
+ return [mParallelView accessibilityAttributeValue:NSAccessibilityParentAttribute];
+
+ NSAssert(mParallelView, @"we're a root accessible w/o native view?");
+ return [super parent];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)hasRepresentedView
+{
+ return YES;
+}
+
+// this will return our parallell NSView. see mozDocAccessible.h
+- (id)representedView
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (mParallelView)
+ return (id)mParallelView;
+
+ mParallelView = getNativeViewFromRootAccessible ([self getGeckoAccessible]);
+
+ NSAssert(mParallelView, @"can't return root accessible's native parallel view.");
+ return mParallelView;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isRoot
+{
+ return YES;
+}
+
+@end
diff --git a/accessible/mac/mozHTMLAccessible.h b/accessible/mac/mozHTMLAccessible.h
new file mode 100644
index 0000000000..c70a3c2a25
--- /dev/null
+++ b/accessible/mac/mozHTMLAccessible.h
@@ -0,0 +1,16 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#import "mozAccessible.h"
+
+@interface mozHeadingAccessible : mozAccessible
+
+@end
+
+@interface mozLinkAccessible : mozAccessible
+
+@end
diff --git a/accessible/mac/mozHTMLAccessible.mm b/accessible/mac/mozHTMLAccessible.mm
new file mode 100644
index 0000000000..2079a4aa6b
--- /dev/null
+++ b/accessible/mac/mozHTMLAccessible.mm
@@ -0,0 +1,141 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#import "mozHTMLAccessible.h"
+
+#import "Accessible-inl.h"
+#import "HyperTextAccessible.h"
+
+#import "nsCocoaUtils.h"
+
+using namespace mozilla::a11y;
+
+@implementation mozHeadingAccessible
+
+- (NSString*)title
+{
+ nsAutoString title;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ mozilla::ErrorResult rv;
+ // XXX use the flattening API when there are available
+ // see bug 768298
+ accWrap->GetContent()->GetTextContent(title, rv);
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ proxy->Title(title);
+ }
+
+ return nsCocoaUtils::ToNSString(title);
+}
+
+- (id)value
+{
+ uint32_t level = 0;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ level = accWrap->GetLevelInternal();
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ level = proxy->GetLevelInternal();
+ }
+
+ return [NSNumber numberWithInt:level];
+}
+
+@end
+
+@interface mozLinkAccessible ()
+-(NSURL*)url;
+@end
+
+@implementation mozLinkAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ // if we're expired, we don't support any attributes.
+ if (![self getGeckoAccessible] && ![self getProxyAccessible])
+ return [NSArray array];
+
+ static NSMutableArray* attributes = nil;
+
+ if (!attributes) {
+ attributes = [[super accessibilityAttributeNames] mutableCopy];
+ [attributes addObject:NSAccessibilityURLAttribute];
+ }
+
+ return attributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ if ([attribute isEqualToString:NSAccessibilityURLAttribute])
+ return [self url];
+
+ return [super accessibilityAttributeValue:attribute];
+}
+
+- (NSArray*)accessibilityActionNames
+{
+ // if we're expired, we don't support any attributes.
+ if (![self getGeckoAccessible] && ![self getProxyAccessible])
+ return [NSArray array];
+
+ static NSArray* actionNames = nil;
+
+ if (!actionNames) {
+ actionNames = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction,
+ nil];
+ }
+
+ return actionNames;
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ if (!accWrap && !proxy) {
+ return;
+ }
+
+ if ([action isEqualToString:NSAccessibilityPressAction]) {
+ if (accWrap) {
+ accWrap->DoAction(0);
+ } else if (proxy) {
+ proxy->DoAction(0);
+ }
+ return;
+ }
+
+ [super accessibilityPerformAction:action];
+
+}
+
+- (NSString*)customDescription
+{
+ return @"";
+}
+
+- (NSString*)value
+{
+ return @"";
+}
+
+- (NSURL*)url
+{
+ nsAutoString value;
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ accWrap->Value(value);
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ proxy->Value(value);
+ }
+
+ NSString* urlString = value.IsEmpty() ? nil : nsCocoaUtils::ToNSString(value);
+ if (!urlString)
+ return nil;
+
+ return [NSURL URLWithString:urlString];
+}
+
+@end
diff --git a/accessible/mac/mozTableAccessible.h b/accessible/mac/mozTableAccessible.h
new file mode 100644
index 0000000000..435b5adc57
--- /dev/null
+++ b/accessible/mac/mozTableAccessible.h
@@ -0,0 +1,28 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#import "mozAccessible.h"
+
+@interface mozTablePartAccessible : mozAccessible
+- (BOOL)isLayoutTablePart;
+- (NSString*)role;
+@end
+
+@interface mozTableAccessible : mozTablePartAccessible
+- (NSArray*)additionalAccessibilityAttributeNames;
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+@end
+
+@interface mozTableRowAccessible : mozTablePartAccessible
+- (NSArray*)additionalAccessibilityAttributeNames;
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+@end
+
+@interface mozTableCellAccessible : mozTablePartAccessible
+- (NSArray*)additionalAccessibilityAttributeNames;
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+@end
diff --git a/accessible/mac/mozTableAccessible.mm b/accessible/mac/mozTableAccessible.mm
new file mode 100644
index 0000000000..6ad157b9f0
--- /dev/null
+++ b/accessible/mac/mozTableAccessible.mm
@@ -0,0 +1,281 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#import "Accessible-inl.h"
+#import "mozTableAccessible.h"
+#import "TableAccessible.h"
+#import "TableCellAccessible.h"
+#import "nsCocoaUtils.h"
+
+using namespace mozilla::a11y;
+
+// convert an array of Gecko accessibles to an NSArray of native accessibles
+static inline NSMutableArray*
+ConvertToNSArray(nsTArray<Accessible*>& aArray)
+{
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+
+ // iterate through the list, and get each native accessible.
+ size_t totalCount = aArray.Length();
+ for (size_t i = 0; i < totalCount; i++) {
+ Accessible* curAccessible = aArray.ElementAt(i);
+ mozAccessible* curNative = GetNativeFromGeckoAccessible(curAccessible);
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+
+ return nativeArray;
+}
+
+// convert an array of Gecko proxy accessibles to an NSArray of native accessibles
+static inline NSMutableArray*
+ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray)
+{
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+
+ // iterate through the list, and get each native accessible.
+ size_t totalCount = aArray.Length();
+ for (size_t i = 0; i < totalCount; i++) {
+ ProxyAccessible* curAccessible = aArray.ElementAt(i);
+ mozAccessible* curNative = GetNativeFromProxy(curAccessible);
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+
+ return nativeArray;
+}
+
+@implementation mozTablePartAccessible
+- (BOOL)isLayoutTablePart;
+{
+ if (Accessible* accWrap = [self getGeckoAccessible]) {
+ while (accWrap) {
+ if (accWrap->IsTable()) {
+ return accWrap->AsTable()->IsProbablyLayoutTable();
+ }
+ accWrap = accWrap->Parent();
+ }
+ return false;
+ }
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ while (proxy) {
+ if (proxy->IsTable()) {
+ return proxy->TableIsProbablyForLayout();
+ }
+ proxy = proxy->Parent();
+ }
+ }
+
+ return false;
+}
+
+- (NSString*)role
+{
+ return [self isLayoutTablePart] ? NSAccessibilityGroupRole : [super role];
+}
+@end
+
+@implementation mozTableAccessible
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
+ if ([self isLayoutTablePart]) {
+ return additionalAttributes;
+ }
+
+ static NSArray* tableAttrs = nil;
+ if (!tableAttrs) {
+ NSMutableArray* tempArray = [NSMutableArray new];
+ [tempArray addObject:NSAccessibilityRowCountAttribute];
+ [tempArray addObject:NSAccessibilityColumnCountAttribute];
+ [tempArray addObject:NSAccessibilityRowsAttribute];
+ tableAttrs = [[NSArray alloc] initWithArray:tempArray];
+ [tempArray release];
+ }
+
+ return [additionalAttributes arrayByAddingObjectsFromArray:tableAttrs];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ TableAccessible* table = accWrap->AsTable();
+ if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
+ return @(table->RowCount());
+ if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
+ return @(table->ColCount());
+ if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
+ // Create a new array with the list of table rows.
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+ uint32_t totalCount = accWrap->ChildCount();
+ for (uint32_t i = 0; i < totalCount; i++) {
+ if (accWrap->GetChildAt(i)->IsTableRow()) {
+ mozAccessible* curNative =
+ GetNativeFromGeckoAccessible(accWrap->GetChildAt(i));
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+ }
+ return nativeArray;
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityRowCountAttribute])
+ return @(proxy->TableRowCount());
+ if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute])
+ return @(proxy->TableColumnCount());
+ if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) {
+ // Create a new array with the list of table rows.
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+ uint32_t totalCount = proxy->ChildrenCount();
+ for (uint32_t i = 0; i < totalCount; i++) {
+ if (proxy->ChildAt(i)->IsTableRow()) {
+ mozAccessible* curNative =
+ GetNativeFromProxy(proxy->ChildAt(i));
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+ }
+ return nativeArray;
+ }
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+}
+@end
+
+@implementation mozTableRowAccessible
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
+ if ([self isLayoutTablePart]) {
+ return additionalAttributes;
+ }
+
+ static NSArray* tableRowAttrs = nil;
+ if (!tableRowAttrs) {
+ NSMutableArray* tempArray = [NSMutableArray new];
+ [tempArray addObject:NSAccessibilityIndexAttribute];
+ tableRowAttrs = [[NSArray alloc] initWithArray:tempArray];
+ [tempArray release];
+ }
+
+ return [additionalAttributes arrayByAddingObjectsFromArray:tableRowAttrs];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
+ // Count the number of rows before that one to obtain the row index.
+ uint32_t index = 0;
+ Accessible* parent = accWrap->Parent();
+ if (parent) {
+ for (int32_t i = accWrap->IndexInParent() - 1; i >= 0; i--) {
+ if (parent->GetChildAt(i)->IsTableRow()) {
+ index++;
+ }
+ }
+ }
+ return [NSNumber numberWithUnsignedInteger:index];
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) {
+ // Count the number of rows before that one to obtain the row index.
+ uint32_t index = 0;
+ ProxyAccessible* parent = proxy->Parent();
+ if (parent) {
+ for (int32_t i = proxy->IndexInParent() - 1; i >= 0; i--) {
+ if (parent->ChildAt(i)->IsTableRow()) {
+ index++;
+ }
+ }
+ }
+ return [NSNumber numberWithUnsignedInteger:index];
+ }
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+}
+@end
+
+@implementation mozTableCellAccessible
+- (NSArray*)additionalAccessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames];
+ if ([self isLayoutTablePart]) {
+ return additionalAttributes;
+ }
+
+ static NSArray* tableCellAttrs = nil;
+ if (!tableCellAttrs) {
+ NSMutableArray* tempArray = [NSMutableArray new];
+ [tempArray addObject:NSAccessibilityRowIndexRangeAttribute];
+ [tempArray addObject:NSAccessibilityColumnIndexRangeAttribute];
+ [tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute];
+ [tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute];
+ tableCellAttrs = [[NSArray alloc] initWithArray:tempArray];
+ [tempArray release];
+ }
+
+ return [additionalAttributes arrayByAddingObjectsFromArray:tableCellAttrs];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ TableCellAccessible* cell = accWrap->AsTableCell();
+ if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(),
+ cell->RowExtent())];
+ if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(),
+ cell->ColExtent())];
+ if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
+ AutoTArray<Accessible*, 10> headerCells;
+ cell->RowHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
+ AutoTArray<Accessible*, 10> headerCells;
+ cell->ColHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(proxy->RowIdx(),
+ proxy->RowExtent())];
+ if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute])
+ return [NSValue valueWithRange:NSMakeRange(proxy->ColIdx(),
+ proxy->ColExtent())];
+ if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) {
+ nsTArray<ProxyAccessible*> headerCells;
+ proxy->RowHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) {
+ nsTArray<ProxyAccessible*> headerCells;
+ proxy->ColHeaderCells(&headerCells);
+ return ConvertToNSArray(headerCells);
+ }
+ }
+
+ return [super accessibilityAttributeValue:attribute];
+}
+@end
diff --git a/accessible/mac/mozTextAccessible.h b/accessible/mac/mozTextAccessible.h
new file mode 100644
index 0000000000..8bc23ae8d5
--- /dev/null
+++ b/accessible/mac/mozTextAccessible.h
@@ -0,0 +1,17 @@
+/* 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/. */
+
+#import "mozAccessible.h"
+
+#import "HyperTextAccessible.h"
+
+@interface mozTextAccessible : mozAccessible
+{
+}
+@end
+
+@interface mozTextLeafAccessible : mozAccessible
+{
+}
+@end
diff --git a/accessible/mac/mozTextAccessible.mm b/accessible/mac/mozTextAccessible.mm
new file mode 100644
index 0000000000..1f433b802e
--- /dev/null
+++ b/accessible/mac/mozTextAccessible.mm
@@ -0,0 +1,627 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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 "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "TextLeafAccessible.h"
+
+#include "nsCocoaUtils.h"
+#include "nsObjCExceptions.h"
+
+#import "mozTextAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+inline bool
+ToNSRange(id aValue, NSRange* aRange)
+{
+ NS_PRECONDITION(aRange, "aRange is nil");
+
+ if ([aValue isKindOfClass:[NSValue class]] &&
+ strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) {
+ *aRange = [aValue rangeValue];
+ return true;
+ }
+
+ return false;
+}
+
+inline NSString*
+ToNSString(id aValue)
+{
+ if ([aValue isKindOfClass:[NSString class]]) {
+ return aValue;
+ }
+
+ return nil;
+}
+
+@interface mozTextAccessible ()
+- (NSString*)subrole;
+- (NSString*)selectedText;
+- (NSValue*)selectedTextRange;
+- (NSValue*)visibleCharacterRange;
+- (long)textLength;
+- (BOOL)isReadOnly;
+- (NSNumber*)caretLineNumber;
+- (void)setText:(NSString*)newText;
+- (NSString*)text;
+- (NSString*)stringFromRange:(NSRange*)range;
+@end
+
+@implementation mozTextAccessible
+
+- (BOOL)accessibilityIsIgnored
+{
+ return ![self getGeckoAccessible] && ![self getProxyAccessible];
+}
+
+- (NSArray*)accessibilityAttributeNames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ static NSMutableArray* supportedAttributes = nil;
+ if (!supportedAttributes) {
+ // text-specific attributes to supplement the standard one
+ supportedAttributes = [[NSMutableArray alloc] initWithObjects:
+ NSAccessibilitySelectedTextAttribute, // required
+ NSAccessibilitySelectedTextRangeAttribute, // required
+ NSAccessibilityNumberOfCharactersAttribute, // required
+ NSAccessibilityVisibleCharacterRangeAttribute, // required
+ NSAccessibilityInsertionPointLineNumberAttribute,
+ @"AXRequired",
+ @"AXInvalid",
+ nil
+ ];
+ [supportedAttributes addObjectsFromArray:[super accessibilityAttributeNames]];
+ }
+ return supportedAttributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute])
+ return [NSNumber numberWithInt:[self textLength]];
+
+ if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute])
+ return [self caretLineNumber];
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute])
+ return [self selectedTextRange];
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute])
+ return [self selectedText];
+
+ if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+ return @"";
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
+ // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText
+ // object's AXSelectedText attribute. See bug 674612 for details.
+ // Also if there is no selected text, we return the full text.
+ // See bug 369710 for details.
+ if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) {
+ NSString* selectedText = [self selectedText];
+ return (selectedText && [selectedText length]) ? selectedText : [self text];
+ }
+
+ return [self text];
+ }
+
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ if ([attribute isEqualToString:@"AXRequired"]) {
+ return [NSNumber numberWithBool:!!(accWrap->State() & states::REQUIRED)];
+ }
+
+ if ([attribute isEqualToString:@"AXInvalid"]) {
+ return [NSNumber numberWithBool:!!(accWrap->State() & states::INVALID)];
+ }
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ if ([attribute isEqualToString:@"AXRequired"]) {
+ return [NSNumber numberWithBool:!!(proxy->State() & states::REQUIRED)];
+ }
+
+ if ([attribute isEqualToString:@"AXInvalid"]) {
+ return [NSNumber numberWithBool:!!(proxy->State() & states::INVALID)];
+ }
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
+ return [self visibleCharacterRange];
+
+ // let mozAccessible handle all other attributes
+ return [super accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSArray*)accessibilityParameterizedAttributeNames
+{
+ static NSArray* supportedParametrizedAttributes = nil;
+ // text specific parametrized attributes
+ if (!supportedParametrizedAttributes) {
+ supportedParametrizedAttributes = [[NSArray alloc] initWithObjects:
+ NSAccessibilityStringForRangeParameterizedAttribute,
+ NSAccessibilityLineForIndexParameterizedAttribute,
+ NSAccessibilityRangeForLineParameterizedAttribute,
+ NSAccessibilityAttributedStringForRangeParameterizedAttribute,
+ NSAccessibilityBoundsForRangeParameterizedAttribute,
+#if DEBUG
+ NSAccessibilityRangeForPositionParameterizedAttribute,
+ NSAccessibilityRangeForIndexParameterizedAttribute,
+ NSAccessibilityRTFForRangeParameterizedAttribute,
+ NSAccessibilityStyleRangeForIndexParameterizedAttribute,
+#endif
+ nil
+ ];
+ }
+ return supportedParametrizedAttributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) {
+ NSRange range;
+ if (!ToNSRange(parameter, &range)) {
+#if DEBUG
+ NSLog(@"%@: range not set", attribute);
+#endif
+ return @"";
+ }
+
+ return [self stringFromRange:&range];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) {
+ // XXX: actually get the integer value for the line #
+ return [NSValue valueWithRange:NSMakeRange(0, [self textLength])];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityAttributedStringForRangeParameterizedAttribute]) {
+ NSRange range;
+ if (!ToNSRange(parameter, &range)) {
+#if DEBUG
+ NSLog(@"%@: range not set", attribute);
+#endif
+ return @"";
+ }
+
+ return [[[NSAttributedString alloc] initWithString:[self stringFromRange:&range]] autorelease];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) {
+ // XXX: actually return the line #
+ return [NSNumber numberWithInt:0];
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) {
+ NSRange range;
+ if (!ToNSRange(parameter, &range)) {
+#if DEBUG
+ NSLog(@"%@:no range", attribute);
+#endif
+ return nil;
+ }
+
+ int32_t start = range.location;
+ int32_t end = start + range.length;
+ DesktopIntRect bounds;
+ if (textAcc) {
+ bounds =
+ DesktopIntRect::FromUnknownRect(textAcc->TextBounds(start, end));
+ } else if (proxy) {
+ bounds =
+ DesktopIntRect::FromUnknownRect(proxy->TextBounds(start, end));
+ }
+
+ return [NSValue valueWithRect:nsCocoaUtils::GeckoRectToCocoaRect(bounds)];
+ }
+
+#if DEBUG
+ NSLog(@"unhandled attribute:%@ forParameter:%@", attribute, parameter);
+#endif
+
+ return nil;
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+ return ![self isReadOnly];
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] ||
+ [attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] ||
+ [attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute])
+ return YES;
+
+ return [super accessibilityIsAttributeSettable:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return;
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute]) {
+ [self setText:ToNSString(value)];
+
+ return;
+ }
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) {
+ NSString* stringValue = ToNSString(value);
+ if (!stringValue)
+ return;
+
+ int32_t start = 0, end = 0;
+ nsString text;
+ if (textAcc) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ textAcc->DeleteText(start, end - start);
+ nsCocoaUtils::GetStringForNSString(stringValue, text);
+ textAcc->InsertText(text, start);
+ } else if (proxy) {
+ nsString data;
+ proxy->SelectionBoundsAt(0, data, &start, &end);
+ proxy->DeleteText(start, end - start);
+ nsCocoaUtils::GetStringForNSString(stringValue, text);
+ proxy->InsertText(text, start);
+ }
+ }
+
+ if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) {
+ NSRange range;
+ if (!ToNSRange(value, &range))
+ return;
+
+ if (textAcc) {
+ textAcc->SetSelectionBoundsAt(0, range.location,
+ range.location + range.length);
+ } else if (proxy) {
+ proxy->SetSelectionBoundsAt(0, range.location,
+ range.location + range.length);
+ }
+ return;
+ }
+
+ if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) {
+ NSRange range;
+ if (!ToNSRange(value, &range))
+ return;
+
+ if (textAcc) {
+ textAcc->ScrollSubstringTo(range.location, range.location + range.length,
+ nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
+ } else if (proxy) {
+ proxy->ScrollSubstringTo(range.location, range.location + range.length,
+ nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
+ }
+ return;
+ }
+
+ [super accessibilitySetValue:value forAttribute:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)subrole
+{
+ if(mRole == roles::PASSWORD_TEXT)
+ return NSAccessibilitySecureTextFieldSubrole;
+
+ return nil;
+}
+
+#pragma mark -
+
+- (BOOL)isReadOnly
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([[self role] isEqualToString:NSAccessibilityStaticTextRole])
+ return YES;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (textAcc)
+ return (accWrap->State() & states::READONLY) == 0;
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible])
+ return (proxy->State() & states::READONLY) == 0;
+
+ return NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (NSNumber*)caretLineNumber
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+
+ int32_t lineNumber = -1;
+ if (textAcc) {
+ lineNumber = textAcc->CaretLineNumber() - 1;
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ lineNumber = proxy->CaretLineNumber() - 1;
+ }
+
+ return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil;
+}
+
+- (void)setText:(NSString*)aNewString
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+
+ nsString text;
+ nsCocoaUtils::GetStringForNSString(aNewString, text);
+ if (textAcc) {
+ textAcc->ReplaceText(text);
+ } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ proxy->ReplaceText(text);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)text
+{
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ // A password text field returns an empty value
+ if (mRole == roles::PASSWORD_TEXT)
+ return @"";
+
+ nsAutoString text;
+ if (textAcc) {
+ textAcc->TextSubstring(0, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, text);
+ } else if (proxy) {
+ proxy->TextSubstring(0, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, text);
+ }
+
+ return nsCocoaUtils::ToNSString(text);
+}
+
+- (long)textLength
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return 0;
+
+ return textAcc ? textAcc->CharacterCount() : proxy->CharacterCount();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (long)selectedTextLength
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return 0;
+
+ int32_t start = 0, end = 0;
+ if (textAcc) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ } else if (proxy) {
+ nsString data;
+ proxy->SelectionBoundsAt(0, data, &start, &end);
+ }
+ return (end - start);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (NSString*)selectedText
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ int32_t start = 0, end = 0;
+ nsAutoString selText;
+ if (textAcc) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ if (start != end) {
+ textAcc->TextSubstring(start, end, selText);
+ }
+ } else if (proxy) {
+ proxy->SelectionBoundsAt(0, selText, &start, &end);
+ }
+
+ return nsCocoaUtils::ToNSString(selText);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)selectedTextRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+
+ int32_t start = 0;
+ int32_t end = 0;
+ int32_t count = 0;
+ if (textAcc) {
+ count = textAcc->SelectionCount();
+ if (count) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ return [NSValue valueWithRange:NSMakeRange(start, end - start)];
+ }
+
+ start = textAcc->CaretOffset();
+ return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)];
+ }
+
+ if (proxy) {
+ count = proxy->SelectionCount();
+ if (count) {
+ nsString data;
+ proxy->SelectionBoundsAt(0, data, &start, &end);
+ return [NSValue valueWithRange:NSMakeRange(start, end - start)];
+ }
+
+ start = proxy->CaretOffset();
+ return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)];
+ }
+
+ return [NSValue valueWithRange:NSMakeRange(0, 0)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSValue*)visibleCharacterRange
+{
+ // XXX this won't work with Textarea and such as we actually don't give
+ // the visible character range.
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return 0;
+
+ return [NSValue valueWithRange:
+ NSMakeRange(0, textAcc ?
+ textAcc->CharacterCount() : proxy->CharacterCount())];
+}
+
+- (void)valueDidChange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
+ NSAccessibilityValueChangedNotification);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)selectedTextDidChange
+{
+ NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
+ NSAccessibilitySelectedTextChangedNotification);
+}
+
+- (NSString*)stringFromRange:(NSRange*)range
+{
+ NS_PRECONDITION(range, "no range");
+
+ AccessibleWrap* accWrap = [self getGeckoAccessible];
+ ProxyAccessible* proxy = [self getProxyAccessible];
+ HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr;
+ if (!textAcc && !proxy)
+ return nil;
+
+ nsAutoString text;
+ if (textAcc) {
+ textAcc->TextSubstring(range->location,
+ range->location + range->length, text);
+ } else if (proxy) {
+ proxy->TextSubstring(range->location,
+ range->location + range->length, text);
+ }
+
+ return nsCocoaUtils::ToNSString(text);
+}
+
+@end
+
+@implementation mozTextLeafAccessible
+
+- (NSArray*)accessibilityAttributeNames
+{
+ static NSMutableArray* supportedAttributes = nil;
+ if (!supportedAttributes) {
+ supportedAttributes = [[super accessibilityAttributeNames] mutableCopy];
+ [supportedAttributes removeObject:NSAccessibilityChildrenAttribute];
+ }
+
+ return supportedAttributes;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
+ return @"";
+
+ if ([attribute isEqualToString:NSAccessibilityValueAttribute])
+ return [self text];
+
+ return [super accessibilityAttributeValue:attribute];
+}
+
+- (NSString*)text
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ return nsCocoaUtils::ToNSString(accWrap->AsTextLeaf()->Text());
+ }
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ nsString text;
+ proxy->Text(&text);
+ return nsCocoaUtils::ToNSString(text);
+ }
+
+ return nil;
+}
+
+- (long)textLength
+{
+ if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
+ return accWrap->AsTextLeaf()->Text().Length();
+ }
+
+ if (ProxyAccessible* proxy = [self getProxyAccessible]) {
+ nsString text;
+ proxy->Text(&text);
+ return text.Length();
+ }
+
+ return 0;
+}
+
+@end
diff --git a/accessible/moz.build b/accessible/moz.build
index edfd88f504..aa1cccb982 100644
--- a/accessible/moz.build
+++ b/accessible/moz.build
@@ -9,6 +9,8 @@ if 'gtk' in toolkit:
DIRS += ['atk']
elif toolkit == 'windows':
DIRS += ['windows']
+elif toolkit == 'cocoa':
+ DIRS += ['mac']
else:
DIRS += ['other']
diff --git a/accessible/xpcom/moz.build b/accessible/xpcom/moz.build
index 5d1ad16884..d43efd06d0 100644
--- a/accessible/xpcom/moz.build
+++ b/accessible/xpcom/moz.build
@@ -42,6 +42,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
LOCAL_INCLUDES += [
'/accessible/windows/msaa',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
else:
LOCAL_INCLUDES += [
'/accessible/other',
diff --git a/accessible/xul/moz.build b/accessible/xul/moz.build
index b04c35dc40..54182c0975 100644
--- a/accessible/xul/moz.build
+++ b/accessible/xul/moz.build
@@ -37,6 +37,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'/accessible/windows/ia2',
'/accessible/windows/msaa',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/accessible/mac',
+ ]
else:
LOCAL_INCLUDES += [
'/accessible/other',
diff --git a/build/macosx/build-cctools.sh b/build/macosx/build-cctools.sh
new file mode 100755
index 0000000000..af0b36221b
--- /dev/null
+++ b/build/macosx/build-cctools.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+set -e
+
+if ! git remote -v | grep origin | grep -q cctools-port; then
+ echo "must be in a cctools-port checkout"
+ exit 1
+fi
+
+mkdir build-cctools
+cd build-cctools
+
+CFLAGS='-mcpu=generic -mtune=generic' MACOSX_DEPLOYMENT_TARGET=10.7 ../cctools/configure --target=x86_64-apple-darwin11
+env MACOSX_DEPLOYMENT_TARGET=10.7 make -s -j4
+
+if test ! -e ld64/src/ld/ld; then
+ echo "ld did not get built"
+ exit 1
+fi
+
+gtar jcf cctools.tar.bz2 ld64/src/ld/ld --transform 's#ld64/src/ld#cctools/bin#'
+
+cd ../
+
+echo "build from $(git show --pretty=format:%H -s HEAD) complete!"
+echo "upload the build-cctools/cctools.tar.bz2 file to tooltool"
diff --git a/build/macosx/cross-mozconfig.common b/build/macosx/cross-mozconfig.common
new file mode 100644
index 0000000000..8e56394d00
--- /dev/null
+++ b/build/macosx/cross-mozconfig.common
@@ -0,0 +1,47 @@
+# 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/.
+
+MOZ_AUTOMATION_L10N_CHECK=0
+
+if [ "x$IS_NIGHTLY" = "xyes" ]; then
+ # Some nightlies (eg: Mulet) don't want these set.
+ MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-1}
+ MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+fi
+. "$topsrcdir/build/mozconfig.common"
+
+# ld needs libLTO.so from llvm
+mk_add_options "export LD_LIBRARY_PATH=$topsrcdir/clang/lib"
+
+CROSS_CCTOOLS_PATH=$topsrcdir/cctools
+CROSS_SYSROOT=$topsrcdir/MacOSX10.7.sdk
+CROSS_PRIVATE_FRAMEWORKS=$CROSS_SYSROOT/System/Library/PrivateFrameworks
+FLAGS="-target x86_64-apple-darwin10 -mlinker-version=136 -B $CROSS_CCTOOLS_PATH/bin -isysroot $CROSS_SYSROOT"
+
+export CC="$topsrcdir/clang/bin/clang $FLAGS"
+export CXX="$topsrcdir/clang/bin/clang++ $FLAGS"
+export CPP="$topsrcdir/clang/bin/clang $FLAGS -E"
+export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
+export LDFLAGS="-Wl,-syslibroot,$CROSS_SYSROOT -Wl,-dead_strip"
+export TOOLCHAIN_PREFIX=$CROSS_CCTOOLS_PATH/bin/x86_64-apple-darwin10-
+export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
+export GENISOIMAGE=$topsrcdir/genisoimage/genisoimage
+export DMG_TOOL=$topsrcdir/dmg/dmg
+
+export HOST_CC="$topsrcdir/clang/bin/clang"
+export HOST_CXX="$topsrcdir/clang/bin/clang++"
+export HOST_CPP="$topsrcdir/clang/bin/clang -E"
+export HOST_CFLAGS="-g"
+export HOST_CXXFLAGS="-g"
+export HOST_LDFLAGS="-g"
+
+ac_add_options --target=x86_64-apple-darwin
+ac_add_options --with-macos-private-frameworks=$CROSS_PRIVATE_FRAMEWORKS
+
+# Enable static analysis checks by default on OSX cross builds.
+ac_add_options --enable-clang-plugin
+
+. "$topsrcdir/build/mozconfig.cache"
+
+export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token
diff --git a/build/macosx/local-mozconfig.common b/build/macosx/local-mozconfig.common
new file mode 100644
index 0000000000..02a09d2fe0
--- /dev/null
+++ b/build/macosx/local-mozconfig.common
@@ -0,0 +1,46 @@
+# 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/.
+
+if [ "x$IS_NIGHTLY" = "xyes" ]; then
+ # Some nightlies (eg: Mulet) don't want these set.
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=${MOZ_AUTOMATION_UPLOAD_SYMBOLS-1}
+ MOZ_AUTOMATION_UPDATE_PACKAGING=${MOZ_AUTOMATION_UPDATE_PACKAGING-1}
+ MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+fi
+. "$topsrcdir/build/mozconfig.common"
+
+if [ -d "$topsrcdir/clang" ]; then
+ # mozilla-central based build
+ export CC=$topsrcdir/clang/bin/clang
+ export CXX=$topsrcdir/clang/bin/clang++
+ export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
+ export DSYMUTIL=$topsrcdir/clang/bin/llvm-dsymutil
+ # Use an updated linker.
+ ldflags="-B$topsrcdir/cctools/bin"
+elif [ -d "$topsrcdir/../clang" ]; then
+ # comm-central based build
+ export CC=$topsrcdir/../clang/bin/clang
+ export CXX=$topsrcdir/../clang/bin/clang++
+ export LLVMCONFIG=$topsrcdir/../clang/bin/llvm-config
+ export DSYMUTIL=$topsrcdir/../clang/bin/llvm-dsymutil
+ # Use an updated linker.
+ ldflags="-B$topsrcdir/../cctools/bin"
+fi
+
+# Ensure the updated linker doesn't generate things our older build tools
+# don't understand.
+ldflags="$ldflags -Wl,-no_data_in_code_info"
+export LDFLAGS="$ldflags"
+
+# If not set use the system default clang
+if [ -z "$CC" ]; then
+ export CC=clang
+fi
+
+# If not set use the system default clang++
+if [ -z "$CXX" ]; then
+ export CXX=clang++
+fi
+
+export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token
diff --git a/build/macosx/mozconfig.common b/build/macosx/mozconfig.common
new file mode 100644
index 0000000000..27634b7f31
--- /dev/null
+++ b/build/macosx/mozconfig.common
@@ -0,0 +1,5 @@
+if test `uname -s` = Linux; then
+ . $topsrcdir/build/macosx/cross-mozconfig.common
+else
+ . $topsrcdir/build/macosx/local-mozconfig.common
+fi
diff --git a/build/macosx/permissions/chown_revert.c b/build/macosx/permissions/chown_revert.c
new file mode 100644
index 0000000000..2cf3e37c3f
--- /dev/null
+++ b/build/macosx/permissions/chown_revert.c
@@ -0,0 +1,20 @@
+/* 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 <unistd.h>
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ if (argc != 2)
+ return 1;
+
+ uid_t realuser = getuid();
+ char uidstring[20];
+ snprintf(uidstring, 19, "%i", realuser);
+ uidstring[19] = '\0';
+
+ return execl("/usr/sbin/chown",
+ "/usr/sbin/chown", "-R", "-h", uidstring, argv[1], (char*) 0);
+}
diff --git a/build/macosx/permissions/chown_root.c b/build/macosx/permissions/chown_root.c
new file mode 100644
index 0000000000..c9b13a5308
--- /dev/null
+++ b/build/macosx/permissions/chown_root.c
@@ -0,0 +1,14 @@
+/* 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 <unistd.h>
+
+int main(int argc, char **argv)
+{
+ if (argc != 2)
+ return 1;
+
+ return execl("/usr/sbin/chown",
+ "/usr/sbin/chown", "-R", "-h", "root:admin", argv[1], (char*) 0);
+}
diff --git a/build/macosx/universal/mozconfig b/build/macosx/universal/mozconfig
new file mode 100644
index 0000000000..32ab66f2df
--- /dev/null
+++ b/build/macosx/universal/mozconfig
@@ -0,0 +1,11 @@
+# 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/.
+
+# i386/x86-64 Universal Build mozconfig
+
+# As used here, arguments in $MOZ_BUILD_PROJECTS are suitable as arguments
+# to gcc's -arch parameter.
+mk_add_options MOZ_BUILD_PROJECTS="x86_64 i386"
+
+. $topsrcdir/build/macosx/universal/mozconfig.common
diff --git a/build/macosx/universal/mozconfig.common b/build/macosx/universal/mozconfig.common
new file mode 100644
index 0000000000..bb54bc6c43
--- /dev/null
+++ b/build/macosx/universal/mozconfig.common
@@ -0,0 +1,55 @@
+# 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/.
+
+mk_add_options MOZ_UNIFY_BDATE=1
+
+DARWIN_VERSION=10
+ac_add_app_options i386 --target=i386-apple-darwin$DARWIN_VERSION
+ac_add_app_options x86_64 --target=x86_64-apple-darwin$DARWIN_VERSION
+ac_add_app_options i386 --with-unify-dist=../x86_64/dist
+ac_add_app_options x86_64 --with-unify-dist=../i386/dist
+
+if ! test `uname -s` = Linux; then
+ # Cross-universal builds already do the equivalent of this by setting -isysroot directly
+ ac_add_options --with-macos-sdk=/Developer/SDKs/MacOSX10.7.sdk
+fi
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+# $MOZ_BUILD_APP is only defined when sourced by configure. That's not a
+# problem, because the variables it affects only need to be set for
+# configure.
+if test -n "$MOZ_BUILD_APP" ; then
+if test "$MOZ_BUILD_APP" = "i386" -o "$MOZ_BUILD_APP" = "x86_64"; then
+ TARGET_CPU=$MOZ_BUILD_APP
+
+ # $HOST_CXX is presently unused. $HOST_CC will only be used during a cross
+ # compile.
+ HOST_CC=$CC
+ HOST_CXX=$CXX
+
+ NATIVE_CPU=`$topsrcdir/build/autoconf/config.guess | cut -f1 -d-`
+
+ # It's not strictly necessary to specify -arch during native builds, but it
+ # makes the merged about:buildconfig easier to follow, and it reduces
+ # conditionalized differences between builds.
+ CC="$CC -arch $TARGET_CPU"
+ CXX="$CXX -arch $TARGET_CPU"
+
+ # These must be set for cross builds, and don't hurt straight builds.
+ RANLIB="${TOOLCHAIN_PREFIX}ranlib"
+ AR="${TOOLCHAIN_PREFIX}ar"
+ AS=$CC
+ LD=ld
+ STRIP="${TOOLCHAIN_PREFIX}strip"
+ OTOOL="${TOOLCHAIN_PREFIX}otool"
+
+ # Each per-CPU build should be entirely oblivious to the fact that a
+ # universal binary will be produced. The exception is packager.mk, which
+ # needs to know to look for universal bits when building the .dmg.
+ UNIVERSAL_BINARY=1
+
+ export CC CXX HOST_CC HOST_CXX RANLIB AR AS LD STRIP OTOOL
+fi
+fi
diff --git a/build/macosx/universal/unify b/build/macosx/universal/unify
new file mode 100755
index 0000000000..38dd354145
--- /dev/null
+++ b/build/macosx/universal/unify
@@ -0,0 +1,1525 @@
+#!/usr/bin/perl
+# 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/.
+
+use strict;
+use warnings;
+
+=pod
+
+=head1 NAME
+
+B<unify> - Mac OS X universal binary packager
+
+=head1 SYNOPSIS
+
+B<unify>
+I<ppc-path>
+I<x86-path>
+I<universal-path>
+[B<--dry-run>]
+[B<--only-one> I<action>]
+[B<--verbosity> I<level>]
+[B<--unify-with-sort> I<regex>]
+
+=head1 DESCRIPTION
+
+I<unify> merges any two architecture-specific files or directory trees
+into a single file or tree suitable for use on either architecture as a
+"fat" or "universal binary."
+
+Architecture-specific Mach-O files will be merged into fat Mach-O files
+using L<lipo(1)>. Non-Mach-O files in the architecture-specific trees
+are compared to ensure that they are equivalent before copying. Symbolic
+links are permitted in the architecture-specific trees and will cause
+identical links to be created in the merged tree, provided that the source
+links have identical targets. Directories are processed recursively.
+
+If the architecture-specific source trees contain zip archives (including
+jar files) that are not identical according to a byte-for-byte check, they
+are still assumed to be equivalent if both archives contain exactly the
+same members with identical checksums and sizes.
+
+Behavior when one architecture-specific tree contains files that the other
+does not is controlled by the B<--only-one> option.
+
+If Mach-O files cannot be merged using L<lipo(1)>, zip archives are not
+equivalent, regular files are not identical, or any other error occurs,
+B<unify> will fail with an exit status of 1. Diagnostic messages are
+typically printed to stderr; this behavior can be controlled with the
+B<--verbosity> option.
+
+=head1 OPTIONS
+
+=over 5
+
+=item I<ppc-path>
+
+=item I<x86-path>
+
+The paths to directory trees containing PowerPC and x86 builds,
+respectively. I<ppc-path> and I<x86-path> are permitted to contain files
+that are already "fat," and only the appropriate architecture's images will
+be used.
+
+I<ppc-path> and I<x86-path> are also permitted to both be files, in which
+case B<unify> operates solely on those files, and produces an appropriate
+merged file at I<target-path>.
+
+=item I<target-path>
+
+The path to the merged file or directory tree. This path will be created,
+and it must not exist prior to running B<unify>.
+
+=item B<--dry-run>
+
+When specified, the commands that would be executed are printed, without
+actually executing them. Note that B<--dry-run> and the equivalent
+B<--verbosity> level during "wet" runs may print equivalent commands when
+no commands are in fact executed: certain operations are handled internally
+within B<unify>, and an approximation of a command that performs a similar
+task is printed.
+
+=item B<--only-one> I<action>
+
+Controls handling of files that are only present in one of the two source
+trees. I<action> may be:
+ skip - These files are skipped.
+ copy - These files are copied from the tree in which they exist.
+ fail - When this condition occurs, it is treated as an error.
+
+The default I<action> is copy.
+
+=item B<--verbosity> I<level>
+
+Adjusts the level of loudness of B<unify>. The possible values for
+I<level> are:
+ 0 - B<unify> never prints anything.
+ (Other programs that B<unify> calls may still print messages.)
+ 1 - Fatal error messages are printed to stderr.
+ 2 - Nonfatal warnings are printed to stderr.
+ 3 - Commands are printed to stdout as they are executed.
+
+The default I<level> is 2.
+
+=item B<--unify-with-sort> I<regex>
+
+Allows merging files matching I<regex> that differ only by the ordering
+of the lines contained within them. The unified file will have its contents
+sorted. This option may be given multiple times to specify multiple
+regexes for matching files.
+
+=back
+
+=head1 EXAMPLES
+
+=over 5
+
+=item Create a universal .app bundle from two architecture-specific .app
+bundles:
+
+unify --only-one copy ppc/dist/firefox/Firefox.app
+ x86/dist/firefox/Firefox.app universal/Firefox.app
+ --verbosity 3
+
+=item Merge two identical architecture-specific trees:
+
+unify --only-one fail /usr/local /nfs/x86/usr/local
+ /tmp/usrlocal.fat
+
+=back
+
+=head1 REQUIREMENTS
+
+The only esoteric requirement of B<unify> is that the L<lipo(1)> command
+be available. It is present on Mac OS X systems at least as early as
+10.3.9, and probably earlier. Mac OS X 10.4 ("Tiger") or later are
+recommended.
+
+=head1 LICENSE
+
+MPL 2.
+
+=head1 AUTHOR
+
+The software was initially written by Mark Mentovai; copyright 2006
+Google Inc.
+
+=head1 SEE ALSO
+
+L<cmp(1)>, L<ditto(1)>, L<lipo(1)>
+
+=cut
+
+use Archive::Zip(':ERROR_CODES');
+use Errno;
+use Fcntl;
+use File::Compare;
+use File::Copy;
+use Getopt::Long;
+
+my (%gConfig, $gDryRun, $gOnlyOne, $gVerbosity, @gSortMatches);
+
+sub argumentEscape(@);
+sub command(@);
+sub compareZipArchives($$);
+sub complain($$@);
+sub copyIfIdentical($$$);
+sub slurp($);
+sub get_sorted($);
+sub compare_sorted($$);
+sub copyIfIdenticalWhenSorted($$$);
+sub createUniqueFile($$);
+sub makeUniversal($$$);
+sub makeUniversalDirectory($$$);
+sub makeUniversalInternal($$$$);
+sub makeUniversalFile($$$);
+sub usage();
+sub readZipCRCs($);
+
+{
+ package FileAttrCache;
+
+ sub new($$);
+
+ sub isFat($);
+ sub isMachO($);
+ sub isZip($);
+ sub lIsDir($);
+ sub lIsExecutable($);
+ sub lIsRegularFile($);
+ sub lIsSymLink($);
+ sub lstat($);
+ sub lstatMode($);
+ sub lstatType($);
+ sub magic($);
+ sub magic2($);
+ sub path($);
+ sub stat($);
+ sub statSize($);
+}
+
+%gConfig = (
+ 'cmd_lipo' => 'lipo',
+ 'cmd_rm' => 'rm',
+);
+
+$gDryRun = 0;
+$gOnlyOne = 'copy';
+$gVerbosity = 2;
+@gSortMatches = ();
+
+Getopt::Long::Configure('pass_through');
+GetOptions('dry-run' => \$gDryRun,
+ 'only-one=s' => \$gOnlyOne,
+ 'verbosity=i' => \$gVerbosity,
+ 'unify-with-sort=s' => \@gSortMatches,
+ 'config=s' => \%gConfig); # "hidden" option not in usage()
+
+if (scalar(@ARGV) != 3 || $gVerbosity < 0 || $gVerbosity > 3 ||
+ ($gOnlyOne ne 'skip' && $gOnlyOne ne 'copy' && $gOnlyOne ne 'fail')) {
+ usage();
+ exit(1);
+}
+
+if (!makeUniversal($ARGV[0],$ARGV[1],$ARGV[2])) {
+ # makeUniversal or something it called will have printed an error.
+ exit(1);
+}
+
+exit(0);
+
+# argumentEscape(@arguments)
+#
+# Takes a list of @arguments and makes them shell-safe.
+sub argumentEscape(@) {
+ my (@arguments);
+ @arguments = @_;
+
+ my ($argument, @argumentsOut);
+ foreach $argument (@arguments) {
+ $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g;
+ push(@argumentsOut, $argument);
+ }
+
+ return @argumentsOut;
+}
+
+# command(@arguments)
+#
+# Runs the specified command by calling system(@arguments). If $gDryRun
+# is true, the command is printed but not executed, and 0 is returned.
+# if $gVerbosity is greater than 1, the command is printed before being
+# executed. When the command is executed, the system() return value will
+# be returned. stdout and stderr are left connected for command output.
+sub command(@) {
+ my (@arguments);
+ @arguments = @_;
+ if ($gVerbosity >= 3 || $gDryRun) {
+ print(join(' ', argumentEscape(@arguments))."\n");
+ }
+ if ($gDryRun) {
+ return 0;
+ }
+ return system(@arguments);
+}
+
+# compareZipArchives($zip1, $zip2)
+#
+# Given two pathnames to zip archives, determines whether or not they are
+# functionally identical. Returns true if they are, false if they differ in
+# some substantial way, and undef if an error occurs. If the zip files
+# differ, diagnostic messages are printed indicating how they differ.
+#
+# Zip files will differ if any of the members are different as defined by
+# readZipCRCs, which consider CRCs, sizes, and file types as stored in the
+# file header. Timestamps are not considered. Zip files also differ if one
+# file contains members that the other one does not. $gOnlyOne has no
+# effect on this behavior.
+sub compareZipArchives($$) {
+ my ($zip1, $zip2);
+ ($zip1, $zip2) = @_;
+
+ my ($CRCHash1, $CRCHash2);
+ if (!defined($CRCHash1 = readZipCRCs($zip1))) {
+ # readZipCRCs printed an error.
+ return undef;
+ }
+ if (!defined($CRCHash2 = readZipCRCs($zip2))) {
+ # readZipCRCs printed an error.
+ return undef;
+ }
+
+ my (@diffCRCs, @onlyInZip1);
+ @diffCRCs = ();
+ @onlyInZip1 = ();
+
+ my ($memberName);
+ foreach $memberName (keys(%$CRCHash1)) {
+ if (!exists($$CRCHash2{$memberName})) {
+ # The member is present in $zip1 but not $zip2.
+ push(@onlyInZip1, $memberName);
+ }
+ elsif ($$CRCHash1{$memberName} ne $$CRCHash2{$memberName}) {
+ # The member is present in both archives but its CRC or some other
+ # other critical attribute isn't identical.
+ push(@diffCRCs, $memberName);
+ }
+ delete($$CRCHash2{$memberName});
+ }
+
+ # If any members remain in %CRCHash2, it's because they're not present
+ # in $zip1.
+ my (@onlyInZip2);
+ @onlyInZip2 = keys(%$CRCHash2);
+
+ if (scalar(@onlyInZip1) + scalar(@onlyInZip2) + scalar(@diffCRCs)) {
+ complain(1, 'compareZipArchives: zip archives differ:',
+ $zip1,
+ $zip2);
+ if (scalar(@onlyInZip1)) {
+ complain(1, 'compareZipArchives: members only in former:',
+ @onlyInZip1);
+ }
+ if (scalar(@onlyInZip2)) {
+ complain(1, 'compareZipArchives: members only in latter:',
+ @onlyInZip2);
+ }
+ if (scalar(@diffCRCs)) {
+ complain(1, 'compareZipArchives: members differ:',
+ @diffCRCs);
+ }
+ return 0;
+ }
+
+ return 1;
+}
+
+# complain($severity, $message, @list)
+#
+# Prints $message to stderr if $gVerbosity allows it for severity level
+# $severity. @list is a list of words that will be shell-escaped and printed
+# after $message, one per line, intended to be used, for example, to list
+# arguments to a call that failed.
+#
+# Expected severity levels are 1 for hard errors and 2 for non-fatal warnings.
+#
+# Always returns false as a convenience, so callers can return complain's
+# return value when it is used to signal errors.
+sub complain($$@) {
+ my ($severity, $message, @list);
+ ($severity, $message, @list) = @_;
+
+ if ($gVerbosity >= $severity) {
+ print STDERR ($0.': '.$message."\n");
+
+ my ($item);
+ while ($item = shift(@list)) {
+ print STDERR (' '.(argumentEscape($item))[0].
+ (scalar(@list)?',':'')."\n");
+ }
+ }
+
+ return 0;
+}
+
+# copyIfIdentical($source1, $source2, $target)
+#
+# $source1 and $source2 are FileAttrCache objects that are compared, and if
+# identical, copied to path string $target. The comparison is initially
+# done as a byte-for-byte comparison, but if the files differ and appear to
+# be zip archives, compareZipArchives is called to determine whether
+# files that are not byte-for-byte identical are equivalent archives.
+#
+# Returns true on success, false for files that are not identical or
+# equivalent archives, and undef if an error occurs.
+#
+# One of $source1 and $source2 is permitted to be undef. In this event,
+# whichever source is defined is copied directly to $target without performing
+# any comparisons. This enables the $gOnlyOne = 'copy' mode, which is
+# driven by makeUniversalDirectory and makeUniversalInternal.
+sub copyIfIdentical($$$) {
+ my ($source1, $source2, $target);
+ ($source1, $source2, $target) = @_;
+
+ if (!defined($source1)) {
+ # If there's only one source file, make it the first file. Order
+ # isn't important here, and this makes it possible to use
+ # defined($source2) as the switch, and to always copy from $source1.
+ $source1 = $source2;
+ $source2 = undef;
+ }
+
+ if (defined($source2)) {
+ # Only do the comparisons if there are two source files. If there's
+ # only one source file, skip the comparisons and go straight to the
+ # copy operation.
+ if ($gVerbosity >= 3 || $gDryRun) {
+ print('cmp -s '.
+ join(' ',argumentEscape($source1->path(), $source2->path()))."\n");
+ }
+ my ($comparison);
+ if (!defined($comparison = compare($source1->path(), $source2->path())) ||
+ $comparison == -1) {
+ return complain(1, 'copyIfIdentical: compare: '.$!.' while comparing:',
+ $source1->path(),
+ $source2->path());
+ }
+ elsif ($comparison != 0) {
+ my ($zip1, $zip2);
+ if (defined($zip1 = $source1->isZip()) &&
+ defined($zip2 = $source2->isZip()) &&
+ $zip1 && $zip2) {
+ my ($zipComparison);
+ if (!defined($zipComparison = compareZipArchives($source1->path(),
+ $source2->path)) ||
+ !$zipComparison) {
+ # An error occurred or the zip files aren't sufficiently identical.
+ # compareZipArchives will have printed an error message.
+ return 0;
+ }
+ # The zip files were compared successfully, and they both contain
+ # all of the same members, and all of their members' CRCs are
+ # identical. For the purposes of this script, the zip files can be
+ # treated as identical, so reset $comparison.
+ $comparison = 0;
+ }
+ }
+ if ($comparison != 0) {
+ return complain(1, 'copyIfIdentical: files differ:',
+ $source1->path(),
+ $source2->path());
+ }
+ }
+
+ if ($gVerbosity >= 3 || $gDryRun) {
+ print('cp '.
+ join(' ',argumentEscape($source1->path(), $target))."\n");
+ }
+
+ if (!$gDryRun) {
+ my ($isExecutable);
+
+ # Set the execute bits (as allowed by the umask) on the new file if any
+ # execute bit is set on either old file.
+ $isExecutable = $source1->lIsExecutable() ||
+ (defined($source2) && $source2->lIsExecutable());
+
+ if (!createUniqueFile($target, $isExecutable ? 0777 : 0666)) {
+ # createUniqueFile printed an error.
+ return 0;
+ }
+
+ if (!copy($source1->path(), $target)) {
+ complain(1, 'copyIfIdentical: copy: '.$!.' while copying',
+ $source1->path(),
+ $target);
+ unlink($target);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+# slurp($file)
+#
+# Read the contents of $file into an array and return it.
+# Returns undef on error.
+sub slurp($) {
+ my $file = $_[0];
+ open FILE, $file or return undef;
+ my @lines = <FILE>;
+ close FILE;
+ return @lines;
+}
+
+# get_sorted($file)
+# Get the sorted lines of a file as a list, normalizing a newline on the last line if necessary.
+sub get_sorted($) {
+ my ($file) = @_;
+ my @lines = slurp($file);
+ my $lastline = $lines[-1];
+ if (!($lastline =~ /\n/)) {
+ $lines[-1] = $lastline . "\n";
+ }
+ return sort(@lines);
+}
+
+# compare_sorted($file1, $file2)
+#
+# Read the contents of both files into arrays, sort the arrays,
+# and then compare the two arrays for equality.
+#
+# Returns 0 if the sorted array contents are equal, or 1 if not.
+# Returns undef on error.
+sub compare_sorted($$) {
+ my ($file1, $file2) = @_;
+ my @lines1 = get_sorted($file1);
+ my @lines2 = get_sorted($file2);
+
+ return undef if !@lines1 || !@lines2;
+ return 1 unless scalar @lines1 == scalar @lines2;
+
+ for (my $i = 0; $i < scalar @lines1; $i++) {
+ return 1 if $lines1[$i] ne $lines2[$i];
+ }
+ return 0;
+}
+
+# copyIfIdenticalWhenSorted($source1, $source2, $target)
+#
+# $source1 and $source2 are FileAttrCache objects that are compared, and if
+# identical, copied to path string $target. The comparison is done by
+# sorting the individual lines within the two files and comparing the results.
+#
+# Returns true on success, false for files that are not equivalent,
+# and undef if an error occurs.
+sub copyIfIdenticalWhenSorted($$$) {
+ my ($source1, $source2, $target);
+ ($source1, $source2, $target) = @_;
+
+ if ($gVerbosity >= 3 || $gDryRun) {
+ print('cmp -s '.
+ join(' ',argumentEscape($source1->path(), $source2->path()))."\n");
+ }
+ my ($comparison);
+ if (!defined($comparison = compare_sorted($source1->path(),
+ $source2->path())) ||
+ $comparison == -1) {
+ return complain(1, 'copyIfIdenticalWhenSorted: compare: '.$!
+ .' while comparing:',
+ $source1->path(),
+ $source2->path());
+ }
+ if ($comparison != 0) {
+ return complain(1, 'copyIfIdenticalWhenSorted: files differ:',
+ $source1->path(),
+ $source2->path());
+ }
+
+ if ($gVerbosity >= 3 || $gDryRun) {
+ print('cp '.
+ join(' ',argumentEscape($source1->path(), $target))."\n");
+ }
+
+ if (!$gDryRun) {
+ my ($isExecutable);
+
+ # Set the execute bits (as allowed by the umask) on the new file if any
+ # execute bit is set on either old file.
+ $isExecutable = $source1->lIsExecutable() ||
+ (defined($source2) && $source2->lIsExecutable());
+
+ if (!createUniqueFile($target, $isExecutable ? 0777 : 0666)) {
+ # createUniqueFile printed an error.
+ return 0;
+ }
+
+ if (!copy($source1->path(), $target)) {
+ complain(1, 'copyIfIdenticalWhenSorted: copy: '.$!
+ .' while copying',
+ $source1->path(),
+ $target);
+ unlink($target);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+# createUniqueFile($path, $mode)
+#
+# Creates a new plain empty file at pathname $path, provided it does not
+# yet exist. $mode is used as the file mode. The actual file's mode will
+# be modified by the effective umask. Returns false if the file could
+# not be created, setting $! to the error. An error message is printed
+# in the event of failure.
+sub createUniqueFile($$) {
+ my ($path, $mode);
+ ($path, $mode) = @_;
+
+ my ($fh);
+ if (!sysopen($fh, $path, O_WRONLY | O_CREAT | O_EXCL, $mode)) {
+ return complain(1, 'createUniqueFile: open: '.$!.' for:',
+ $path);
+ }
+ close($fh);
+
+ return 1;
+}
+
+# makeUniversal($pathPPC, $pathX86, $pathTarget)
+#
+# The top-level call. $pathPPC, $pathX86, and $pathTarget are strings
+# identifying the ppc and x86 files or directories to merge and the location
+# to merge them to. Returns false on failure and true on success.
+sub makeUniversal($$$) {
+ my ($pathTarget, $pathPPC, $pathX86);
+ ($pathPPC, $pathX86, $pathTarget) = @_;
+
+ my ($filePPC, $fileX86);
+ $filePPC = FileAttrCache->new($pathPPC);
+ $fileX86 = FileAttrCache->new($pathX86);
+
+ return makeUniversalInternal(1, $filePPC, $fileX86, $pathTarget);
+}
+
+# makeUniversalDirectory($dirPPC, $dirX86, $dirTarget)
+#
+# This is part of the heart of recursion. $dirPPC and $dirX86 are
+# FileAttrCache objects designating the source ppc and x86 directories to
+# merge into a universal directory at $dirTarget, a string. For each file
+# in $dirPPC and $dirX86, makeUniversalInternal is called.
+# makeUniversalInternal will call back into makeUniversalDirectory for
+# directories, thus completing the recursion. If a failure is encountered
+# in ths function or in makeUniversalInternal or anything that it calls,
+# false is returned, otherwise, true is returned.
+#
+# If there are files present in one source directory but not both, the
+# value of $gOnlyOne controls the behavior. If $gOnlyOne is 'copy', the
+# single source file is copied into $pathTarget. If it is 'skip', it is
+# skipped. If it is 'fail', such files will trigger makeUniversalDirectory
+# to fail.
+#
+# If either source directory is undef, it is treated as having no files.
+# This facilitates deep recursion when entire directories are only present
+# in one source when $gOnlyOne = 'copy'.
+sub makeUniversalDirectory($$$) {
+ my ($dirPPC, $dirX86, $dirTarget);
+ ($dirPPC, $dirX86, $dirTarget) = @_;
+
+ my ($dh, @filesPPC, @filesX86);
+
+ @filesPPC = ();
+ if (defined($dirPPC)) {
+ if (!opendir($dh, $dirPPC->path())) {
+ return complain(1, 'makeUniversalDirectory: opendir ppc: '.$!.' for:',
+ $dirPPC->path());
+ }
+ @filesPPC = readdir($dh);
+ closedir($dh);
+ }
+
+ @filesX86 = ();
+ if (defined($dirX86)) {
+ if (!opendir($dh, $dirX86->path())) {
+ return complain(1, 'makeUniversalDirectory: opendir x86: '.$!.' for:',
+ $dirX86->path());
+ }
+ @filesX86 = readdir($dh);
+ closedir($dh);
+ }
+
+ my (%common, $file, %onlyPPC, %onlyX86);
+
+ %onlyPPC = ();
+ foreach $file (@filesPPC) {
+ if ($file eq '.' || $file eq '..') {
+ next;
+ }
+ $onlyPPC{$file}=1;
+ }
+
+ %common = ();
+ %onlyX86 = ();
+ foreach $file (@filesX86) {
+ if ($file eq '.' || $file eq '..') {
+ next;
+ }
+ if ($onlyPPC{$file}) {
+ delete $onlyPPC{$file};
+ $common{$file}=1;
+ }
+ else {
+ $onlyX86{$file}=1;
+ }
+ }
+
+ # First, handle files common to both.
+ foreach $file (sort(keys(%common))) {
+ if (!makeUniversalInternal(0,
+ FileAttrCache->new($dirPPC->path().'/'.$file),
+ FileAttrCache->new($dirX86->path().'/'.$file),
+ $dirTarget.'/'.$file)) {
+ # makeUniversalInternal will have printed an error.
+ return 0;
+ }
+ }
+
+ # Handle files found only in a single directory here. There are three
+ # options, dictated by $gOnlyOne: fail if files are only present in
+ # one directory, skip any files only present in one directory, or copy
+ # these files straight over to the target directory. In any event,
+ # a message will be printed indicating that the file trees don't match
+ # exactly.
+ if (keys(%onlyPPC)) {
+ complain(($gOnlyOne eq 'fail' ? 1 : 2),
+ ($gOnlyOne ne 'fail' ? 'warning: ' : '').
+ 'makeUniversalDirectory: only in ppc '.
+ (argumentEscape($dirPPC->path()))[0].':',
+ argumentEscape(keys(%onlyPPC)));
+ }
+
+ if (keys(%onlyX86)) {
+ complain(($gOnlyOne eq 'fail' ? 1 : 2),
+ ($gOnlyOne ne 'fail' ? 'warning: ' : '').
+ 'makeUniversalDirectory: only in x86 '.
+ (argumentEscape($dirX86->path()))[0].':',
+ argumentEscape(keys(%onlyX86)));
+ }
+
+ if ($gOnlyOne eq 'fail' && (keys(%onlyPPC) || keys(%onlyX86))) {
+ # Error message(s) printed above.
+ return 0;
+ }
+
+ if ($gOnlyOne eq 'copy') {
+ foreach $file (sort(keys(%onlyPPC))) {
+ if (!makeUniversalInternal(0,
+ FileAttrCache->new($dirPPC->path().'/'.$file),
+ undef,
+ $dirTarget.'/'.$file)) {
+ # makeUniversalInternal will have printed an error.
+ return 0;
+ }
+ }
+
+ foreach $file (sort(keys(%onlyX86))) {
+ if (!makeUniversalInternal(0,
+ undef,
+ FileAttrCache->new($dirX86->path().'/'.$file),
+ $dirTarget.'/'.$file)) {
+ # makeUniversalInternal will have printed an error.
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+# makeUniversalFile($sourcePPC, $sourceX86, $targetPath)
+#
+# Creates a universal file at pathname $targetPath based on a ppc image at
+# $sourcePPC and an x86 image at $sourceX86. $sourcePPC and $sourceX86 are
+# both FileAttrCache objects. Returns true on success and false on failure.
+# On failure, diagnostics will be printed to stderr.
+#
+# The source files may be either thin Mach-O images of the appropriate
+# architecture, or fat Mach-O files that contain images of the appropriate
+# architecture.
+#
+# This function wraps the lipo utility, see lipo(1).
+sub makeUniversalFile($$$) {
+ my ($sourcePPC, $sourceX86, $targetPath, @tempThinFiles, $thinPPC, $thinX86);
+ ($sourcePPC, $sourceX86, $targetPath) = @_;
+ $thinPPC = $sourcePPC;
+ $thinX86 = $sourceX86;
+
+ @tempThinFiles = ();
+
+ # The source files might already be fat. They should be thinned out to only
+ # contain a single architecture.
+
+ my ($isFatPPC, $isFatX86);
+
+ if(!defined($isFatPPC = $sourcePPC->isFat())) {
+ # isFat printed its own error
+ return 0;
+ }
+ elsif($isFatPPC) {
+ $thinPPC = FileAttrCache->new($targetPath.'.ppc');
+ push(@tempThinFiles, $thinPPC->path());
+ if (command($gConfig{'cmd_lipo'}, '-thin', 'ppc',
+ $sourcePPC->path(), '-output', $thinPPC->path()) != 0) {
+ unlink(@tempThinFiles);
+ return complain(1, 'lipo thin ppc failed for:',
+ $sourcePPC->path(),
+ $thinPPC->path());
+ }
+ }
+
+ if(!defined($isFatX86 = $sourceX86->isFat())) {
+ # isFat printed its own error
+ unlink(@tempThinFiles);
+ return 0;
+ }
+ elsif($isFatX86) {
+ $thinX86 = FileAttrCache->new($targetPath.'.x86');
+ push(@tempThinFiles, $thinX86->path());
+ if (command($gConfig{'cmd_lipo'}, '-thin', 'i386',
+ $sourceX86->path(), '-output', $thinX86->path()) != 0) {
+ unlink(@tempThinFiles);
+ return complain(1, 'lipo thin x86 failed for:',
+ $sourceX86->path(),
+ $thinX86->path());
+ }
+ }
+
+ # The image for each architecture in the fat file will be aligned on
+ # a specific boundary, default 4096 bytes, see lipo(1) -segalign.
+ # Since there's no tail-padding, the fat file will consume the least
+ # space on disk if the image that comes last exceeds the segment size
+ # by the smallest amount.
+ #
+ # This saves an average of 1kB per fat file over the naive approach of
+ # always putting one architecture first: average savings is 2kB per
+ # file, but the naive approach would have gotten it right half of the
+ # time.
+
+ my ($sizePPC, $sizeX86, $thinPPCForStat, $thinX86ForStat);
+
+ if (!$gDryRun) {
+ $thinPPCForStat = $thinPPC;
+ $thinX86ForStat = $thinX86;
+ }
+ else {
+ # Normally, fat source files will have been converted into temporary
+ # thin files. During a dry run, that doesn't happen, so fake it up
+ # a little bit by always using the source file, fat or thin, for the
+ # stat.
+ $thinPPCForStat = $sourcePPC;
+ $thinX86ForStat = $sourceX86;
+ }
+
+ if (!defined($sizePPC = $thinPPCForStat->statSize())) {
+ unlink(@tempThinFiles);
+ return complain(1, 'stat ppc: '.$!.' for:',
+ $thinPPCForStat->path());
+ }
+ if (!defined($sizeX86 = $thinX86ForStat->statSize())) {
+ unlink(@tempThinFiles);
+ return complain(1, 'stat x86: '.$!.' for:',
+ $thinX86ForStat->path());
+ }
+
+ $sizePPC = $sizePPC % 4096;
+ $sizeX86 = $sizeX86 % 4096;
+
+ my (@thinFiles);
+
+ if ($sizePPC == 0) {
+ # PPC image ends on an alignment boundary, there will be no padding before
+ # starting the x86 image.
+ @thinFiles = ($thinPPC->path(), $thinX86->path());
+ }
+ elsif ($sizeX86 == 0 || $sizeX86 > $sizePPC) {
+ # x86 image ends on an alignment boundary, there will be no padding before
+ # starting the PPC image, or the x86 image exceeds its alignment boundary
+ # by more than the PPC image, so there will be less padding if the x86
+ # comes first.
+ @thinFiles = ($thinX86->path(), $thinPPC->path());
+ }
+ else {
+ # PPC image exceeds its alignment boundary by more than the x86 image, so
+ # there will be less padding if the PPC comes first.
+ @thinFiles = ($thinPPC->path(), $thinX86->path());
+ }
+
+ my ($isExecutable);
+ $isExecutable = $sourcePPC->lIsExecutable() ||
+ $sourceX86->lIsExecutable();
+
+ if (!$gDryRun) {
+ # Ensure that the file does not yet exist.
+
+ # Set the execute bits (as allowed by the umask) on the new file if any
+ # execute bit is set on either old file. Yes, it is possible to have
+ # proper Mach-O files without x-bits: think object files (.o) and static
+ # archives (.a).
+ if (!createUniqueFile($targetPath, $isExecutable ? 0777 : 0666)) {
+ # createUniqueFile printed an error.
+ unlink(@tempThinFiles);
+ return 0;
+ }
+ }
+
+ # Create the fat file.
+ if (command($gConfig{'cmd_lipo'}, '-create', @thinFiles,
+ '-output', $targetPath) != 0) {
+ unlink(@tempThinFiles, $targetPath);
+ return complain(1, 'lipo create fat failed for:',
+ @thinFiles,
+ $targetPath);
+ }
+
+ unlink(@tempThinFiles);
+
+ if (!$gDryRun) {
+ # lipo seems to think that it's free to set its own file modes that
+ # ignore the umask, which is bogus when the rest of this script
+ # respects the umask.
+ if (!chmod(($isExecutable ? 0777 : 0666) & ~umask(), $targetPath)) {
+ complain(1, 'makeUniversalFile: chmod: '.$!.' for',
+ $targetPath);
+ unlink($targetPath);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+# makeUniversalInternal($isToplevel, $filePPC, $fileX86, $fileTargetPath)
+#
+# Given FileAttrCache objects $filePPC and $fileX86, compares filetypes
+# and performs the appropriate action to produce a universal file at
+# path string $fileTargetPath. $isToplevel should be true if this is
+# the recursive base and false otherwise; this controls cleanup behavior
+# (cleanup is only performed at the base, because cleanup itself is
+# recursive).
+#
+# This handles regular files by determining whether they are Mach-O files
+# and calling makeUniversalFile if so and copyIfIdentical otherwise. Symbolic
+# links are handled directly in this function by ensuring that the source link
+# targets are identical and creating a new link with the same target
+# at $fileTargetPath. Directories are handled by calling
+# makeUniversalDirectory.
+#
+# One of $filePPC and $fileX86 is permitted to be undef. In that case,
+# the defined source file is copied directly to the target if a regular
+# file, and symlinked appropriately if a symbolic link. This facilitates
+# use of $gOnlyOne = 'copy', although no $gOnlyOne checks are made in this
+# function, they are all handled in makeUniversalDirectory.
+#
+# Returns true on success. Returns false on failure, including failures
+# in other functions called.
+sub makeUniversalInternal($$$$) {
+ my ($filePPC, $fileTargetPath, $fileX86, $isToplevel);
+ ($isToplevel, $filePPC, $fileX86, $fileTargetPath) = @_;
+
+ my ($typePPC, $typeX86);
+ if (defined($filePPC) && !defined($typePPC = $filePPC->lstatType())) {
+ return complain(1, 'makeUniversal: lstat ppc: '.$!.' for:',
+ $filePPC->path());
+ }
+ if (defined($fileX86) && !defined($typeX86 = $fileX86->lstatType())) {
+ return complain(1, 'makeUniversal: lstat x86: '.$!.' for:',
+ $fileX86->path());
+ }
+
+ if (defined($filePPC) && defined($fileX86) && $typePPC != $typeX86) {
+ return complain(1, 'makeUniversal: incompatible types:',
+ $filePPC->path(),
+ $fileX86->path());
+ }
+
+ # $aSourceFile will contain a FileAttrCache object that will return
+ # the correct type data. It's used because it's possible for one of
+ # the two source files to be undefined (indicating a straight copy).
+ my ($aSourceFile);
+ if (defined($filePPC)) {
+ $aSourceFile = $filePPC;
+ }
+ else {
+ $aSourceFile = $fileX86;
+ }
+
+ if ($aSourceFile->lIsDir()) {
+ if ($gVerbosity >= 3 || $gDryRun) {
+ print('mkdir '.(argumentEscape($fileTargetPath))[0]."\n");
+ }
+ if (!$gDryRun && !mkdir($fileTargetPath)) {
+ return complain(1, 'makeUniversal: mkdir: '.$!.' for:',
+ $fileTargetPath);
+ }
+
+ my ($rv);
+
+ if (!($rv = makeUniversalDirectory($filePPC, $fileX86, $fileTargetPath))) {
+ # makeUniversalDirectory printed an error.
+ if ($isToplevel) {
+ command($gConfig{'cmd_rm'},'-rf','--',$fileTargetPath);
+ }
+ }
+ else {
+ # Touch the directory when leaving it. If unify is being run on an
+ # .app bundle, the .app might show up without an icon because the
+ # system might have found the .app before it was completely built.
+ # Touching it dirties it in LaunchServices' mind.
+ if ($gVerbosity >= 3) {
+ print('touch '.(argumentEscape($fileTargetPath))[0]."\n");
+ }
+ utime(undef, undef, $fileTargetPath);
+ }
+
+ return $rv;
+ }
+ elsif ($aSourceFile->lIsSymLink()) {
+ my ($linkPPC, $linkX86);
+ if (defined($filePPC) && !defined($linkPPC=readlink($filePPC->path()))) {
+ return complain(1, 'makeUniversal: readlink ppc: '.$!.' for:',
+ $filePPC->path());
+ }
+ if (defined($fileX86) && !defined($linkX86=readlink($fileX86->path()))) {
+ return complain(1, 'makeUniversal: readlink x86: '.$!.' for:',
+ $fileX86->path());
+ }
+ if (defined($filePPC) && defined($fileX86) && $linkPPC ne $linkX86) {
+ return complain(1, 'makeUniversal: symbolic links differ:',
+ $filePPC->path(),
+ $fileX86->path());
+ }
+
+ # $aLink here serves the same purpose as $aSourceFile in the enclosing
+ # block: it refers to the target of the symbolic link, whether there
+ # is one valid source or two.
+ my ($aLink);
+ if (defined($linkPPC)) {
+ $aLink = $linkPPC;
+ }
+ else {
+ $aLink = $linkX86;
+ }
+
+ if ($gVerbosity >= 3 || $gDryRun) {
+ print('ln -s '.
+ join(' ',argumentEscape($aLink, $fileTargetPath))."\n");
+ }
+ if (!$gDryRun && !symlink($aLink, $fileTargetPath)) {
+ return complain(1, 'makeUniversal: symlink: '.$!.' for:',
+ $aLink,
+ $fileTargetPath);
+ }
+
+ return 1;
+ }
+ elsif($aSourceFile->lIsRegularFile()) {
+ my ($machPPC, $machX86, $fileName);
+ if (!defined($filePPC) || !defined($fileX86)) {
+ # One of the source files isn't present. The right thing to do is
+ # to just copy what does exist straight over, so skip Mach-O checks.
+ $machPPC = 0;
+ $machX86 = 0;
+ if (defined($filePPC)) {
+ $fileName = $filePPC;
+ } elsif (defined($fileX86)) {
+ $fileName = $fileX86;
+ } else {
+ complain(1, "The file must exist in at least one directory");
+ exit(1);
+ }
+ }
+ else {
+ # both files exist, pick the name of one.
+ $fileName = $fileX86;
+ if (!defined($machPPC=$filePPC->isMachO())) {
+ return complain(1, 'makeUniversal: isFileMachO ppc failed for:',
+ $filePPC->path());
+ }
+ if (!defined($machX86=$fileX86->isMachO())) {
+ return complain(1, 'makeUniversal: isFileMachO x86 failed for:',
+ $fileX86->path());
+ }
+ }
+
+ if ($machPPC != $machX86) {
+ return complain(1, 'makeUniversal: variant Mach-O attributes:',
+ $filePPC->path(),
+ $fileX86->path());
+ }
+
+ if ($machPPC) {
+ # makeUniversalFile will print an error if it fails.
+ return makeUniversalFile($filePPC, $fileX86, $fileTargetPath);
+ }
+
+ if (grep { $fileName->path() =~ m/$_/; } @gSortMatches) {
+ # Regular files, but should be compared with sorting first.
+ # copyIfIdenticalWhenSorted will print an error if it fails.
+ return copyIfIdenticalWhenSorted($filePPC, $fileX86, $fileTargetPath);
+ }
+
+ # Regular file. copyIfIdentical will print an error if it fails.
+ return copyIfIdentical($filePPC, $fileX86, $fileTargetPath);
+ }
+
+ # Special file, don't know how to handle.
+ return complain(1, 'makeUniversal: cannot handle special file:',
+ $filePPC->path(),
+ $fileX86->path());
+}
+
+# usage()
+#
+# Give the user a hand.
+sub usage() {
+ print STDERR (
+"usage: unify <ppc-path> <x86-path> <universal-path>\n".
+" [--dry-run] (print what would be done)\n".
+" [--only-one <action>] (skip, copy, fail; default=copy)\n".
+" [--verbosity <level>] (0, 1, 2, 3; default=2)\n");
+ return;
+}
+
+# readZipCRCs($zipFile)
+#
+# $zipFile is the pathname to a zip file whose directory will be read.
+# A reference to a hash is returned, with the member pathnames from the
+# zip file as keys, and reasonably unique identifiers as values. The
+# format of the values is not specified exactly, but does include the
+# member CRCs and sizes and differentiates between files and directories.
+# It specifically does not distinguish between modification times. On
+# failure, prints a message and returns undef.
+sub readZipCRCs($) {
+ my ($zipFile);
+ ($zipFile) = @_;
+
+ my ($ze, $zip);
+ $zip = Archive::Zip->new();
+
+ if (($ze = $zip->read($zipFile)) != AZ_OK) {
+ complain(1, 'readZipCRCs: read error '.$ze.' for:',
+ $zipFile);
+ return undef;
+ }
+
+ my ($member, %memberCRCs, @memberList);
+ %memberCRCs = ();
+ @memberList = $zip->members();
+
+ foreach $member (@memberList) {
+ # Take a few of the attributes that identify the file and stuff them into
+ # the members hash. Directories will show up with size 0 and crc32 0,
+ # so isDirectory() is used to distinguish them from empty files.
+ $memberCRCs{$member->fileName()} = join(',', $member->isDirectory() ? 1 : 0,
+ $member->uncompressedSize(),
+ $member->crc32String());
+ }
+
+ return {%memberCRCs};
+}
+
+{
+ # FileAttrCache allows various attributes about a file to be cached
+ # so that if they are needed again after first use, no system calls
+ # will be made and the program won't need to hit the disk.
+
+ package FileAttrCache;
+
+ # from /usr/include/mach-o/loader.h
+ use constant MH_MAGIC => 0xfeedface;
+ use constant MH_CIGAM => 0xcefaedfe;
+ use constant MH_MAGIC_64 => 0xfeedfacf;
+ use constant MH_CIGAM_64 => 0xcffaedfe;
+
+ use Fcntl(':DEFAULT', ':mode');
+
+ # FileAttrCache->new($path)
+ #
+ # Creates a new FileAttrCache object for the file at path $path and
+ # returns it. The cache is not primed at creation time, values are
+ # fetched lazily as they are needed.
+ sub new($$) {
+ my ($class, $path, $proto, $this);
+ ($proto, $path) = @_;
+ if (!($class = ref($proto))) {
+ $class = $proto;
+ }
+ $this = {
+ 'path' => $path,
+ 'lstat' => undef,
+ 'lstatErrno' => 0,
+ 'lstatInit' => 0,
+ 'magic' => undef,
+ 'magic2' => undef,
+ 'magicErrno' => 0,
+ 'magicErrMsg' => undef,
+ 'magicInit' => 0,
+ 'stat' => undef,
+ 'statErrno' => 0,
+ 'statInit' => 0,
+ };
+ bless($this, $class);
+ return($this);
+ }
+
+ # $FileAttrCache->isFat()
+ #
+ # Returns true if the file is a fat Mach-O file, false if it's not, and
+ # undef if an error occurs. See /usr/include/mach-o/fat.h.
+ sub isFat($) {
+ my ($magic, $magic2, $this);
+ ($this) = @_;
+
+ # magic() caches, there's no separate cache because isFat() doesn't hit
+ # the disk other than by calling magic().
+
+ if (!defined($magic = $this->magic())) {
+ return undef;
+ }
+ $magic2 = $this->magic2();
+
+ # We have to sanity check the second four bytes, because Java class
+ # files use the same magic number as Mach-O fat binaries.
+ # This logic is adapted from file(1), which says that Mach-O uses
+ # these bytes to count the number of architectures within, while
+ # Java uses it for a version number. Conveniently, there are only
+ # 18 labelled Mach-O architectures, and Java's first released
+ # class format used the version 43.0.
+ if ($magic == 0xcafebabe && $magic2 < 20) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ # $FileAttrCache->isMachO()
+ #
+ # Returns true if the file is a Mach-O image (including a fat file), false
+ # if it's not, and undef if an error occurs. See
+ # /usr/include/mach-o/loader.h and /usr/include/mach-o/fat.h.
+ sub isMachO($) {
+ my ($magic, $this);
+ ($this) = @_;
+
+ # magic() caches, there's no separate cache because isMachO() doesn't hit
+ # the disk other than by calling magic().
+
+ if (!defined($magic = $this->magic())) {
+ return undef;
+ }
+
+ # Accept Mach-O fat files or Mach-O thin files of either endianness.
+ if ($magic == MH_MAGIC ||
+ $magic == MH_CIGAM ||
+ $magic == MH_MAGIC_64 ||
+ $magic == MH_CIGAM_64 ||
+ $this->isFat()) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ # $FileAttrCache->isZip()
+ #
+ # Returns true if the file is a zip file, false if it's not, and undef if
+ # an error occurs. See http://www.pkware.com/business_and_developers/developer/popups/appnote.txt .
+ sub isZip($) {
+ my ($magic, $this);
+ ($this) = @_;
+
+ # magic() caches, there's no separate cache because isFat() doesn't hit
+ # the disk other than by calling magic().
+
+ if (!defined($magic = $this->magic())) {
+ return undef;
+ }
+
+ if ($magic == 0x504b0304) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ # $FileAttrCache->lIsExecutable()
+ #
+ # Wraps $FileAttrCache->lstat(), returning true if the file is has any,
+ # execute bit set, false if none are set, or undef if an error occurs.
+ # On error, $! is set to lstat's errno.
+ sub lIsExecutable($) {
+ my ($mode, $this);
+ ($this) = @_;
+
+ if (!defined($mode = $this->lstatMode())) {
+ return undef;
+ }
+
+ return $mode & (S_IXUSR | S_IXGRP | S_IXOTH);
+ }
+
+ # $FileAttrCache->lIsDir()
+ #
+ # Wraps $FileAttrCache->lstat(), returning true if the file is a directory,
+ # false if it isn't, or undef if an error occurs. Because lstat is used,
+ # this will return false even if the file is a symlink pointing to a
+ # directory. On error, $! is set to lstat's errno.
+ sub lIsDir($) {
+ my ($type, $this);
+ ($this) = @_;
+
+ if (!defined($type = $this->lstatType())) {
+ return undef;
+ }
+
+ return S_ISDIR($type);
+ }
+
+ # $FileAttrCache->lIsRegularFile()
+ #
+ # Wraps $FileAttrCache->lstat(), returning true if the file is a regular,
+ # file, false if it isn't, or undef if an error occurs. Because lstat is
+ # used, this will return false even if the file is a symlink pointing to a
+ # regular file. On error, $! is set to lstat's errno.
+ sub lIsRegularFile($) {
+ my ($type, $this);
+ ($this) = @_;
+
+ if (!defined($type = $this->lstatType())) {
+ return undef;
+ }
+
+ return S_ISREG($type);
+ }
+
+ # $FileAttrCache->lIsSymLink()
+ #
+ # Wraps $FileAttrCache->lstat(), returning true if the file is a symbolic,
+ # link, false if it isn't, or undef if an error occurs. On error, $! is
+ # set to lstat's errno.
+ sub lIsSymLink($) {
+ my ($type, $this);
+ ($this) = @_;
+
+ if (!defined($type = $this->lstatType())) {
+ return undef;
+ }
+
+ return S_ISLNK($type);
+ }
+
+ # $FileAttrCache->lstat()
+ #
+ # Wraps the lstat system call, providing a cache to speed up multiple
+ # lstat calls for the same file. See lstat(2) and lstat in perlfunc(1).
+ sub lstat($) {
+ my (@stat, $this);
+ ($this) = @_;
+
+ # Use the cached lstat result.
+ if ($$this{'lstatInit'}) {
+ if (defined($$this{'lstatErrno'})) {
+ $! = $$this{'lstatErrno'};
+ }
+ return @{$$this{'lstat'}};
+ }
+ $$this{'lstatInit'} = 1;
+
+ if (!(@stat = CORE::lstat($$this{'path'}))) {
+ $$this{'lstatErrno'} = $!;
+ }
+
+ $$this{'lstat'} = [@stat];
+ return @stat;
+ }
+
+ # $FileAttrCache->lstatMode()
+ #
+ # Wraps $FileAttrCache->lstat(), returning the mode bits from the st_mode
+ # field, or undef if an error occurs. On error, $! is set to lstat's
+ # errno.
+ sub lstatMode($) {
+ my (@stat, $this);
+ ($this) = @_;
+
+ if (!(@stat = $this->lstat())) {
+ return undef;
+ }
+
+ return S_IMODE($stat[2]);
+ }
+
+ # $FileAttrCache->lstatType()
+ #
+ # Wraps $FileAttrCache->lstat(), returning the type bits from the st_mode
+ # field, or undef if an error occurs. On error, $! is set to lstat's
+ # errno.
+ sub lstatType($) {
+ my (@stat, $this);
+ ($this) = @_;
+
+ if (!(@stat = $this->lstat())) {
+ return undef;
+ }
+
+ return S_IFMT($stat[2]);
+ }
+
+ # $FileAttrCache->magic()
+ #
+ # Returns the "magic number" for the file by reading its first four bytes
+ # as a big-endian unsigned 32-bit integer and returning the result. If an
+ # error occurs, returns undef and prints diagnostic messages to stderr. If
+ # the file is shorter than 32 bits, returns -1. A cache is provided to
+ # speed multiple magic calls for the same file.
+ sub magic($) {
+ my ($this);
+ ($this) = @_;
+
+ # Use the cached magic result.
+ if ($$this{'magicInit'}) {
+ if (defined($$this{'magicErrno'})) {
+ if (defined($$this{'magicErrMsg'})) {
+ main::complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
+ $$this{'path'});
+ }
+ $! = $$this{'magicErrno'};
+ }
+ return $$this{'magic'};
+ }
+
+ $$this{'magicInit'} = 1;
+
+ my ($fh);
+ if (!sysopen($fh, $$this{'path'}, O_RDONLY)) {
+ $$this{'magicErrno'} = $!;
+ $$this{'magicErrMsg'} = 'open "'.$$this{'path'}.'": '.$!;
+ main::complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
+ $$this{'path'});
+ return undef;
+ }
+
+ $! = 0;
+ my ($bytes, $magic, $bytes2, $magic2);
+ if (!defined($bytes = sysread($fh, $magic, 4))) {
+ $$this{'magicErrno'} = $!;
+ $$this{'magicErrMsg'} = 'read "'.$$this{'path'}.'": '.$!;
+ main::complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
+ $$this{'path'});
+ close($fh);
+ return undef;
+ }
+ else {
+ $bytes2 = sysread($fh, $magic2, 4);
+ }
+
+ close($fh);
+
+ if ($bytes != 4) {
+ # The file is too short, didn't read a magic number. This isn't really
+ # an error. Return an unlikely value.
+ $$this{'magic'} = -1;
+ $$this{'magic2'} = -1;
+ return -1;
+ }
+ if ($bytes2 != 4) {
+ # File is too short to read a second 4 bytes.
+ $magic2 = -1;
+ }
+
+ $$this{'magic'} = unpack('N', $magic);
+ $$this{'magic2'} = unpack('N', $magic2);
+ return $$this{'magic'};
+ }
+
+ # $FileAttrCache->magic2()
+ #
+ # Returns the second four bytes of the file as a 32-bit little endian number.
+ # See magic(), above for more info.
+ sub magic2($) {
+ my ($this);
+ ($this) = @_;
+
+ # we do the actual work (and cache it) in magic().
+ if (!$$this{'magicInit'}) {
+ my $magic = $$this->magic();
+ }
+
+ return $$this{'magic2'};
+ }
+
+ # $FileAttrCache->path()
+ #
+ # Returns the file's pathname.
+ sub path($) {
+ my ($this);
+ ($this) = @_;
+ return $$this{'path'};
+ }
+
+ # $FileAttrCache->stat()
+ #
+ # Wraps the stat system call, providing a cache to speed up multiple
+ # stat calls for the same file. If lstat() has already been called and
+ # the file is not a symbolic link, the cached lstat() result will be used.
+ # See stat(2) and lstat in perlfunc(1).
+ sub stat($) {
+ my (@stat, $this);
+ ($this) = @_;
+
+ # Use the cached stat result.
+ if ($$this{'statInit'}) {
+ if (defined($$this{'statErrno'})) {
+ $! = $$this{'statErrno'};
+ }
+ return @{$$this{'stat'}};
+ }
+
+ $$this{'statInit'} = 1;
+
+ # If lstat has already been called, and the file isn't a symbolic link,
+ # use the cached lstat result.
+ if ($$this{'lstatInit'} && !$$this{'lstatErrno'} &&
+ !S_ISLNK(${$$this{'lstat'}}[2])) {
+ $$this{'stat'} = $$this{'lstat'};
+ return @{$$this{'stat'}};
+ }
+
+ if (!(@stat = CORE::stat($$this{'path'}))) {
+ $$this{'statErrno'} = $!;
+ }
+
+ $$this{'stat'} = [@stat];
+ return @stat;
+ }
+
+ # $FileAttrCache->statSize()
+ #
+ # Wraps $FileAttrCache->stat(), returning the st_size field, or undef
+ # undef if an error occurs. On error, $! is set to stat's errno.
+ sub statSize($) {
+ my (@stat, $this);
+ ($this) = @_;
+
+ if (!(@stat = $this->lstat())) {
+ return undef;
+ }
+
+ return $stat[7];
+ }
+}
diff --git a/build/moz.configure/init.configure b/build/moz.configure/init.configure
index aab1b755bb..f0d6b791b6 100644
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -541,6 +541,20 @@ def target_is_unix(target):
set_define('XP_UNIX', target_is_unix)
@depends(target)
+def target_is_darwin(target):
+ if target.kernel == 'Darwin':
+ return True
+
+set_define('XP_DARWIN', target_is_darwin)
+
+@depends(target)
+def target_is_osx(target):
+ if target.kernel == 'Darwin' and target.os == 'OSX':
+ return True
+
+set_define('XP_MACOSX', target_is_osx)
+
+@depends(target)
def target_is_linux(target):
if target.kernel == 'Linux':
return True
diff --git a/build/moz.configure/toolchain.configure b/build/moz.configure/toolchain.configure
index 1f2b5ecf07..ee14ce009e 100644
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -867,7 +867,8 @@ add_old_configure_assignment('MOZ_DEBUG_FLAGS', debug_flags)
# effectively does the same thing we are doing here.
@depends(c_compiler, target)
def libcxx_inline_visibility(c_compiler, target):
- if c_compiler.type == 'clang':
+ # FIXME: Vestigial conditional left over from Android, please remove.
+ if False:
return ''
set_define('_LIBCPP_INLINE_VISIBILITY', libcxx_inline_visibility)
diff --git a/chrome/nsChromeRegistry.cpp b/chrome/nsChromeRegistry.cpp
index ef2cb79ab7..74de3b80d5 100644
--- a/chrome/nsChromeRegistry.cpp
+++ b/chrome/nsChromeRegistry.cpp
@@ -296,6 +296,8 @@ nsChromeRegistry::ConvertChromeURL(nsIURI* aChromeURI, nsIURI* *aResult)
if (flags & PLATFORM_PACKAGE) {
#if defined(XP_WIN)
path.Insert("win/", 0);
+#elif defined(XP_MACOSX)
+ path.Insert("mac/", 0);
#else
path.Insert("unix/", 0);
#endif
diff --git a/chrome/nsChromeRegistryChrome.cpp b/chrome/nsChromeRegistryChrome.cpp
index c115280fdc..7678183fd3 100644
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -11,6 +11,8 @@
#if defined(XP_WIN)
#include <windows.h>
+#elif defined(XP_MACOSX)
+#include <CoreServices/CoreServices.h>
#endif
#include "nsArrayEnumerator.h"
diff --git a/config/external/nspr/prcpucfg.h b/config/external/nspr/prcpucfg.h
index 8f455b509c..8769abeeb8 100644
--- a/config/external/nspr/prcpucfg.h
+++ b/config/external/nspr/prcpucfg.h
@@ -10,7 +10,9 @@
* Need to support conditionals that are defined in both the top-level build
* system as well as NSS' build system for now.
*/
-#if defined(XP_WIN) || defined(_WINDOWS)
+#if defined(XP_DARWIN) || defined(DARWIN)
+#include "md/_darwin.cfg"
+#elif defined(XP_WIN) || defined(_WINDOWS)
#include "md/_win95.cfg"
#elif defined(__FreeBSD__)
#include "md/_freebsd.cfg"
diff --git a/config/rules.mk b/config/rules.mk
index 840e266ba5..cb3e307ae2 100644
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -1,4 +1,5 @@
# -*- makefile -*-
+# vim:set ts=8 sw=8 sts=8 noet:
#
# 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,
@@ -750,6 +751,7 @@ $(HOST_LIBRARY): $(HOST_OBJS) Makefile
$(EXPAND_LIBS_EXEC) --extract -- $(HOST_AR) $(HOST_AR_FLAGS) $(HOST_OBJS)
ifdef HAVE_DTRACE
+ifndef XP_MACOSX
ifdef DTRACE_PROBE_OBJ
ifndef DTRACE_LIB_DEPENDENT
NON_DTRACE_OBJS := $(filter-out $(DTRACE_PROBE_OBJ),$(OBJS))
@@ -758,6 +760,7 @@ $(DTRACE_PROBE_OBJ): $(NON_DTRACE_OBJS)
endif
endif
endif
+endif
# On Darwin (Mac OS X), dwarf2 debugging uses debug info left in .o files,
# so instead of deleting .o files after repacking them into a dylib, we make
@@ -770,7 +773,9 @@ ifndef INCREMENTAL_LINKER
$(RM) $@
endif
ifdef DTRACE_LIB_DEPENDENT
+ifndef XP_MACOSX
dtrace -x nolibs -G -C -s $(MOZILLA_DTRACE_SRC) -o $(DTRACE_PROBE_OBJ) $(shell $(EXPAND_LIBS) $(MOZILLA_PROBE_LIBS))
+endif
$(EXPAND_MKSHLIB) $(SHLIB_LDSTARTFILE) $(OBJS) $(SUB_SHLOBJS) $(DTRACE_PROBE_OBJ) $(MOZILLA_PROBE_LIBS) $(RESFILE) $(LDFLAGS) $(WRAP_LDFLAGS) $(STATIC_LIBS) $(RUST_STATIC_LIB_FOR_SHARED_LIB) $(SHARED_LIBS) $(EXTRA_DSO_LDOPTS) $(MOZ_GLUE_LDFLAGS) $(EXTRA_LIBS) $(OS_LIBS) $(SHLIB_LDENDFILE)
@$(RM) $(DTRACE_PROBE_OBJ)
else # ! DTRACE_LIB_DEPENDENT
diff --git a/db/mork/src/morkConfig.h b/db/mork/src/morkConfig.h
index dbd4ebdf5b..370cf17139 100644
--- a/db/mork/src/morkConfig.h
+++ b/db/mork/src/morkConfig.h
@@ -18,6 +18,10 @@
// { %%%%% begin platform defs peculiar to Mork %%%%%
+#ifdef XP_MACOSX
+#define MORK_MAC 1
+#endif
+
#ifdef XP_WIN
#define MORK_WIN 1
#endif
@@ -28,7 +32,7 @@
// } %%%%% end platform defs peculiar to Mork %%%%%
-#if defined(MORK_WIN) || defined(MORK_UNIX)
+#if defined(MORK_WIN) || defined(MORK_UNIX) || defined(MORK_MAC)
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
@@ -84,15 +88,20 @@ void mork_fileflush(FILE * file);
#define mork_kTAB '\011'
#define mork_kCRLF "\015\012" /* A CR LF equivalent string */
-#if defined(MORK_WIN)
-# define mork_kNewline "\015\012"
-# define mork_kNewlineSize 2
+#if defined(MORK_MAC)
+# define mork_kNewline "\015"
+# define mork_kNewlineSize 1
#else
-# if defined(MORK_UNIX)
-# define mork_kNewline "\012"
-# define mork_kNewlineSize 1
-# endif /* MORK_UNIX */
-#endif /* MORK_WIN */
+# if defined(MORK_WIN)
+# define mork_kNewline "\015\012"
+# define mork_kNewlineSize 2
+# else
+# if defined(MORK_UNIX)
+# define mork_kNewline "\012"
+# define mork_kNewlineSize 1
+# endif /* MORK_UNIX */
+# endif /* MORK_WIN */
+#endif /* MORK_MAC */
// { %%%%% begin assertion macro %%%%%
extern void mork_assertion_signal(const char* inMessage);
@@ -105,7 +114,7 @@ extern void mork_assertion_signal(const char* inMessage);
// { %%%%% begin standard c utility methods %%%%%
-#if defined(MORK_WIN) || defined(MORK_UNIX)
+#if defined(MORK_WIN) || defined(MORK_UNIX) || defined(MORK_MAC)
#define MORK_USE_C_STDLIB 1
#endif /*MORK_WIN*/
diff --git a/db/sqlite3/src/moz.build b/db/sqlite3/src/moz.build
index 327e77677a..1ffc326f8a 100644
--- a/db/sqlite3/src/moz.build
+++ b/db/sqlite3/src/moz.build
@@ -45,6 +45,10 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
DEFINES['SQLITE_WIN32_GETVERSIONEX'] = 0
DEFINES['SQLITE_ALLOW_URI_AUTHORITY'] = 1
+# -DSQLITE_ENABLE_LOCKING_STYLE=1 to help with AFP folders
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DEFINES['SQLITE_ENABLE_LOCKING_STYLE'] = 1
+
# sqlite defaults this to on on __APPLE_ but it breaks on newer iOS SDKs
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
DEFINES['SQLITE_ENABLE_LOCKING_STYLE'] = 0
diff --git a/devtools/client/framework/dev-edition-promo/dev-edition-promo.css b/devtools/client/framework/dev-edition-promo/dev-edition-promo.css
index b4d6383825..01489fd47f 100644
--- a/devtools/client/framework/dev-edition-promo/dev-edition-promo.css
+++ b/devtools/client/framework/dev-edition-promo/dev-edition-promo.css
@@ -21,10 +21,14 @@ window {
* depend on. Must style font-size to target linux.
*/
%ifdef XP_UNIX
+%ifndef XP_MACOSX
font-size: 13px;
%else
font-size: 15px;
%endif
+%else
+ font-size: 15px;
+%endif
line-height: 19px;
min-height: 100px;
}
diff --git a/devtools/client/framework/toolbox-process-window.js b/devtools/client/framework/toolbox-process-window.js
index 87b3efa438..82edabe9c6 100644
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -142,6 +142,16 @@ function evaluateTestScript(script, toolbox) {
function bindToolboxHandlers() {
gToolbox.once("destroyed", quitApp);
window.addEventListener("unload", onUnload);
+
+#ifdef XP_MACOSX
+ // Badge the dock icon to differentiate this process from the main application process.
+ updateBadgeText(false);
+
+ // Once the debugger panel opens listen for thread pause / resume.
+ gToolbox.getPanelWhenReady("jsdebugger").then(panel => {
+ setupThreadListeners(panel);
+ });
+#endif
}
function setupThreadListeners(panel) {
diff --git a/devtools/client/jar.mn b/devtools/client/jar.mn
index 9951e7e3dc..763a59fbd6 100644
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -110,7 +110,7 @@ devtools.jar:
content/framework/toolbox-init.js (framework/toolbox-init.js)
content/framework/options-panel.css (framework/options-panel.css)
content/framework/toolbox-process-window.xul (framework/toolbox-process-window.xul)
- content/framework/toolbox-process-window.js (framework/toolbox-process-window.js)
+* content/framework/toolbox-process-window.js (framework/toolbox-process-window.js)
content/framework/dev-edition-promo/dev-edition-promo.xul (framework/dev-edition-promo/dev-edition-promo.xul)
* content/framework/dev-edition-promo/dev-edition-promo.css (framework/dev-edition-promo/dev-edition-promo.css)
content/framework/dev-edition-promo/dev-edition-logo.png (framework/dev-edition-promo/dev-edition-logo.png)
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index 3972abc688..28b66be3db 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1388,6 +1388,12 @@ Navigator::GetPlatform(nsAString& aPlatform, bool aUsePrefOverriddenValue)
aPlatform.AssignLiteral("Win64");
#elif defined(WIN32)
aPlatform.AssignLiteral("Win32");
+#elif defined(XP_MACOSX) && defined(__ppc__)
+ aPlatform.AssignLiteral("MacPPC");
+#elif defined(XP_MACOSX) && defined(__i386__)
+ aPlatform.AssignLiteral("MacIntel");
+#elif defined(XP_MACOSX) && defined(__x86_64__)
+ aPlatform.AssignLiteral("MacIntel");
#else
// XXX Communicator uses compiled-in build-time string defines
// to indicate the platform it was compiled *for*, not what it is
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index 657c0c0fb2..fa8ba6d97d 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6840,6 +6840,10 @@ nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2)
bool
nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
{
+#ifdef XP_MACOSX
+ // We control dispatch to all mac plugins.
+ return false;
+#else
if (!aContent || !aContent->IsInUncomposedDoc()) {
return false;
}
@@ -6862,6 +6866,7 @@ nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent)
}
return !isWindowless;
+#endif
}
/* static */
diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp
index 0c12103fba..6cc5bbd36f 100644
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -64,7 +64,9 @@
#include "nsAccessibilityService.h"
#endif
+#ifndef XP_MACOSX
#include "nsIScriptError.h"
+#endif
using namespace mozilla;
using namespace mozilla::dom;
@@ -1264,10 +1266,12 @@ nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
}
}
- // Exit fullscreen if we're focusing a windowed plugin. We don't control
- // event dispatch to windowed plugins, so we can't display the <Press ESC
- // to leave fullscreen mode> warning on key input if a windowed plugin is
- // focused, so just exit fullscreen to guard against phishing.
+ // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
+ // system. We don't control event dispatch to windowed plugins on non-MacOSX,
+ // so we can't display the "Press ESC to leave fullscreen mode" warning on
+ // key input if a windowed plugin is focused, so just exit fullscreen
+ // to guard against phishing.
+#ifndef XP_MACOSX
if (contentToFocus &&
nsContentUtils::
GetRootDocument(contentToFocus->OwnerDoc())->GetFullscreenElement() &&
@@ -1279,6 +1283,7 @@ nsFocusManager::SetFocusInner(nsIContent* aNewContent, int32_t aFlags,
"FocusedWindowedPluginWhileFullscreen");
nsIDocument::AsyncExitFullscreen(contentToFocus->OwnerDoc());
}
+#endif
// if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
// shifted away from the current element if the new shell to focus is
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
index ccfbf488d3..c6b35a9bf0 100644
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6222,7 +6222,18 @@ nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop, bool aCal
screen->GetAvailLeft(&screenLeft);
screen->GetAvailWidth(&screenWidth);
screen->GetAvailHeight(&screenHeight);
+#if defined(XP_MACOSX)
+ /* The mac's coordinate system is different from the assumed Windows'
+ system. It offsets by the height of the menubar so that a window
+ placed at (0,0) will be entirely visible. Unfortunately that
+ correction is made elsewhere (in Widget) and the meaning of
+ the Avail... coordinates is overloaded. Here we allow a window
+ to be placed at (0,0) because it does make sense to do so.
+ */
+ screen->GetTop(&screenTop);
+#else
screen->GetAvailTop(&screenTop);
+#endif
if (aLeft) {
if (screenLeft+screenWidth < *aLeft+winWidth)
@@ -7060,6 +7071,16 @@ nsGlobalWindow::Dump(const nsAString& aStr)
char *cstr = ToNewUTF8String(aStr);
+#if defined(XP_MACOSX)
+ // have to convert \r to \n so that printing to the console works
+ char *c = cstr, *cEnd = cstr + strlen(cstr);
+ while (c < cEnd) {
+ if (*c == '\r')
+ *c = '\n';
+ c++;
+ }
+#endif
+
if (cstr) {
MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("[Window.Dump] %s", cstr));
#ifdef XP_WIN
diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp
index 15448181f7..5229948e01 100644
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -59,6 +59,10 @@
#include "nsJSPrincipals.h"
+#ifdef XP_MACOSX
+// AssertMacros.h defines 'check' and conflicts with AccessCheck.h
+#undef check
+#endif
#include "AccessCheck.h"
#include "mozilla/Logging.h"
diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp
index f8541c61ba..4129135baa 100644
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -95,6 +95,13 @@
#endif
#endif // XP_WIN
+#ifdef XP_MACOSX
+// HandlePluginCrashed() and HandlePluginInstantiated() needed from here to
+// fix bug 1147521. Should later be replaced by proper interface methods,
+// maybe on nsIObjectLoadingContext.
+#include "mozilla/dom/HTMLObjectElement.h"
+#endif
+
static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
static const char *kPrefJavaMIME = "plugin.java.mime";
@@ -909,6 +916,10 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
NS_LITERAL_STRING("PluginInstantiated"));
NS_DispatchToCurrentThread(ev);
+#ifdef XP_MACOSX
+ HTMLObjectElement::HandlePluginInstantiated(thisContent->AsElement());
+#endif
+
return NS_OK;
}
@@ -2907,6 +2918,10 @@ nsObjectLoadingContent::PluginCrashed(nsIPluginTag* aPluginTag,
nsCOMPtr<nsIContent> thisContent =
do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+#ifdef XP_MACOSX
+ HTMLObjectElement::HandlePluginCrashed(thisContent->AsElement());
+#endif
+
PluginDestroyed();
// Switch to fallback/crashed state, notify
@@ -3145,6 +3160,10 @@ nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner,
return;
}
+#if defined(XP_MACOSX)
+ aInstanceOwner->HidePluginWindow();
+#endif
+
RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
NS_ASSERTION(pluginHost, "No plugin host?");
pluginHost->StopPluginInstance(inst);
diff --git a/dom/base/nsWindowRoot.cpp b/dom/base/nsWindowRoot.cpp
index 38348b46c2..509420f76d 100644
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -34,8 +34,14 @@ nsWindowRoot::nsWindowRoot(nsPIDOMWindowOuter* aWindow)
mWindow = aWindow;
MOZ_ASSERT(mWindow->IsOuterWindow());
+ // Keyboard indicators are not shown on Mac by default.
+#if defined(XP_MACOSX)
+ mShowAccelerators = false;
+ mShowFocusRings = false;
+#else
mShowAccelerators = true;
mShowFocusRings = true;
+#endif
}
nsWindowRoot::~nsWindowRoot()
diff --git a/dom/canvas/WebGL2Context.cpp b/dom/canvas/WebGL2Context.cpp
index 09891e7b6e..8c472384e5 100644
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -125,6 +125,13 @@ WebGLContext::InitWebGL2(FailureReason* const out_failReason)
fnGatherMissing2(gl::GLFeature::occlusion_query_boolean,
gl::GLFeature::occlusion_query);
+#ifdef XP_MACOSX
+ // On OSX, GL core profile is used. This requires texture swizzle
+ // support to emulate legacy texture formats: ALPHA, LUMINANCE,
+ // and LUMINANCE_ALPHA.
+ fnGatherMissing(gl::GLFeature::texture_swizzle);
+#endif
+
fnGatherMissing2(gl::GLFeature::prim_restart_fixed,
gl::GLFeature::prim_restart);
diff --git a/dom/canvas/WebGLBuffer.cpp b/dom/canvas/WebGLBuffer.cpp
index 11d956c8a4..02a8f649fe 100644
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -115,7 +115,7 @@ WebGLBuffer::BufferData(GLenum target, size_t size, const void* data, GLenum usa
const ScopedLazyBind lazyBind(gl, target, this);
mContext->InvalidateBufferFetching();
-#if defined(MOZ_WIDGET_GTK)
+#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
// bug 790879
if (gl->WorkAroundDriverBugs() &&
size > INT32_MAX)
diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h
index 628d4713ca..0510e6898a 100644
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -30,6 +30,10 @@
#include "ScopedGLHelpers.h"
#include "TexUnpackBlob.h"
+#ifdef XP_MACOSX
+#include "ForceDiscreteGPUHelperCGL.h"
+#endif
+
// Local
#include "WebGLContextLossHandler.h"
#include "WebGLContextUnchecked.h"
@@ -2011,6 +2015,15 @@ protected:
JSObject* WebGLObjectAsJSObject(JSContext* cx, const WebGLObjectType*,
ErrorResult& rv) const;
+#ifdef XP_MACOSX
+ // see bug 713305. This RAII helper guarantees that we're on the discrete GPU, during its lifetime
+ // Debouncing note: we don't want to switch GPUs too frequently, so try to not create and destroy
+ // these objects at high frequency. Having WebGLContext's hold one such object seems fine,
+ // because WebGLContext objects only go away during GC, which shouldn't happen too frequently.
+ // If in the future GC becomes much more frequent, we may have to revisit then (maybe use a timer).
+ ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper;
+#endif
+
public:
// console logging helpers
void GenerateWarning(const char* fmt, ...);
diff --git a/dom/canvas/WebGLContextBuffers.cpp b/dom/canvas/WebGLContextBuffers.cpp
index 6f583c10c6..e787b99143 100644
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -295,6 +295,16 @@ WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
////
+#ifdef XP_MACOSX
+ if (buffer && buffer->Content() == WebGLBuffer::Kind::Undefined &&
+ gl->WorkAroundDriverBugs())
+ {
+ // BindBufferRange will fail if the buffer's contents is undefined.
+ // Bind so driver initializes the buffer.
+ gl->fBindBuffer(target, buffer->mGLName);
+ }
+#endif
+
gl->fBindBufferRange(target, index, buffer ? buffer->mGLName : 0, offset, size);
////
@@ -342,6 +352,12 @@ WebGLContext::BufferData(GLenum target, WebGLsizeiptr size, GLenum usage)
if (!checkedSize.isValid())
return ErrorOutOfMemory("%s: Size too large for platform.", funcName);
+#if defined(XP_MACOSX)
+ if (gl->WorkAroundDriverBugs() && size > 1200000000) {
+ return ErrorOutOfMemory("Allocations larger than 1200000000 fail on MacOS.");
+ }
+#endif
+
const UniqueBuffer zeroBuffer(calloc(size, 1));
if (!zeroBuffer)
return ErrorOutOfMemory("%s: Failed to allocate zeros.", funcName);
diff --git a/dom/canvas/WebGLContextDraw.cpp b/dom/canvas/WebGLContextDraw.cpp
index c0ba20301a..6c684b2ff8 100644
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -1045,6 +1045,13 @@ WebGLContext::WhatDoesVertexAttrib0Need() const
const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled;
bool legacyAttrib0 = gl->IsCompatibilityProfile();
+#ifdef XP_MACOSX
+ if (gl->WorkAroundDriverBugs()) {
+ // Failures in conformance/attribs/gl-disabled-vertex-attrib.
+ // Even in Core profiles on NV. Sigh.
+ legacyAttrib0 |= (gl->Vendor() == gl::GLVendor::NVIDIA);
+ }
+#endif
if (!legacyAttrib0)
return WebGLVertexAttrib0Status::Default;
diff --git a/dom/canvas/WebGLContextValidate.cpp b/dom/canvas/WebGLContextValidate.cpp
index 60bb3a32cc..30d4c65221 100644
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -695,6 +695,18 @@ WebGLContext::InitAndValidateGL(FailureReason* const out_failReason)
gl->fEnable(LOCAL_GL_PROGRAM_POINT_SIZE);
}
+#ifdef XP_MACOSX
+ if (gl->WorkAroundDriverBugs() &&
+ gl->Vendor() == gl::GLVendor::ATI &&
+ !nsCocoaFeatures::IsAtLeastVersion(10,9))
+ {
+ // The Mac ATI driver, in all known OSX version up to and including
+ // 10.8, renders points sprites upside-down. (Apple bug 11778921)
+ gl->fPointParameterf(LOCAL_GL_POINT_SPRITE_COORD_ORIGIN,
+ LOCAL_GL_LOWER_LEFT);
+ }
+#endif
+
if (gl->IsSupported(gl::GLFeature::seamless_cube_map_opt_in)) {
gl->fEnable(LOCAL_GL_TEXTURE_CUBE_MAP_SEAMLESS);
}
diff --git a/dom/canvas/WebGLProgram.cpp b/dom/canvas/WebGLProgram.cpp
index 1ff27e1d2f..9b204358bb 100644
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -689,6 +689,24 @@ WebGLProgram::GetFragDataLocation(const nsAString& userName_wide) const
gl->MakeCurrent();
const NS_LossyConvertUTF16toASCII userName(userName_wide);
+#ifdef XP_MACOSX
+ if (gl->WorkAroundDriverBugs()) {
+ // OSX doesn't return locs for indexed names, just the base names.
+ // Indicated by failure in: conformance2/programs/gl-get-frag-data-location.html
+ bool isArray;
+ size_t arrayIndex;
+ nsCString baseUserName;
+ if (!ParseName(userName, &baseUserName, &isArray, &arrayIndex))
+ return -1;
+
+ if (arrayIndex >= mContext->mImplMaxDrawBuffers)
+ return -1;
+
+ const auto baseLoc = GetFragDataByUserName(this, baseUserName);
+ const auto loc = baseLoc + GLint(arrayIndex);
+ return loc;
+ }
+#endif
return GetFragDataByUserName(this, userName);
}
@@ -752,6 +770,11 @@ WebGLProgram::GetProgramParameter(GLenum pname) const
return JS::BooleanValue(IsLinked());
case LOCAL_GL_VALIDATE_STATUS:
+#ifdef XP_MACOSX
+ // See comment in ValidateProgram.
+ if (gl->WorkAroundDriverBugs())
+ return JS::BooleanValue(true);
+#endif
// Todo: Implement this in our code.
return JS::BooleanValue(bool(GetProgramiv(gl, mGLName, pname)));
@@ -1354,6 +1377,17 @@ WebGLProgram::ValidateProgram() const
{
mContext->MakeContextCurrent();
gl::GLContext* gl = mContext->gl;
+
+#ifdef XP_MACOSX
+ // See bug 593867 for NVIDIA and bug 657201 for ATI. The latter is confirmed
+ // with Mac OS 10.6.7.
+ if (gl->WorkAroundDriverBugs()) {
+ mContext->GenerateWarning("validateProgram: Implemented as a no-op on"
+ " Mac to work around crashes.");
+ return;
+ }
+#endif
+
gl->fValidateProgram(mGLName);
}
diff --git a/dom/canvas/WebGLShaderValidator.cpp b/dom/canvas/WebGLShaderValidator.cpp
index eadeeb56b0..bf2df82f71 100644
--- a/dom/canvas/WebGLShaderValidator.cpp
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -63,8 +63,34 @@ ChooseValidatorCompileOptions(const ShBuiltInResources& resources,
SH_REGENERATE_STRUCT_NAMES;
}
- // We want to do this everywhere.
+#ifndef XP_MACOSX
+ // We want to do this everywhere, but to do this on Mac, we need
+ // to do it only on Mac OSX > 10.6 as this causes the shader
+ // compiler in 10.6 to crash
options |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS;
+#endif
+
+#ifdef XP_MACOSX
+ if (gl->WorkAroundDriverBugs()) {
+ // Work around https://bugs.webkit.org/show_bug.cgi?id=124684,
+ // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb
+ options |= SH_UNFOLD_SHORT_CIRCUIT;
+
+ // OS X 10.7/10.8 specific:
+
+ // Work around bug 665578 and bug 769810
+ if (gl->Vendor() == gl::GLVendor::ATI) {
+ options |= SH_EMULATE_BUILT_IN_FUNCTIONS;
+ }
+ // Work around bug 735560
+ if (gl->Vendor() == gl::GLVendor::Intel) {
+ options |= SH_EMULATE_BUILT_IN_FUNCTIONS;
+ }
+
+ // Work around that Mac drivers handle struct scopes incorrectly.
+ options |= SH_REGENERATE_STRUCT_NAMES;
+ }
+#endif
if (resources.MaxExpressionComplexity > 0) {
options |= SH_LIMIT_EXPRESSION_COMPLEXITY;
@@ -161,6 +187,15 @@ WebGLContext::CreateShaderValidator(GLenum shaderType) const
// If underlying GLES doesn't have highp in frag shaders, it should complain anyways.
resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1;
+ if (gl->WorkAroundDriverBugs()) {
+#ifdef XP_MACOSX
+ if (gl->Vendor() == gl::GLVendor::NVIDIA) {
+ // Work around bug 890432
+ resources.MaxExpressionComplexity = 1000;
+ }
+#endif
+ }
+
int compileOptions = webgl::ChooseValidatorCompileOptions(resources, gl);
return webgl::ShaderValidator::Create(shaderType, spec, outputLanguage, resources,
compileOptions);
diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp
index 86201bdc30..9f06deb2ba 100644
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -93,6 +93,10 @@
#include "Units.h"
#include "mozilla/layers/APZCTreeManager.h"
+#ifdef XP_MACOSX
+#import <ApplicationServices/ApplicationServices.h>
+#endif
+
namespace mozilla {
using namespace dom;
@@ -1511,6 +1515,14 @@ EventStateManager::FireContextClick()
return;
}
+#ifdef XP_MACOSX
+ // Hack to ensure that we don't show a context menu when the user
+ // let go of the mouse after a long cpu-hogging operation prevented
+ // us from handling any OS events. See bug 117589.
+ if (!CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft))
+ return;
+#endif
+
nsEventStatus status = nsEventStatus_eIgnore;
// Dispatch to the DOM. We have to fake out the ESM and tell it that the
@@ -2823,6 +2835,29 @@ EventStateManager::DecideGestureEvent(WidgetGestureNotifyEvent* aEvent,
aEvent->mPanDirection = panDirection;
}
+#ifdef XP_MACOSX
+static bool
+NodeAllowsClickThrough(nsINode* aNode)
+{
+ while (aNode) {
+ if (aNode->IsXULElement()) {
+ mozilla::dom::Element* element = aNode->AsElement();
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::always, &nsGkAtoms::never, nullptr};
+ switch (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::clickthrough,
+ strings, eCaseMatters)) {
+ case 0:
+ return true;
+ case 1:
+ return false;
+ }
+ }
+ aNode = nsContentUtils::GetCrossDocParentNode(aNode);
+ }
+ return true;
+}
+#endif
+
void
EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
nsEventStatus& aStatus,
@@ -3055,7 +3090,10 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
// focused frame
EnsureDocument(mPresContext);
if (mDocument) {
- fm->ClearFocus(mDocument->GetWindow());
+#ifdef XP_MACOSX
+ if (!activeContent || !activeContent->IsXULElement())
+#endif
+ fm->ClearFocus(mDocument->GetWindow());
fm->SetFocusedWindow(mDocument->GetWindow());
}
}
@@ -3467,6 +3505,18 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
break;
+#ifdef XP_MACOSX
+ case eMouseActivate:
+ if (mCurrentTarget) {
+ nsCOMPtr<nsIContent> targetContent;
+ mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
+ if (!NodeAllowsClickThrough(targetContent)) {
+ *aStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ break;
+#endif
+
default:
break;
}
diff --git a/dom/events/TextComposition.cpp b/dom/events/TextComposition.cpp
index 3b3dc6505b..bd7ebbc468 100644
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -21,8 +21,21 @@
#include "mozilla/Unused.h"
#include "mozilla/dom/TabParent.h"
+#ifdef XP_MACOSX
+// Some defiens will be conflict with OSX SDK
+#define TextRange _TextRange
+#define TextRangeArray _TextRangeArray
+#define Comment _Comment
+#endif
+
#include "nsPluginInstanceOwner.h"
+#ifdef XP_MACOSX
+#undef TextRange
+#undef TextRangeArray
+#undef Comment
+#endif
+
using namespace mozilla::widget;
namespace mozilla {
diff --git a/dom/gamepad/cocoa/CocoaGamepad.cpp b/dom/gamepad/cocoa/CocoaGamepad.cpp
new file mode 100644
index 0000000000..eb6eda9a17
--- /dev/null
+++ b/dom/gamepad/cocoa/CocoaGamepad.cpp
@@ -0,0 +1,590 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+// mostly derived from the Allegro source code at:
+// http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
+
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Unused.h"
+#include "nsThreadUtils.h"
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/hid/IOHIDBase.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+#include <stdio.h>
+#include <vector>
+
+namespace {
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using std::vector;
+
+struct Button {
+ int id;
+ bool analog;
+ IOHIDElementRef element;
+ CFIndex min;
+ CFIndex max;
+
+ Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax) :
+ id(aId),
+ analog((aMax - aMin) > 1),
+ element(aElement),
+ min(aMin),
+ max(aMax) {}
+};
+
+struct Axis {
+ int id;
+ IOHIDElementRef element;
+ uint32_t usagePage;
+ uint32_t usage;
+ CFIndex min;
+ CFIndex max;
+};
+
+typedef bool dpad_buttons[4];
+
+// These values can be found in the USB HID Usage Tables:
+// http://www.usb.org/developers/hidpage
+const unsigned kDesktopUsagePage = 0x01;
+const unsigned kSimUsagePage = 0x02;
+const unsigned kAcceleratorUsage = 0xC4;
+const unsigned kBrakeUsage = 0xC5;
+const unsigned kJoystickUsage = 0x04;
+const unsigned kGamepadUsage = 0x05;
+const unsigned kAxisUsageMin = 0x30;
+const unsigned kAxisUsageMax = 0x35;
+const unsigned kDpadUsage = 0x39;
+const unsigned kButtonUsagePage = 0x09;
+const unsigned kConsumerPage = 0x0C;
+const unsigned kHomeUsage = 0x223;
+const unsigned kBackUsage = 0x224;
+
+
+class Gamepad {
+ private:
+ IOHIDDeviceRef mDevice;
+ nsTArray<Button> buttons;
+ nsTArray<Axis> axes;
+ IOHIDElementRef mDpad;
+ dpad_buttons mDpadState;
+
+ public:
+ Gamepad() : mDevice(nullptr), mDpad(nullptr), mSuperIndex(-1) {}
+ bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
+ bool empty() const { return mDevice == nullptr; }
+ void clear()
+ {
+ mDevice = nullptr;
+ buttons.Clear();
+ axes.Clear();
+ mDpad = nullptr;
+ mSuperIndex = -1;
+ }
+ void init(IOHIDDeviceRef device);
+ size_t numButtons() { return buttons.Length() + (mDpad ? 4 : 0); }
+ size_t numAxes() { return axes.Length(); }
+
+ // Index given by our superclass.
+ uint32_t mSuperIndex;
+
+ bool isDpad(IOHIDElementRef element) const
+ {
+ return element == mDpad;
+ }
+
+ const dpad_buttons& getDpadState() const
+ {
+ return mDpadState;
+ }
+
+ void setDpadState(const dpad_buttons& dpadState)
+ {
+ for (unsigned i = 0; i < ArrayLength(mDpadState); i++) {
+ mDpadState[i] = dpadState[i];
+ }
+ }
+
+ const Button* lookupButton(IOHIDElementRef element) const
+ {
+ for (unsigned i = 0; i < buttons.Length(); i++) {
+ if (buttons[i].element == element)
+ return &buttons[i];
+ }
+ return nullptr;
+ }
+
+ const Axis* lookupAxis(IOHIDElementRef element) const
+ {
+ for (unsigned i = 0; i < axes.Length(); i++) {
+ if (axes[i].element == element)
+ return &axes[i];
+ }
+ return nullptr;
+ }
+};
+
+class AxisComparator {
+public:
+ bool Equals(const Axis& a1, const Axis& a2) const
+ {
+ return a1.usagePage == a2.usagePage && a1.usage == a2.usage;
+ }
+ bool LessThan(const Axis& a1, const Axis& a2) const
+ {
+ if (a1.usagePage == a2.usagePage) {
+ return a1.usage < a2.usage;
+ }
+ return a1.usagePage < a2.usagePage;
+ }
+};
+
+void Gamepad::init(IOHIDDeviceRef device)
+{
+ clear();
+ mDevice = device;
+
+ CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device,
+ nullptr,
+ kIOHIDOptionsTypeNone);
+ CFIndex n = CFArrayGetCount(elements);
+ for (CFIndex i = 0; i < n; i++) {
+ IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements,
+ i);
+ uint32_t usagePage = IOHIDElementGetUsagePage(element);
+ uint32_t usage = IOHIDElementGetUsage(element);
+
+ if (usagePage == kDesktopUsagePage &&
+ usage >= kAxisUsageMin &&
+ usage <= kAxisUsageMax)
+ {
+ Axis axis = { int(axes.Length()),
+ element,
+ usagePage,
+ usage,
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element) };
+ axes.AppendElement(axis);
+ } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
+ // Don't know how to handle d-pads that return weird values.
+ IOHIDElementGetLogicalMax(element) - IOHIDElementGetLogicalMin(element) == 7) {
+ mDpad = element;
+ } else if ((usagePage == kSimUsagePage &&
+ (usage == kAcceleratorUsage ||
+ usage == kBrakeUsage)) ||
+ (usagePage == kButtonUsagePage) ||
+ (usagePage == kConsumerPage &&
+ (usage == kHomeUsage ||
+ usage == kBackUsage))) {
+ Button button(int(buttons.Length()), element, IOHIDElementGetLogicalMin(element), IOHIDElementGetLogicalMax(element));
+ buttons.AppendElement(button);
+ } else {
+ //TODO: handle other usage pages
+ }
+ }
+
+ AxisComparator comparator;
+ axes.Sort(comparator);
+ for (unsigned i = 0; i < axes.Length(); i++) {
+ axes[i].id = i;
+ }
+}
+
+class DarwinGamepadService {
+ private:
+ IOHIDManagerRef mManager;
+ vector<Gamepad> mGamepads;
+
+ //Workaround to support running in background thread
+ CFRunLoopRef mMonitorRunLoop;
+ nsCOMPtr<nsIThread> mMonitorThread;
+
+ static void DeviceAddedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device);
+ static void DeviceRemovedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device);
+ static void InputValueChangedCallback(void* data, IOReturn result,
+ void* sender, IOHIDValueRef newValue);
+
+ void DeviceAdded(IOHIDDeviceRef device);
+ void DeviceRemoved(IOHIDDeviceRef device);
+ void InputValueChanged(IOHIDValueRef value);
+ void StartupInternal();
+
+ public:
+ DarwinGamepadService();
+ ~DarwinGamepadService();
+ void Startup();
+ void Shutdown();
+ friend class DarwinGamepadServiceStartupRunnable;
+};
+
+class DarwinGamepadServiceStartupRunnable final : public Runnable
+{
+ private:
+ ~DarwinGamepadServiceStartupRunnable() {}
+ // This Runnable schedules startup of DarwinGamepadService
+ // in a new thread, pointer to DarwinGamepadService is only
+ // used by this Runnable within its thread.
+ DarwinGamepadService MOZ_NON_OWNING_REF *mService;
+ public:
+ explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService *service)
+ : mService(service) {}
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(mService);
+ mService->StartupInternal();
+ return NS_OK;
+ }
+};
+
+void
+DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ size_t slot = size_t(-1);
+ for (size_t i = 0; i < mGamepads.size(); i++) {
+ if (mGamepads[i] == device)
+ return;
+ if (slot == size_t(-1) && mGamepads[i].empty())
+ slot = i;
+ }
+
+ if (slot == size_t(-1)) {
+ slot = mGamepads.size();
+ mGamepads.push_back(Gamepad());
+ }
+ mGamepads[slot].init(device);
+
+ // Gather some identifying information
+ CFNumberRef vendorIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey));
+ CFNumberRef productIdRef =
+ (CFNumberRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductIDKey));
+ CFStringRef productRef =
+ (CFStringRef)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
+ int vendorId, productId;
+ CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
+ CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
+ char product_name[128];
+ CFStringGetCString(productRef, product_name,
+ sizeof(product_name), kCFStringEncodingASCII);
+ char buffer[256];
+ sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name);
+ uint32_t index = service->AddGamepad(buffer,
+ mozilla::dom::GamepadMappingType::_empty,
+ (int)mGamepads[slot].numButtons(),
+ (int)mGamepads[slot].numAxes());
+ mGamepads[slot].mSuperIndex = index;
+}
+
+void
+DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+ for (size_t i = 0; i < mGamepads.size(); i++) {
+ if (mGamepads[i] == device) {
+ service->RemoveGamepad(mGamepads[i].mSuperIndex);
+ mGamepads[i].clear();
+ return;
+ }
+ }
+}
+
+/*
+ * Given a value from a d-pad (POV hat in USB HID terminology),
+ * represent it as 4 buttons, one for each cardinal direction.
+ */
+static void
+UnpackDpad(int dpad_value, int min, int max, dpad_buttons& buttons)
+{
+ const unsigned kUp = 0;
+ const unsigned kDown = 1;
+ const unsigned kLeft = 2;
+ const unsigned kRight = 3;
+
+ // Different controllers have different ways of representing
+ // "nothing is pressed", but they're all outside the range of values.
+ if (dpad_value < min || dpad_value > max) {
+ // Nothing is pressed.
+ return;
+ }
+
+ // Normalize value to start at 0.
+ int value = dpad_value - min;
+
+ // Value will be in the range 0-7. The value represents the
+ // position of the d-pad around a circle, with 0 being straight up,
+ // 2 being right, 4 being straight down, and 6 being left.
+ if (value < 2 || value > 6) {
+ buttons[kUp] = true;
+ }
+ if (value > 2 && value < 6) {
+ buttons[kDown] = true;
+ }
+ if (value > 4) {
+ buttons[kLeft] = true;
+ }
+ if (value > 0 && value < 4) {
+ buttons[kRight] = true;
+ }
+}
+
+void
+DarwinGamepadService::InputValueChanged(IOHIDValueRef value)
+{
+ RefPtr<GamepadPlatformService> service =
+ GamepadPlatformService::GetParentService();
+ if (!service) {
+ return;
+ }
+
+ uint32_t value_length = IOHIDValueGetLength(value);
+ if (value_length > 4) {
+ // Workaround for bizarre issue with PS3 controllers that try to return
+ // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
+ return;
+ }
+ IOHIDElementRef element = IOHIDValueGetElement(value);
+ IOHIDDeviceRef device = IOHIDElementGetDevice(element);
+
+ for (unsigned i = 0; i < mGamepads.size(); i++) {
+ Gamepad &gamepad = mGamepads[i];
+ if (gamepad == device) {
+ if (gamepad.isDpad(element)) {
+ const dpad_buttons& oldState = gamepad.getDpadState();
+ dpad_buttons newState = { false, false, false, false };
+ UnpackDpad(IOHIDValueGetIntegerValue(value),
+ IOHIDElementGetLogicalMin(element),
+ IOHIDElementGetLogicalMax(element),
+ newState);
+ const int numButtons = gamepad.numButtons();
+ for (unsigned b = 0; b < ArrayLength(newState); b++) {
+ if (newState[b] != oldState[b]) {
+ service->NewButtonEvent(gamepad.mSuperIndex,
+ numButtons - 4 + b,
+ newState[b]);
+ }
+ }
+ gamepad.setDpadState(newState);
+ } else if (const Axis* axis = gamepad.lookupAxis(element)) {
+ double d = IOHIDValueGetIntegerValue(value);
+ double v = 2.0f * (d - axis->min) /
+ (double)(axis->max - axis->min) - 1.0f;
+ service->NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v);
+ } else if (const Button* button = gamepad.lookupButton(element)) {
+ int iv = IOHIDValueGetIntegerValue(value);
+ bool pressed = iv != 0;
+ double v = 0;
+ if (button->analog) {
+ double dv = iv;
+ v = (dv - button->min) / (double)(button->max - button->min);
+ } else {
+ v = pressed ? 1.0 : 0.0;
+ }
+ service->NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v);
+ }
+ return;
+ }
+ }
+}
+
+void
+DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device)
+{
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceAdded(device);
+}
+
+void
+DarwinGamepadService::DeviceRemovedCallback(void* data, IOReturn result,
+ void* sender, IOHIDDeviceRef device)
+{
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->DeviceRemoved(device);
+}
+
+void
+DarwinGamepadService::InputValueChangedCallback(void* data,
+ IOReturn result,
+ void* sender,
+ IOHIDValueRef newValue)
+{
+ DarwinGamepadService* service = (DarwinGamepadService*)data;
+ service->InputValueChanged(newValue);
+}
+
+static CFMutableDictionaryRef
+MatchingDictionary(UInt32 inUsagePage, UInt32 inUsage)
+{
+ CFMutableDictionaryRef dict =
+ CFDictionaryCreateMutable(kCFAllocatorDefault,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (!dict)
+ return nullptr;
+ CFNumberRef number = CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberIntType,
+ &inUsagePage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), number);
+ CFRelease(number);
+
+ number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &inUsage);
+ if (!number) {
+ CFRelease(dict);
+ return nullptr;
+ }
+ CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), number);
+ CFRelease(number);
+
+ return dict;
+}
+
+DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {}
+
+DarwinGamepadService::~DarwinGamepadService()
+{
+ if (mManager != nullptr)
+ CFRelease(mManager);
+}
+
+void
+DarwinGamepadService::StartupInternal()
+{
+ if (mManager != nullptr)
+ return;
+
+ IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault,
+ kIOHIDOptionsTypeNone);
+
+ CFMutableDictionaryRef criteria_arr[2];
+ criteria_arr[0] = MatchingDictionary(kDesktopUsagePage,
+ kJoystickUsage);
+ if (!criteria_arr[0]) {
+ CFRelease(manager);
+ return;
+ }
+
+ criteria_arr[1] = MatchingDictionary(kDesktopUsagePage,
+ kGamepadUsage);
+ if (!criteria_arr[1]) {
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ CFArrayRef criteria =
+ CFArrayCreate(kCFAllocatorDefault, (const void**)criteria_arr, 2, nullptr);
+ if (!criteria) {
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+ CFRelease(manager);
+ return;
+ }
+
+ IOHIDManagerSetDeviceMatchingMultiple(manager, criteria);
+ CFRelease(criteria);
+ CFRelease(criteria_arr[1]);
+ CFRelease(criteria_arr[0]);
+
+ IOHIDManagerRegisterDeviceMatchingCallback(manager,
+ DeviceAddedCallback,
+ this);
+ IOHIDManagerRegisterDeviceRemovalCallback(manager,
+ DeviceRemovedCallback,
+ this);
+ IOHIDManagerRegisterInputValueCallback(manager,
+ InputValueChangedCallback,
+ this);
+ IOHIDManagerScheduleWithRunLoop(manager,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode);
+ IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
+ if (rv != kIOReturnSuccess) {
+ CFRelease(manager);
+ return;
+ }
+
+ mManager = manager;
+
+ // We held the handle of the CFRunLoop to make sure we
+ // can shut it down explicitly by CFRunLoopStop in another
+ // thread.
+ mMonitorRunLoop = CFRunLoopGetCurrent();
+
+ // CFRunLoopRun() is a blocking message loop when it's called in
+ // non-main thread so this thread cannot receive any other runnables
+ // and nsITimer timeout events after it's called.
+ CFRunLoopRun();
+}
+
+void DarwinGamepadService::Startup()
+{
+ Unused << NS_NewThread(getter_AddRefs(mMonitorThread),
+ new DarwinGamepadServiceStartupRunnable(this));
+}
+
+void DarwinGamepadService::Shutdown()
+{
+ IOHIDManagerRef manager = (IOHIDManagerRef)mManager;
+ CFRunLoopStop(mMonitorRunLoop);
+ if (manager) {
+ IOHIDManagerClose(manager, 0);
+ CFRelease(manager);
+ mManager = nullptr;
+ }
+ mMonitorThread->Shutdown();
+}
+
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+DarwinGamepadService* gService = nullptr;
+
+void StartGamepadMonitoring()
+{
+ if (gService) {
+ return;
+ }
+
+ gService = new DarwinGamepadService();
+ gService->Startup();
+}
+
+void StopGamepadMonitoring()
+{
+ if (!gService) {
+ return;
+ }
+
+ gService->Shutdown();
+ delete gService;
+ gService = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/gamepad/moz.build b/dom/gamepad/moz.build
index cee84f4290..1ed5830567 100644
--- a/dom/gamepad/moz.build
+++ b/dom/gamepad/moz.build
@@ -48,6 +48,10 @@ if CONFIG['MOZ_GAMEPAD']:
SOURCES += [
'fallback/FallbackGamepad.cpp'
]
+ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'cocoa':
+ SOURCES += [
+ 'cocoa/CocoaGamepad.cpp'
+ ]
elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'windows':
SOURCES += [
'windows/WindowsGamepad.cpp'
diff --git a/dom/geolocation/moz.build b/dom/geolocation/moz.build
index 4a7d8b17be..441349e5f9 100644
--- a/dom/geolocation/moz.build
+++ b/dom/geolocation/moz.build
@@ -25,7 +25,11 @@ LOCAL_INCLUDES += [
'/dom/ipc',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/dom/system/mac',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
LOCAL_INCLUDES += [
'/dom/system/windows',
]
diff --git a/dom/html/HTMLButtonElement.cpp b/dom/html/HTMLButtonElement.cpp
index 80df5bc740..e493d1891d 100644
--- a/dom/html/HTMLButtonElement.cpp
+++ b/dom/html/HTMLButtonElement.cpp
@@ -156,7 +156,11 @@ HTMLButtonElement::IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t
return true;
}
- *aIsFocusable = !IsDisabled();
+ *aIsFocusable =
+#ifdef XP_MACOSX
+ (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
+#endif
+ !IsDisabled();
return false;
}
diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp
index f7fe0fdcee..b11cd101a0 100644
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -597,6 +597,9 @@ HTMLImageElement::IsHTMLFocusable(bool aWithMouse,
}
*aIsFocusable =
+#ifdef XP_MACOSX
+ (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) &&
+#endif
(tabIndex >= 0 || HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex));
return false;
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index 866a5ac8c5..d9f745d4ca 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -3566,6 +3566,7 @@ HTMLInputElement::Focus(ErrorResult& aError)
return;
}
+#if !defined(XP_MACOSX)
bool
HTMLInputElement::IsNodeApzAwareInternal() const
{
@@ -3574,6 +3575,7 @@ HTMLInputElement::IsNodeApzAwareInternal() const
return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
nsINode::IsNodeApzAwareInternal();
}
+#endif
bool
HTMLInputElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
@@ -4726,6 +4728,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
}
break;
}
+#if !defined(XP_MACOSX)
case eWheel: {
// Handle wheel events as increasing / decreasing the input element's
// value when it's focused and it's type is number or range.
@@ -4759,6 +4762,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
}
break;
}
+#endif
default:
break;
}
@@ -6651,9 +6655,11 @@ FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
void
HTMLInputElement::UpdateApzAwareFlag()
{
+#if !defined(XP_MACOSX)
if ((mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE)) {
SetMayBeApzAware();
}
+#endif
}
nsresult
@@ -7223,7 +7229,11 @@ HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t*
return false;
}
+#ifdef XP_MACOSX
+ const bool defaultFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
+#else
const bool defaultFocusable = true;
+#endif
if (mType == NS_FORM_INPUT_FILE ||
mType == NS_FORM_INPUT_NUMBER ||
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
index d7eccc0756..23c098df3b 100644
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -137,7 +137,9 @@ public:
virtual void Focus(ErrorResult& aError) override;
// nsINode
+#if !defined(XP_MACOSX)
virtual bool IsNodeApzAwareInternal() const override;
+#endif
// Element
virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp
index a0d06a8c65..a77cd6fe57 100644
--- a/dom/html/HTMLObjectElement.cpp
+++ b/dom/html/HTMLObjectElement.cpp
@@ -18,6 +18,11 @@
#include "nsNPAPIPluginInstance.h"
#include "nsIWidget.h"
#include "nsContentUtils.h"
+#ifdef XP_MACOSX
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Event.h"
+#include "nsFocusManager.h"
+#endif
namespace mozilla {
namespace dom {
@@ -39,6 +44,9 @@ HTMLObjectElement::HTMLObjectElement(already_AddRefed<mozilla::dom::NodeInfo>& a
HTMLObjectElement::~HTMLObjectElement()
{
+#ifdef XP_MACOSX
+ OnFocusBlurPlugin(this, false);
+#endif
UnregisterActivityObserver();
DestroyImageLoadingContent();
}
@@ -109,6 +117,131 @@ NS_IMPL_ELEMENT_CLONE(HTMLObjectElement)
// nsIConstraintValidation
NS_IMPL_NSICONSTRAINTVALIDATION(HTMLObjectElement)
+#ifdef XP_MACOSX
+
+static nsIWidget* GetWidget(Element* aElement)
+{
+ return nsContentUtils::WidgetForDocument(aElement->OwnerDoc());
+}
+
+Element* HTMLObjectElement::sLastFocused = nullptr; // Weak
+
+class PluginFocusSetter : public Runnable
+{
+public:
+ PluginFocusSetter(nsIWidget* aWidget, Element* aElement)
+ : mWidget(aWidget), mElement(aElement)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mElement) {
+ HTMLObjectElement::sLastFocused = mElement;
+ bool value = true;
+ mWidget->SetPluginFocused(value);
+ } else if (!HTMLObjectElement::sLastFocused) {
+ bool value = false;
+ mWidget->SetPluginFocused(value);
+ }
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIWidget> mWidget;
+ nsCOMPtr<Element> mElement;
+};
+
+void
+HTMLObjectElement::OnFocusBlurPlugin(Element* aElement, bool aFocus)
+{
+ // In general we don't want to call nsIWidget::SetPluginFocused() for any
+ // Element that doesn't have a plugin running. But if SetPluginFocused(true)
+ // was just called for aElement while it had a plugin running, we want to
+ // make sure nsIWidget::SetPluginFocused(false) gets called for it now, even
+ // if aFocus is true.
+ if (aFocus) {
+ nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(aElement);
+ bool hasRunningPlugin = false;
+ if (olc) {
+ // nsIObjectLoadingContent::GetHasRunningPlugin() fails when
+ // nsContentUtils::IsCallerChrome() returns false (which it can do even
+ // when we're processing a trusted focus event). We work around this by
+ // calling nsObjectLoadingContent::HasRunningPlugin() directly.
+ hasRunningPlugin =
+ static_cast<nsObjectLoadingContent*>(olc.get())->HasRunningPlugin();
+ }
+ if (!hasRunningPlugin) {
+ aFocus = false;
+ }
+ }
+
+ if (aFocus || aElement == sLastFocused) {
+ if (!aFocus) {
+ sLastFocused = nullptr;
+ }
+ nsIWidget* widget = GetWidget(aElement);
+ if (widget) {
+ nsContentUtils::AddScriptRunner(
+ new PluginFocusSetter(widget, aFocus ? aElement : nullptr));
+ }
+ }
+}
+
+void
+HTMLObjectElement::HandlePluginCrashed(Element* aElement)
+{
+ OnFocusBlurPlugin(aElement, false);
+}
+
+void
+HTMLObjectElement::HandlePluginInstantiated(Element* aElement)
+{
+ // If aElement is already focused when a plugin is instantiated, we need
+ // to initiate a call to nsIWidget::SetPluginFocused(true). Otherwise
+ // keyboard input won't work in a click-to-play plugin until aElement
+ // loses focus and regains it.
+ nsIContent* focusedContent = nullptr;
+ nsFocusManager *fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ focusedContent = fm->GetFocusedContent();
+ }
+ if (SameCOMIdentity(focusedContent, aElement)) {
+ OnFocusBlurPlugin(aElement, true);
+ }
+}
+
+void
+HTMLObjectElement::HandleFocusBlurPlugin(Element* aElement,
+ WidgetEvent* aEvent)
+{
+ if (!aEvent->IsTrusted()) {
+ return;
+ }
+ switch (aEvent->mMessage) {
+ case eFocus: {
+ OnFocusBlurPlugin(aElement, true);
+ break;
+ }
+ case eBlur: {
+ OnFocusBlurPlugin(aElement, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+NS_IMETHODIMP
+HTMLObjectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ HandleFocusBlurPlugin(this, aVisitor.mEvent);
+ return NS_OK;
+}
+
+#endif // #ifdef XP_MACOSX
+
NS_IMETHODIMP
HTMLObjectElement::GetForm(nsIDOMHTMLFormElement **aForm)
{
@@ -149,6 +282,14 @@ void
HTMLObjectElement::UnbindFromTree(bool aDeep,
bool aNullParent)
{
+#ifdef XP_MACOSX
+ // When a page is reloaded (when an nsIDocument's content is removed), the
+ // focused element isn't necessarily sent an eBlur event. See
+ // nsFocusManager::ContentRemoved(). This means that a widget may think it
+ // still contains a focused plugin when it doesn't -- which in turn can
+ // disable text input in the browser window. See bug 1137229.
+ OnFocusBlurPlugin(this, false);
+#endif
nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent);
nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
}
diff --git a/dom/html/HTMLObjectElement.h b/dom/html/HTMLObjectElement.h
index 0e69ef5faa..6f0990918f 100644
--- a/dom/html/HTMLObjectElement.h
+++ b/dom/html/HTMLObjectElement.h
@@ -32,6 +32,18 @@ public:
NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLObjectElement, object)
virtual int32_t TabIndexDefault() override;
+#ifdef XP_MACOSX
+ // nsIDOMEventTarget
+ NS_IMETHOD PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+ // Helper methods
+ static void OnFocusBlurPlugin(Element* aElement, bool aFocus);
+ static void HandleFocusBlurPlugin(Element* aElement, WidgetEvent* aEvent);
+ static void HandlePluginCrashed(Element* aElement);
+ static void HandlePluginInstantiated(Element* aElement);
+ // Weak pointer. Null if last action was blur.
+ static Element* sLastFocused;
+#endif
+
// Element
virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
diff --git a/dom/html/HTMLSharedObjectElement.cpp b/dom/html/HTMLSharedObjectElement.cpp
index d7eec215a0..2aeb51573a 100644
--- a/dom/html/HTMLSharedObjectElement.cpp
+++ b/dom/html/HTMLSharedObjectElement.cpp
@@ -16,6 +16,10 @@
#include "nsIScriptError.h"
#include "nsIWidget.h"
#include "nsContentUtils.h"
+#ifdef XP_MACOSX
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/dom/Event.h"
+#endif
#include "mozilla/dom/HTMLObjectElement.h"
@@ -38,6 +42,9 @@ HTMLSharedObjectElement::HTMLSharedObjectElement(already_AddRefed<mozilla::dom::
HTMLSharedObjectElement::~HTMLSharedObjectElement()
{
+#ifdef XP_MACOSX
+ HTMLObjectElement::OnFocusBlurPlugin(this, false);
+#endif
UnregisterActivityObserver();
DestroyImageLoadingContent();
}
@@ -89,6 +96,17 @@ NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
NS_IMPL_ELEMENT_CLONE(HTMLSharedObjectElement)
+#ifdef XP_MACOSX
+
+NS_IMETHODIMP
+HTMLSharedObjectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ HTMLObjectElement::HandleFocusBlurPlugin(this, aVisitor.mEvent);
+ return NS_OK;
+}
+
+#endif // #ifdef XP_MACOSX
+
void
HTMLSharedObjectElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
{
@@ -130,6 +148,14 @@ void
HTMLSharedObjectElement::UnbindFromTree(bool aDeep,
bool aNullParent)
{
+#ifdef XP_MACOSX
+ // When a page is reloaded (when an nsIDocument's content is removed), the
+ // focused element isn't necessarily sent an eBlur event. See
+ // nsFocusManager::ContentRemoved(). This means that a widget may think it
+ // still contains a focused plugin when it doesn't -- which in turn can
+ // disable text input in the browser window. See bug 1137229.
+ HTMLObjectElement::OnFocusBlurPlugin(this, false);
+#endif
nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent);
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
}
diff --git a/dom/html/HTMLSharedObjectElement.h b/dom/html/HTMLSharedObjectElement.h
index 09b44fa115..e550b9927c 100644
--- a/dom/html/HTMLSharedObjectElement.h
+++ b/dom/html/HTMLSharedObjectElement.h
@@ -31,6 +31,11 @@ public:
virtual int32_t TabIndexDefault() override;
+#ifdef XP_MACOSX
+ // nsIDOMEventTarget
+ NS_IMETHOD PostHandleEvent(EventChainPostVisitor& aVisitor) override;
+#endif
+
// nsIDOMHTMLAppletElement
NS_DECL_NSIDOMHTMLAPPLETELEMENT
diff --git a/dom/html/HTMLSummaryElement.cpp b/dom/html/HTMLSummaryElement.cpp
index b9de03fd11..ee3c07b20b 100644
--- a/dom/html/HTMLSummaryElement.cpp
+++ b/dom/html/HTMLSummaryElement.cpp
@@ -116,8 +116,15 @@ HTMLSummaryElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
return disallowOverridingFocusability;
}
- // The main summary element is focusable on all supported platforms.
+#ifdef XP_MACOSX
+ // The parent does not have strong opinion about the focusability of this main
+ // summary element, but we'd like to override it when mouse clicking on Mac OS
+ // like other form elements.
+ *aIsFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
+#else
+ // The main summary element is focusable on other platforms.
*aIsFocusable = true;
+#endif
// Give a chance to allow the subclass to override aIsFocusable.
return false;
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp
index f7340ed7be..0f32d8fb42 100644
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2170,6 +2170,10 @@ nsGenericHTMLFormElement::IsHTMLFocusable(bool aWithMouse,
return true;
}
+#ifdef XP_MACOSX
+ *aIsFocusable =
+ (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) && *aIsFocusable;
+#endif
return false;
}
diff --git a/dom/media/GraphDriver.cpp b/dom/media/GraphDriver.cpp
index daf1ccc989..c4ee2ee2b1 100644
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -14,6 +14,10 @@
#include "webrtc/MediaEngineWebRTC.h"
#endif
+#ifdef XP_MACOSX
+#include <sys/sysctl.h>
+#endif
+
extern mozilla::LazyLogModule gMediaStreamGraphLog;
#define STREAM_LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg)
@@ -577,6 +581,32 @@ AudioCallbackDriver::~AudioCallbackDriver()
MOZ_ASSERT(mPromisesForOperation.IsEmpty());
}
+bool IsMacbookOrMacbookAir()
+{
+#ifdef XP_MACOSX
+ size_t len = 0;
+ sysctlbyname("hw.model", NULL, &len, NULL, 0);
+ if (len) {
+ UniquePtr<char[]> model(new char[len]);
+ // This string can be
+ // MacBook%d,%d for a normal MacBook
+ // MacBookPro%d,%d for a MacBook Pro
+ // MacBookAir%d,%d for a Macbook Air
+ sysctlbyname("hw.model", model.get(), &len, NULL, 0);
+ char* substring = strstr(model.get(), "MacBook");
+ if (substring) {
+ const size_t offset = strlen("MacBook");
+ if (strncmp(model.get() + offset, "Air", len - offset) ||
+ isdigit(model[offset + 1])) {
+ return true;
+ }
+ }
+ return false;
+ }
+#endif
+ return false;
+}
+
void
AudioCallbackDriver::Init()
{
@@ -613,6 +643,13 @@ AudioCallbackDriver::Init()
}
}
+ // Macbook and MacBook air don't have enough CPU to run very low latency
+ // MediaStreamGraphs, cap the minimal latency to 512 frames int this case.
+ if (IsMacbookOrMacbookAir()) {
+ latency_frames = std::max((uint32_t) 512, latency_frames);
+ }
+
+
input = output;
input.channels = mInputChannels; // change to support optional stereo capture
@@ -1026,6 +1063,44 @@ AudioCallbackDriver::MixerCallback(AudioDataValue* aMixedBuffer,
NS_WARNING_ASSERTION(written == aFrames - toWrite, "Dropping frames.");
};
+void AudioCallbackDriver::PanOutputIfNeeded(bool aMicrophoneActive)
+{
+#ifdef XP_MACOSX
+ cubeb_device* out;
+ int rv;
+ char name[128];
+ size_t length = sizeof(name);
+
+ rv = sysctlbyname("hw.model", name, &length, NULL, 0);
+ if (rv) {
+ return;
+ }
+
+ if (!strncmp(name, "MacBookPro", 10)) {
+ if (cubeb_stream_get_current_device(mAudioStream, &out) == CUBEB_OK) {
+ // Check if we are currently outputing sound on external speakers.
+ if (!strcmp(out->output_name, "ispk")) {
+ // Pan everything to the right speaker.
+ if (aMicrophoneActive) {
+ if (cubeb_stream_set_panning(mAudioStream, 1.0) != CUBEB_OK) {
+ NS_WARNING("Could not pan audio output to the right.");
+ }
+ } else {
+ if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) {
+ NS_WARNING("Could not pan audio output to the center.");
+ }
+ }
+ } else {
+ if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) {
+ NS_WARNING("Could not pan audio output to the center.");
+ }
+ }
+ cubeb_stream_device_destroy(mAudioStream, out);
+ }
+ }
+#endif
+}
+
void
AudioCallbackDriver::DeviceChangedCallback() {
// Tell the audio engine the device has changed, it might want to reset some
@@ -1034,6 +1109,9 @@ AudioCallbackDriver::DeviceChangedCallback() {
if (mAudioInput) {
mAudioInput->DeviceChanged();
}
+#ifdef XP_MACOSX
+ PanOutputIfNeeded(mMicrophoneActive);
+#endif
}
void
@@ -1042,6 +1120,10 @@ AudioCallbackDriver::SetMicrophoneActive(bool aActive)
MonitorAutoLock mon(mGraphImpl->GetMonitor());
mMicrophoneActive = aActive;
+
+#ifdef XP_MACOSX
+ PanOutputIfNeeded(mMicrophoneActive);
+#endif
}
uint32_t
diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h
index f2a514b328..bb4f2689b8 100644
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -457,6 +457,11 @@ public:
void CompleteAudioContextOperations(AsyncCubebOperation aOperation);
private:
/**
+ * On certain MacBookPro, the microphone is located near the left speaker.
+ * We need to pan the sound output to the right speaker if we are using the
+ * mic and the built-in speaker, or we will have terrible echo. */
+ void PanOutputIfNeeded(bool aMicrophoneActive);
+ /**
* This is called when the output device used by the cubeb stream changes. */
void DeviceChangedCallback();
/* Start the cubeb stream */
diff --git a/dom/media/eme/MediaKeySystemAccessManager.cpp b/dom/media/eme/MediaKeySystemAccessManager.cpp
index a1e1254ad4..ed31059e22 100644
--- a/dom/media/eme/MediaKeySystemAccessManager.cpp
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -14,6 +14,9 @@
#ifdef XP_WIN
#include "mozilla/WindowsVersion.h"
#endif
+#ifdef XP_MACOSX
+#include "nsCocoaFeatures.h"
+#endif
#include "nsPrintfCString.h"
namespace mozilla {
diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp
index eb18037364..fa6f2f4c83 100644
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -112,12 +112,14 @@ GetPluginFile(const nsAString& aPluginPath,
nsAutoString baseName;
GetFileBase(aPluginPath, aLibDirectory, aLibFile, baseName);
-#if defined(OS_POSIX)
+#if defined(XP_MACOSX)
+ nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".dylib");
+#elif defined(OS_POSIX)
nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so");
#elif defined(XP_WIN)
nsAutoString binaryName = baseName + NS_LITERAL_STRING(".dll");
#else
-#error Unsupported O.S.
+#error not defined
#endif
aLibFile->AppendRelativePath(binaryName);
return true;
diff --git a/dom/media/gmp/rlz/GMPDeviceBinding.cpp b/dom/media/gmp/rlz/GMPDeviceBinding.cpp
index 0871d2e4ee..04def8e8e0 100644
--- a/dom/media/gmp/rlz/GMPDeviceBinding.cpp
+++ b/dom/media/gmp/rlz/GMPDeviceBinding.cpp
@@ -32,6 +32,14 @@
#include "windows.h"
#endif
+#ifdef XP_MACOSX
+#include <assert.h>
+#ifdef HASH_NODE_ID_WITH_DEVICE_ID
+#include <unistd.h>
+#include <mach/mach.h>
+#include <mach/mach_vm.h>
+#endif
+#endif
#endif // HASH_NODE_ID_WITH_DEVICE_ID
@@ -75,6 +83,46 @@ GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom)
}
#endif
+#if defined(XP_MACOSX) && defined(HASH_NODE_ID_WITH_DEVICE_ID)
+static mach_vm_address_t
+RegionContainingAddress(mach_vm_address_t aAddress)
+{
+ mach_port_t task;
+ kern_return_t kr = task_for_pid(mach_task_self(), getpid(), &task);
+ if (kr != KERN_SUCCESS) {
+ return 0;
+ }
+
+ mach_vm_address_t address = aAddress;
+ mach_vm_size_t size;
+ vm_region_basic_info_data_64_t info;
+ mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
+ mach_port_t object_name;
+ kr = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO_64,
+ reinterpret_cast<vm_region_info_t>(&info), &count,
+ &object_name);
+ if (kr != KERN_SUCCESS || size == 0
+ || address > aAddress || address + size <= aAddress) {
+ // mach_vm_region failed, or couldn't find region at given address.
+ return 0;
+ }
+
+ return address;
+}
+
+MOZ_NEVER_INLINE
+static bool
+GetStackAfterCurrentFrame(uint8_t** aOutTop, uint8_t** aOutBottom)
+{
+ mach_vm_address_t stackFrame =
+ reinterpret_cast<mach_vm_address_t>(__builtin_frame_address(0));
+ *aOutTop = reinterpret_cast<uint8_t*>(stackFrame);
+ // Kernel code shows that stack is always a single region.
+ *aOutBottom = reinterpret_cast<uint8_t*>(RegionContainingAddress(stackFrame));
+ return *aOutBottom && (*aOutBottom < *aOutTop);
+}
+#endif
+
#ifdef HASH_NODE_ID_WITH_DEVICE_ID
static void SecureMemset(void* start, uint8_t value, size_t size)
{
diff --git a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
index 513a8998b8..4671499e5d 100644
--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
@@ -26,6 +26,14 @@ public:
static FFmpegLibWrapper sLibAV;
static const char* sLibs[] = {
+#if defined(XP_DARWIN)
+ "libavcodec.58.dylib",
+ "libavcodec.57.dylib",
+ "libavcodec.56.dylib",
+ "libavcodec.55.dylib",
+ "libavcodec.54.dylib",
+ "libavcodec.53.dylib",
+#else
"libavcodec.so.58",
"libavcodec-ffmpeg.so.58",
"libavcodec-ffmpeg.so.57",
@@ -35,6 +43,7 @@ static const char* sLibs[] = {
"libavcodec.so.55",
"libavcodec.so.54",
"libavcodec.so.53",
+#endif
};
/* static */ bool
diff --git a/dom/media/standalone/moz.build b/dom/media/standalone/moz.build
index 5ec7e82c20..7ef15adaa9 100644
--- a/dom/media/standalone/moz.build
+++ b/dom/media/standalone/moz.build
@@ -13,6 +13,9 @@ SOURCES += [
'../VideoSegment.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += ['../systemservices/OSXRunLoopSingleton.cpp']
+
LOCAL_INCLUDES += [
'/caps',
'/dom/base',
diff --git a/dom/media/systemservices/LoadMonitor.cpp b/dom/media/systemservices/LoadMonitor.cpp
index 9ef01999c0..c05d43b1e5 100644
--- a/dom/media/systemservices/LoadMonitor.cpp
+++ b/dom/media/systemservices/LoadMonitor.cpp
@@ -31,6 +31,12 @@
#include <unistd.h>
#endif
+#ifdef XP_MACOSX
+#include <mach/mach_host.h>
+#include <mach/mach_init.h>
+#include <mach/host_info.h>
+#endif
+
#if defined(__DragonFly__) || defined(__FreeBSD__) \
|| defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/sysctl.h>
@@ -407,6 +413,27 @@ nsresult RTCLoadInfo::UpdateSystemLoad()
cpu_times,
&mSystemLoad);
return NS_OK;
+#elif defined(XP_MACOSX)
+ mach_msg_type_number_t info_cnt = HOST_CPU_LOAD_INFO_COUNT;
+ host_cpu_load_info_data_t load_info;
+ kern_return_t rv = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO,
+ (host_info_t)(&load_info), &info_cnt);
+
+ if (rv != KERN_SUCCESS || info_cnt != HOST_CPU_LOAD_INFO_COUNT) {
+ LOG(("Error from mach/host_statistics call"));
+ return NS_ERROR_FAILURE;
+ }
+
+ const uint64_t cpu_times = load_info.cpu_ticks[CPU_STATE_NICE]
+ + load_info.cpu_ticks[CPU_STATE_SYSTEM]
+ + load_info.cpu_ticks[CPU_STATE_USER];
+ const uint64_t total_times = cpu_times + load_info.cpu_ticks[CPU_STATE_IDLE];
+
+ UpdateCpuLoad(mTicksPerInterval,
+ total_times,
+ cpu_times,
+ &mSystemLoad);
+ return NS_OK;
#elif defined(__DragonFly__) || defined(__FreeBSD__) \
|| defined(__NetBSD__) || defined(__OpenBSD__)
#if defined(__NetBSD__)
diff --git a/dom/media/systemservices/OSXRunLoopSingleton.cpp b/dom/media/systemservices/OSXRunLoopSingleton.cpp
new file mode 100644
index 0000000000..6211d5c127
--- /dev/null
+++ b/dom/media/systemservices/OSXRunLoopSingleton.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; 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 "OSXRunLoopSingleton.h"
+#include <mozilla/StaticMutex.h>
+
+#include <AudioUnit/AudioUnit.h>
+#include <CoreAudio/AudioHardware.h>
+#include <CoreAudio/HostTime.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+static bool gRunLoopSet = false;
+static mozilla::StaticMutex gMutex;
+
+void mozilla_set_coreaudio_notification_runloop_if_needed()
+{
+ mozilla::StaticMutexAutoLock lock(gMutex);
+ if (gRunLoopSet) {
+ return;
+ }
+
+ /* This is needed so that AudioUnit listeners get called on this thread, and
+ * not the main thread. If we don't do that, they are not called, or a crash
+ * occur, depending on the OSX version. */
+ AudioObjectPropertyAddress runloop_address = {
+ kAudioHardwarePropertyRunLoop,
+ kAudioObjectPropertyScopeGlobal,
+ kAudioObjectPropertyElementMaster
+ };
+
+ CFRunLoopRef run_loop = nullptr;
+
+ OSStatus r;
+ r = AudioObjectSetPropertyData(kAudioObjectSystemObject,
+ &runloop_address,
+ 0, NULL, sizeof(CFRunLoopRef), &run_loop);
+ if (r != noErr) {
+ NS_WARNING("Could not make global CoreAudio notifications use their own thread.");
+ }
+
+ gRunLoopSet = true;
+}
diff --git a/dom/media/systemservices/OSXRunLoopSingleton.h b/dom/media/systemservices/OSXRunLoopSingleton.h
new file mode 100644
index 0000000000..d06365e146
--- /dev/null
+++ b/dom/media/systemservices/OSXRunLoopSingleton.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef OSXRUNLOOPSINGLETON_H_
+#define OSXRUNLOOPSINGLETON_H_
+
+#include <mozilla/Types.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* This function tells CoreAudio to use its own thread for device change
+ * notifications, and can be called from any thread without external
+ * synchronization. */
+void MOZ_EXPORT
+mozilla_set_coreaudio_notification_runloop_if_needed();
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // OSXRUNLOOPSINGLETON_H_
diff --git a/dom/media/systemservices/moz.build b/dom/media/systemservices/moz.build
index 8fb5e54a19..402a6988c0 100644
--- a/dom/media/systemservices/moz.build
+++ b/dom/media/systemservices/moz.build
@@ -29,6 +29,10 @@ if CONFIG['OS_TARGET'] == 'WINNT':
else:
DEFINES['WEBRTC_POSIX'] = True
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += ['OSXRunLoopSingleton.cpp']
+ EXPORTS += ['OSXRunLoopSingleton.h']
+
if CONFIG['_MSC_VER']:
DEFINES['__PRETTY_FUNCTION__'] = '__FUNCSIG__'
diff --git a/dom/media/webaudio/AudioContext.cpp b/dom/media/webaudio/AudioContext.cpp
index 3c7958349b..75f57a630b 100755
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -873,6 +873,7 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
}
#ifndef WIN32 // Bug 1170547
+#ifndef XP_MACOSX
#ifdef DEBUG
if (!((mAudioContextState == AudioContextState::Suspended &&
@@ -891,6 +892,7 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
}
#endif // DEBUG
+#endif // XP_MACOSX
#endif // WIN32
MOZ_ASSERT(
diff --git a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
index e63a9afded..e1e572724f 100644
--- a/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineCameraVideoSource.cpp
@@ -339,6 +339,13 @@ MediaEngineCameraVideoSource::SetName(nsString aName)
facingMode = VideoFacingModeEnum::User;
}
#endif // ANDROID
+#ifdef XP_MACOSX
+ // Kludge to test user-facing cameras on OSX.
+ if (aName.Find(NS_LITERAL_STRING("Face")) != -1) {
+ hasFacingMode = true;
+ facingMode = VideoFacingModeEnum::User;
+ }
+#endif
#ifdef XP_WIN
// The cameras' name of Surface book are "Microsoft Camera Front" and
// "Microsoft Camera Rear" respectively.
diff --git a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp
new file mode 100644
index 0000000000..ef69170003
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; 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 "mozilla/ModuleUtils.h"
+#include "nsIClassInfoImpl.h"
+
+#include "OSXSpeechSynthesizerService.h"
+
+using namespace mozilla::dom;
+
+#define OSXSPEECHSYNTHESIZERSERVICE_CID \
+ {0x914e73b4, 0x6337, 0x4bef, {0x97, 0xf3, 0x4d, 0x06, 0x9e, 0x05, 0x3a, 0x12}}
+
+#define OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID "@mozilla.org/synthsystem;1"
+
+// Defines OSXSpeechSynthesizerServiceConstructor
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(OSXSpeechSynthesizerService,
+ OSXSpeechSynthesizerService::GetInstanceForService)
+
+// Defines kOSXSERVICE_CID
+NS_DEFINE_NAMED_CID(OSXSPEECHSYNTHESIZERSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kCIDs[] = {
+ { &kOSXSPEECHSYNTHESIZERSERVICE_CID, true, nullptr, OSXSpeechSynthesizerServiceConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kContracts[] = {
+ { OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID, &kOSXSPEECHSYNTHESIZERSERVICE_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kCategories[] = {
+ { "speech-synth-started", "OSX Speech Synth", OSXSPEECHSYNTHESIZERSERVICE_CONTRACTID },
+ { nullptr }
+};
+
+static void
+UnloadOSXSpeechSynthesizerModule()
+{
+ OSXSpeechSynthesizerService::Shutdown();
+}
+
+static const mozilla::Module kModule = {
+ mozilla::Module::kVersion,
+ kCIDs,
+ kContracts,
+ kCategories,
+ nullptr,
+ nullptr,
+ UnloadOSXSpeechSynthesizerModule
+};
+
+NSMODULE_DEFN(osxsynth) = &kModule;
diff --git a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h
new file mode 100644
index 0000000000..ba04f0fecb
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_dom_OsxSpeechSynthesizerService_h
+#define mozilla_dom_OsxSpeechSynthesizerService_h
+
+#include "nsISpeechService.h"
+#include "nsIObserver.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class OSXSpeechSynthesizerService final : public nsISpeechService
+ , public nsIObserver
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISPEECHSERVICE
+ NS_DECL_NSIOBSERVER
+
+ bool Init();
+
+ static OSXSpeechSynthesizerService* GetInstance();
+ static already_AddRefed<OSXSpeechSynthesizerService> GetInstanceForService();
+ static void Shutdown();
+
+private:
+ OSXSpeechSynthesizerService();
+ virtual ~OSXSpeechSynthesizerService();
+
+ bool RegisterVoices();
+
+ bool mInitialized;
+ static mozilla::StaticRefPtr<OSXSpeechSynthesizerService> sSingleton;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
new file mode 100644
index 0000000000..ec752e00f0
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -0,0 +1,498 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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 "nsISupports.h"
+#include "nsServiceManagerUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/dom/nsSynthVoiceRegistry.h"
+#include "mozilla/dom/nsSpeechTask.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Assertions.h"
+#include "OSXSpeechSynthesizerService.h"
+
+#import <Cocoa/Cocoa.h>
+
+// We can escape the default delimiters ("[[" and "]]") by temporarily
+// changing the delimiters just before they appear, and changing them back
+// just after.
+#define DLIM_ESCAPE_START "[[dlim (( ))]]"
+#define DLIM_ESCAPE_END "((dlim [[ ]]))"
+
+using namespace mozilla;
+
+class SpeechTaskCallback final : public nsISpeechTaskCallback
+{
+public:
+ SpeechTaskCallback(nsISpeechTask* aTask,
+ NSSpeechSynthesizer* aSynth,
+ const nsTArray<size_t>& aOffsets)
+ : mTask(aTask)
+ , mSpeechSynthesizer(aSynth)
+ , mOffsets(aOffsets)
+ {
+ mStartingTime = TimeStamp::Now();
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback)
+
+ NS_DECL_NSISPEECHTASKCALLBACK
+
+ void OnWillSpeakWord(uint32_t aIndex);
+ void OnError(uint32_t aIndex);
+ void OnDidFinishSpeaking();
+
+private:
+ virtual ~SpeechTaskCallback()
+ {
+ [mSpeechSynthesizer release];
+ }
+
+ float GetTimeDurationFromStart();
+
+ nsCOMPtr<nsISpeechTask> mTask;
+ NSSpeechSynthesizer* mSpeechSynthesizer;
+ TimeStamp mStartingTime;
+ uint32_t mCurrentIndex;
+ nsTArray<size_t> mOffsets;
+};
+
+NS_IMPL_CYCLE_COLLECTION(SpeechTaskCallback, mTask);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechTaskCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechTaskCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechTaskCallback)
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnCancel()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [mSpeechSynthesizer stopSpeaking];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnPause()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [mSpeechSynthesizer pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
+ if (!mTask) {
+ // When calling pause() on child porcess, it may not receive end event
+ // from chrome process yet.
+ return NS_ERROR_FAILURE;
+ }
+ mTask->DispatchPause(GetTimeDurationFromStart(), mCurrentIndex);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnResume()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [mSpeechSynthesizer continueSpeaking];
+ if (!mTask) {
+ // When calling resume() on child porcess, it may not receive end event
+ // from chrome process yet.
+ return NS_ERROR_FAILURE;
+ }
+ mTask->DispatchResume(GetTimeDurationFromStart(), mCurrentIndex);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+SpeechTaskCallback::OnVolumeChanged(float aVolume)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [mSpeechSynthesizer setObject:[NSNumber numberWithFloat:aVolume]
+ forProperty:NSSpeechVolumeProperty error:nil];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+float
+SpeechTaskCallback::GetTimeDurationFromStart()
+{
+ TimeDuration duration = TimeStamp::Now() - mStartingTime;
+ return duration.ToMilliseconds();
+}
+
+void
+SpeechTaskCallback::OnWillSpeakWord(uint32_t aIndex)
+{
+ mCurrentIndex = aIndex < mOffsets.Length() ? mOffsets[aIndex] : mCurrentIndex;
+ if (!mTask) {
+ return;
+ }
+ mTask->DispatchBoundary(NS_LITERAL_STRING("word"),
+ GetTimeDurationFromStart(), mCurrentIndex);
+}
+
+void
+SpeechTaskCallback::OnError(uint32_t aIndex)
+{
+ if (!mTask) {
+ return;
+ }
+ mTask->DispatchError(GetTimeDurationFromStart(), aIndex);
+}
+
+void
+SpeechTaskCallback::OnDidFinishSpeaking()
+{
+ mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
+ // no longer needed
+ [mSpeechSynthesizer setDelegate:nil];
+ mTask = nullptr;
+}
+
+@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
+{
+@private
+ SpeechTaskCallback* mCallback;
+}
+
+ - (id)initWithCallback:(SpeechTaskCallback*)aCallback;
+@end
+
+@implementation SpeechDelegate
+- (id)initWithCallback:(SpeechTaskCallback*)aCallback
+{
+ [super init];
+ mCallback = aCallback;
+ return self;
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+ willSpeakWord:(NSRange)aRange ofString:(NSString*)aString
+{
+ mCallback->OnWillSpeakWord(aRange.location);
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)aSender
+ didFinishSpeaking:(BOOL)aFinishedSpeaking
+{
+ mCallback->OnDidFinishSpeaking();
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer*)aSender
+ didEncounterErrorAtIndex:(NSUInteger)aCharacterIndex
+ ofString:(NSString*)aString
+ message:(NSString*)aMessage
+{
+ mCallback->OnError(aCharacterIndex);
+}
+@end
+
+namespace mozilla {
+namespace dom {
+
+struct OSXVoice
+{
+ OSXVoice() : mIsDefault(false)
+ {
+ }
+
+ nsString mUri;
+ nsString mName;
+ nsString mLocale;
+ bool mIsDefault;
+};
+
+class RegisterVoicesRunnable final : public Runnable
+{
+public:
+ RegisterVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService,
+ nsTArray<OSXVoice>& aList)
+ : mSpeechService(aSpeechService)
+ , mVoices(aList)
+ {
+ }
+
+ NS_IMETHOD Run() override;
+
+private:
+ ~RegisterVoicesRunnable()
+ {
+ }
+
+ // This runnable always use sync mode. It is unnecesarry to reference object
+ OSXSpeechSynthesizerService* mSpeechService;
+ nsTArray<OSXVoice>& mVoices;
+};
+
+NS_IMETHODIMP
+RegisterVoicesRunnable::Run()
+{
+ nsresult rv;
+ nsCOMPtr<nsISynthVoiceRegistry> registry =
+ do_GetService(NS_SYNTHVOICEREGISTRY_CONTRACTID, &rv);
+ if (!registry) {
+ return rv;
+ }
+
+ for (OSXVoice voice : mVoices) {
+ rv = registry->AddVoice(mSpeechService, voice.mUri, voice.mName, voice.mLocale, true, false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ if (voice.mIsDefault) {
+ registry->SetDefaultVoice(voice.mUri, true);
+ }
+ }
+
+ registry->NotifyVoicesChanged();
+
+ return NS_OK;
+}
+
+class EnumVoicesRunnable final : public Runnable
+{
+public:
+ explicit EnumVoicesRunnable(OSXSpeechSynthesizerService* aSpeechService)
+ : mSpeechService(aSpeechService)
+ {
+ }
+
+ NS_IMETHOD Run() override;
+
+private:
+ ~EnumVoicesRunnable()
+ {
+ }
+
+ RefPtr<OSXSpeechSynthesizerService> mSpeechService;
+};
+
+NS_IMETHODIMP
+EnumVoicesRunnable::Run()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoTArray<OSXVoice, 64> list;
+
+ NSArray* voices = [NSSpeechSynthesizer availableVoices];
+ NSString* defaultVoice = [NSSpeechSynthesizer defaultVoice];
+
+ for (NSString* voice in voices) {
+ OSXVoice item;
+
+ NSDictionary* attr = [NSSpeechSynthesizer attributesForVoice:voice];
+
+ nsAutoString identifier;
+ nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceIdentifier],
+ identifier);
+
+ nsCocoaUtils::GetStringForNSString([attr objectForKey:NSVoiceName], item.mName);
+
+ nsCocoaUtils::GetStringForNSString(
+ [attr objectForKey:NSVoiceLocaleIdentifier], item.mLocale);
+ item.mLocale.ReplaceChar('_', '-');
+
+ item.mUri.AssignLiteral("urn:moz-tts:osx:");
+ item.mUri.Append(identifier);
+
+ if ([voice isEqualToString:defaultVoice]) {
+ item.mIsDefault = true;
+ }
+
+ list.AppendElement(item);
+ }
+
+ RefPtr<RegisterVoicesRunnable> runnable = new RegisterVoicesRunnable(mSpeechService, list);
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+StaticRefPtr<OSXSpeechSynthesizerService> OSXSpeechSynthesizerService::sSingleton;
+
+NS_INTERFACE_MAP_BEGIN(OSXSpeechSynthesizerService)
+ NS_INTERFACE_MAP_ENTRY(nsISpeechService)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechService)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(OSXSpeechSynthesizerService)
+NS_IMPL_RELEASE(OSXSpeechSynthesizerService)
+
+OSXSpeechSynthesizerService::OSXSpeechSynthesizerService()
+ : mInitialized(false)
+{
+}
+
+OSXSpeechSynthesizerService::~OSXSpeechSynthesizerService()
+{
+}
+
+bool
+OSXSpeechSynthesizerService::Init()
+{
+ if (Preferences::GetBool("media.webspeech.synth.test") ||
+ !Preferences::GetBool("media.webspeech.synth.enabled")) {
+ // When test is enabled, we shouldn't add OS backend (Bug 1160844)
+ return false;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ if (NS_FAILED(NS_NewNamedThread("SpeechWorker", getter_AddRefs(thread)))) {
+ return false;
+ }
+
+ // Get all the voices and register in the SynthVoiceRegistry
+ nsCOMPtr<nsIRunnable> runnable = new EnumVoicesRunnable(this);
+ thread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+
+ mInitialized = true;
+ return true;
+}
+
+NS_IMETHODIMP
+OSXSpeechSynthesizerService::Speak(const nsAString& aText,
+ const nsAString& aUri,
+ float aVolume,
+ float aRate,
+ float aPitch,
+ nsISpeechTask* aTask)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ MOZ_ASSERT(StringBeginsWith(aUri, NS_LITERAL_STRING("urn:moz-tts:osx:")),
+ "OSXSpeechSynthesizerService doesn't allow this voice URI");
+
+ NSSpeechSynthesizer* synth = [[NSSpeechSynthesizer alloc] init];
+ // strlen("urn:moz-tts:osx:") == 16
+ NSString* identifier = nsCocoaUtils::ToNSString(Substring(aUri, 16));
+ [synth setVoice:identifier];
+
+ // default rate is 180-220
+ [synth setObject:[NSNumber numberWithInt:aRate * 200]
+ forProperty:NSSpeechRateProperty error:nil];
+ // volume allows 0.0-1.0
+ [synth setObject:[NSNumber numberWithFloat:aVolume]
+ forProperty:NSSpeechVolumeProperty error:nil];
+ // Use default pitch value to calculate this
+ NSNumber* defaultPitch =
+ [synth objectForProperty:NSSpeechPitchBaseProperty error:nil];
+ if (defaultPitch) {
+ int newPitch = [defaultPitch intValue] * (aPitch / 2 + 0.5);
+ [synth setObject:[NSNumber numberWithInt:newPitch]
+ forProperty:NSSpeechPitchBaseProperty error:nil];
+ }
+
+ nsAutoString escapedText;
+ // We need to map the the offsets from the given text to the escaped text.
+ // The index of the offsets array is the position in the escaped text,
+ // the element value is the position in the user-supplied text.
+ nsTArray<size_t> offsets;
+ offsets.SetCapacity(aText.Length());
+
+ // This loop looks for occurances of "[[" or "]]", escapes them, and
+ // populates the offsets array to supply a map to the original offsets.
+ for (size_t i = 0; i < aText.Length(); i++) {
+ if (aText.Length() > i + 1 &&
+ ((aText[i] == ']' && aText[i+1] == ']') ||
+ (aText[i] == '[' && aText[i+1] == '['))) {
+ escapedText.AppendLiteral(DLIM_ESCAPE_START);
+ offsets.AppendElements(strlen(DLIM_ESCAPE_START));
+ escapedText.Append(aText[i]);
+ offsets.AppendElement(i);
+ escapedText.Append(aText[++i]);
+ offsets.AppendElement(i);
+ escapedText.AppendLiteral(DLIM_ESCAPE_END);
+ offsets.AppendElements(strlen(DLIM_ESCAPE_END));
+ } else {
+ escapedText.Append(aText[i]);
+ offsets.AppendElement(i);
+ }
+ }
+
+ RefPtr<SpeechTaskCallback> callback = new SpeechTaskCallback(aTask, synth, offsets);
+ nsresult rv = aTask->Setup(callback, 0, 0, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SpeechDelegate* delegate = [[SpeechDelegate alloc] initWithCallback:callback];
+ [synth setDelegate:delegate];
+ [delegate release ];
+
+ NSString* text = nsCocoaUtils::ToNSString(escapedText);
+ BOOL success = [synth startSpeakingString:text];
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+ aTask->DispatchStart();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXSpeechSynthesizerService::GetServiceType(SpeechServiceType* aServiceType)
+{
+ *aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+OSXSpeechSynthesizerService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ return NS_OK;
+}
+
+OSXSpeechSynthesizerService*
+OSXSpeechSynthesizerService::GetInstance()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (XRE_GetProcessType() != GeckoProcessType_Default) {
+ return nullptr;
+ }
+
+ if (!sSingleton) {
+ RefPtr<OSXSpeechSynthesizerService> speechService =
+ new OSXSpeechSynthesizerService();
+ if (speechService->Init()) {
+ sSingleton = speechService;
+ }
+ }
+ return sSingleton;
+}
+
+already_AddRefed<OSXSpeechSynthesizerService>
+OSXSpeechSynthesizerService::GetInstanceForService()
+{
+ RefPtr<OSXSpeechSynthesizerService> speechService = GetInstance();
+ return speechService.forget();
+}
+
+void
+OSXSpeechSynthesizerService::Shutdown()
+{
+ if (!sSingleton) {
+ return;
+ }
+ sSingleton = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/webspeech/synth/cocoa/moz.build b/dom/media/webspeech/synth/cocoa/moz.build
new file mode 100644
index 0000000000..6953a81e95
--- /dev/null
+++ b/dom/media/webspeech/synth/cocoa/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+ 'OSXSpeechSynthesizerModule.cpp',
+ 'OSXSpeechSynthesizerService.mm'
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/webspeech/synth/moz.build b/dom/media/webspeech/synth/moz.build
index 9784686dfe..6603689261 100644
--- a/dom/media/webspeech/synth/moz.build
+++ b/dom/media/webspeech/synth/moz.build
@@ -32,6 +32,9 @@ SOURCES += [
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
DIRS += ['windows']
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += ['cocoa']
+
if CONFIG['MOZ_SYNTH_SPEECHD']:
DIRS += ['speechd']
diff --git a/dom/moz.build b/dom/moz.build
index 17b0fed15b..ad278beb9c 100644
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -111,6 +111,6 @@ TEST_DIRS += [
'imptests',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'windows'):
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'cocoa', 'windows'):
TEST_DIRS += ['plugins/test']
diff --git a/dom/plugins/base/PluginPRLibrary.cpp b/dom/plugins/base/PluginPRLibrary.cpp
index e6a202c244..57c6c57ab3 100644
--- a/dom/plugins/base/PluginPRLibrary.cpp
+++ b/dom/plugins/base/PluginPRLibrary.cpp
@@ -24,7 +24,7 @@ static int gNotOptimized;
using namespace mozilla::layers;
namespace mozilla {
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
nsresult
PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs,
NPPluginFuncs* pFuncs, NPError* error)
@@ -110,7 +110,7 @@ nsresult
PluginPRLibrary::NP_GetValue(void *future, NPPVariable aVariable,
void *aValue, NPError* error)
{
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
if (mNP_GetValue) {
*error = mNP_GetValue(future, aVariable, aValue);
} else {
@@ -125,7 +125,7 @@ PluginPRLibrary::NP_GetValue(void *future, NPPVariable aVariable,
#endif
}
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
nsresult
PluginPRLibrary::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error)
{
@@ -231,7 +231,17 @@ PluginPRLibrary::GetImageContainer(NPP instance, ImageContainer** aContainer)
return NS_ERROR_NOT_IMPLEMENTED;
}
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+nsresult
+PluginPRLibrary::IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing)
+{
+ nsNPAPIPluginInstance* inst = (nsNPAPIPluginInstance*)instance->ndata;
+ NS_ENSURE_TRUE(inst, NS_ERROR_NULL_POINTER);
+ *aDrawing = false;
+ return NS_OK;
+}
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsresult
PluginPRLibrary::ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor)
{
diff --git a/dom/plugins/base/PluginPRLibrary.h b/dom/plugins/base/PluginPRLibrary.h
index 4934b37bbb..ffd3de934e 100644
--- a/dom/plugins/base/PluginPRLibrary.h
+++ b/dom/plugins/base/PluginPRLibrary.h
@@ -17,17 +17,17 @@ class PluginPRLibrary : public PluginLibrary
{
public:
PluginPRLibrary(const char* aFilePath, PRLibrary* aLibrary) :
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
mNP_Initialize(nullptr),
#else
mNP_Initialize(nullptr),
#endif
mNP_Shutdown(nullptr),
mNP_GetMIMEDescription(nullptr),
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
mNP_GetValue(nullptr),
#endif
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
mNP_GetEntryPoints(nullptr),
#endif
mNPP_New(nullptr),
@@ -60,17 +60,19 @@ public:
mNP_GetMIMEDescription = (NP_GetMIMEDescriptionFunc)
PR_FindFunctionSymbol(mLibrary, "NP_GetMIMEDescription");
+#ifndef XP_MACOSX
if (!mNP_GetMIMEDescription)
return false;
+#endif
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
mNP_GetValue = (NP_GetValueFunc)
PR_FindFunctionSymbol(mLibrary, "NP_GetValue");
if (!mNP_GetValue)
return false;
#endif
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
mNP_GetEntryPoints = (NP_GetEntryPointsFunc)
PR_FindFunctionSymbol(mLibrary, "NP_GetEntryPoints");
if (!mNP_GetEntryPoints)
@@ -79,7 +81,7 @@ public:
return true;
}
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
virtual nsresult NP_Initialize(NPNetscapeFuncs* aNetscapeFuncs,
NPPluginFuncs* aFuncs, NPError* aError) override;
#else
@@ -93,7 +95,7 @@ public:
virtual nsresult NP_GetValue(void* aFuture, NPPVariable aVariable,
void* aValue, NPError* aError) override;
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
virtual nsresult NP_GetEntryPoints(NPPluginFuncs* aFuncs, NPError* aError) override;
#endif
@@ -110,7 +112,10 @@ public:
virtual nsresult GetImageContainer(NPP aInstance, mozilla::layers::ImageContainer** aContainer) override;
virtual nsresult GetImageSize(NPP aInstance, nsIntSize* aSize) override;
virtual bool IsOOP() override { return false; }
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+ virtual nsresult IsRemoteDrawingCoreAnimation(NPP aInstance, bool* aDrawing) override;
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
virtual nsresult ContentsScaleFactorChanged(NPP aInstance, double aContentsScaleFactor) override;
#endif
virtual nsresult SetBackgroundUnknown(NPP instance) override;
@@ -134,10 +139,10 @@ private:
NP_InitializeFunc mNP_Initialize;
NP_ShutdownFunc mNP_Shutdown;
NP_GetMIMEDescriptionFunc mNP_GetMIMEDescription;
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
NP_GetValueFunc mNP_GetValue;
#endif
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
NP_GetEntryPointsFunc mNP_GetEntryPoints;
#endif
NPP_NewProcPtr mNPP_New;
diff --git a/dom/plugins/base/moz.build b/dom/plugins/base/moz.build
index 11649a8626..08f87d56a3 100644
--- a/dom/plugins/base/moz.build
+++ b/dom/plugins/base/moz.build
@@ -56,6 +56,11 @@ if CONFIG['OS_ARCH'] == 'WINNT':
'nsPluginNativeWindowWin.cpp',
'nsPluginsDirWin.cpp',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsPluginNativeWindow.cpp',
+ 'nsPluginsDirDarwin.cpp',
+ ]
else:
SOURCES += [
'nsPluginsDirUnix.cpp',
@@ -77,6 +82,7 @@ LOCAL_INCLUDES += [
'/layout/xul',
'/netwerk/base',
'/widget',
+ '/widget/cocoa',
'/xpcom/base',
]
diff --git a/dom/plugins/base/npapi.h b/dom/plugins/base/npapi.h
index de58f818c4..aa6a66b2bd 100644
--- a/dom/plugins/base/npapi.h
+++ b/dom/plugins/base/npapi.h
@@ -22,6 +22,25 @@
#endif
#endif
+#if defined(__APPLE_CC__) && !defined(XP_UNIX)
+#ifndef XP_MACOSX
+#define XP_MACOSX 1
+#endif
+#endif
+
+#if defined(XP_MACOSX) && defined(__LP64__)
+#define NP_NO_QUICKDRAW
+#define NP_NO_CARBON
+#endif
+
+#if defined(XP_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#include <OpenGL/OpenGL.h>
+#ifndef NP_NO_CARBON
+#include <Carbon/Carbon.h>
+#endif
+#endif
+
#if defined(XP_UNIX)
#include <stdio.h>
#if defined(MOZ_X11)
@@ -98,6 +117,12 @@ typedef char* NPMIMEType;
/* Structures and definitions */
/*----------------------------------------------------------------------*/
+#if !defined(__LP64__)
+#if defined(XP_MACOSX)
+#pragma options align=mac68k
+#endif
+#endif /* __LP64__ */
+
/*
* NPP is a plug-in's opaque instance handle
*/
@@ -255,6 +280,15 @@ typedef struct _NPAudioDeviceChangeDetails
typedef enum {
NPDrawingModelDUMMY
+#if defined(XP_MACOSX)
+#ifndef NP_NO_QUICKDRAW
+ , NPDrawingModelQuickDraw = 0
+#endif
+ , NPDrawingModelCoreGraphics = 1
+ , NPDrawingModelOpenGL = 2
+ , NPDrawingModelCoreAnimation = 3
+ , NPDrawingModelInvalidatingCoreAnimation = 4
+#endif
#if defined(XP_WIN)
, NPDrawingModelSyncWin = 5
#endif
@@ -267,6 +301,15 @@ typedef enum {
#endif
} NPDrawingModel;
+#ifdef XP_MACOSX
+typedef enum {
+#ifndef NP_NO_CARBON
+ NPEventModelCarbon = 0,
+#endif
+ NPEventModelCocoa = 1
+} NPEventModel;
+#endif
+
/*
* The following masks are applied on certain platforms to NPNV and
* NPPV selectors that pass around pointers to COM interfaces. Newer
@@ -294,7 +337,12 @@ typedef enum {
#define _NP_ABI_MIXIN_FOR_GCC3 0
#endif
+#if defined(XP_MACOSX)
+#define NP_ABI_MACHO_MASK 0x01000000
+#define _NP_ABI_MIXIN_FOR_MACHO NP_ABI_MACHO_MASK
+#else
#define _NP_ABI_MIXIN_FOR_MACHO 0
+#endif
#define NP_ABI_MASK (_NP_ABI_MIXIN_FOR_GCC3 | _NP_ABI_MIXIN_FOR_MACHO)
@@ -345,6 +393,12 @@ typedef enum {
/* Used for negotiating drawing models */
NPPVpluginDrawingModel = 1000
+#if defined(XP_MACOSX)
+ /* Used for negotiating event models */
+ , NPPVpluginEventModel = 1001
+ /* In the NPDrawingModelCoreAnimation drawing model, the browser asks the plug-in for a Core Animation layer. */
+ , NPPVpluginCoreAnimationLayer = 1003
+#endif
/* Notification that the plugin just started or stopped playing audio */
, NPPVpluginIsPlayingAudio = 4000
#if defined(XP_WIN)
@@ -388,18 +442,39 @@ typedef enum {
NPNVCSSZoomFactor = 23,
NPNVpluginDrawingModel = 1000 /* Get the current drawing model (NPDrawingModel) */
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
, NPNVcontentsScaleFactor = 1001
#endif
+#if defined(XP_MACOSX)
+#ifndef NP_NO_QUICKDRAW
+ , NPNVsupportsQuickDrawBool = 2000
+#endif
+ , NPNVsupportsCoreGraphicsBool = 2001
+ , NPNVsupportsOpenGLBool = 2002
+ , NPNVsupportsCoreAnimationBool = 2003
+ , NPNVsupportsInvalidatingCoreAnimationBool = 2004
+#endif
, NPNVsupportsAsyncBitmapSurfaceBool = 2007
#if defined(XP_WIN)
, NPNVsupportsAsyncWindowsDXGISurfaceBool = 2008
, NPNVpreferredDXGIAdapter = 2009
#endif
+#if defined(XP_MACOSX)
+#ifndef NP_NO_CARBON
+ , NPNVsupportsCarbonBool = 3000 /* TRUE if the browser supports the Carbon event model */
+#endif
+ , NPNVsupportsCocoaBool = 3001 /* TRUE if the browser supports the Cocoa event model */
+ , NPNVsupportsUpdatedCocoaTextInputBool = 3002 /* TRUE if the browser supports the updated
+ Cocoa text input specification. */
+#endif
, NPNVmuteAudioBool = 4000 /* Request that the browser wants to mute or unmute the plugin */
#if defined(XP_WIN)
, NPNVaudioDeviceChangeDetails = 4001 /* Provides information about the new default audio device */
#endif
+#if defined(XP_MACOSX)
+ , NPNVsupportsCompositingCoreAnimationPluginsBool = 74656 /* TRUE if the browser supports
+ CA model compositing */
+#endif
} NPNVariable;
typedef enum {
@@ -434,7 +509,7 @@ typedef struct _NPWindow
uint32_t width; /* Maximum window size */
uint32_t height;
NPRect clipRect; /* Clipping rectangle in port coordinates */
-#if defined(XP_UNIX) || defined(XP_SYMBIAN)
+#if (defined(XP_UNIX) || defined(XP_SYMBIAN)) && !defined(XP_MACOSX)
void * ws_info; /* Platform-dependent additional data */
#endif /* XP_UNIX */
NPWindowType type; /* Is this a window or a drawable? */
@@ -480,7 +555,11 @@ typedef struct _NPPrint
} print;
} NPPrint;
-#if defined(XP_SYMBIAN)
+#if defined(XP_MACOSX)
+#ifndef NP_NO_CARBON
+typedef EventRecord NPEvent;
+#endif
+#elif defined(XP_SYMBIAN)
typedef QEvent NPEvent;
#elif defined(XP_WIN)
typedef struct _NPEvent
@@ -495,7 +574,13 @@ typedef XEvent NPEvent;
typedef void* NPEvent;
#endif
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+typedef void* NPRegion;
+#ifndef NP_NO_QUICKDRAW
+typedef RgnHandle NPQDRegion;
+#endif
+typedef CGPathRef NPCGRegion;
+#elif defined(XP_WIN)
typedef HRGN NPRegion;
#elif defined(XP_UNIX) && defined(MOZ_X11)
typedef Region NPRegion;
@@ -509,7 +594,11 @@ typedef struct _NPNSString NPNSString;
typedef struct _NPNSWindow NPNSWindow;
typedef struct _NPNSMenu NPNSMenu;
+#if defined(XP_MACOSX)
+typedef NPNSMenu NPMenu;
+#else
typedef void *NPMenu;
+#endif
typedef enum {
NPCoordinateSpacePlugin = 1,
@@ -519,6 +608,112 @@ typedef enum {
NPCoordinateSpaceFlippedScreen
} NPCoordinateSpace;
+#if defined(XP_MACOSX)
+
+#ifndef NP_NO_QUICKDRAW
+typedef struct NP_Port
+{
+ CGrafPtr port;
+ int32_t portx; /* position inside the topmost window */
+ int32_t porty;
+} NP_Port;
+#endif /* NP_NO_QUICKDRAW */
+
+/*
+ * NP_CGContext is the type of the NPWindow's 'window' when the plugin specifies NPDrawingModelCoreGraphics
+ * as its drawing model.
+ */
+
+typedef struct NP_CGContext
+{
+ CGContextRef context;
+ void *window; /* A WindowRef under the Carbon event model. */
+} NP_CGContext;
+
+/*
+ * NP_GLContext is the type of the NPWindow's 'window' when the plugin specifies NPDrawingModelOpenGL as its
+ * drawing model.
+ */
+
+typedef struct NP_GLContext
+{
+ CGLContextObj context;
+#ifdef NP_NO_CARBON
+ NPNSWindow *window;
+#else
+ void *window; /* Can be either an NSWindow or a WindowRef depending on the event model */
+#endif
+} NP_GLContext;
+
+typedef enum {
+ NPCocoaEventDrawRect = 1,
+ NPCocoaEventMouseDown,
+ NPCocoaEventMouseUp,
+ NPCocoaEventMouseMoved,
+ NPCocoaEventMouseEntered,
+ NPCocoaEventMouseExited,
+ NPCocoaEventMouseDragged,
+ NPCocoaEventKeyDown,
+ NPCocoaEventKeyUp,
+ NPCocoaEventFlagsChanged,
+ NPCocoaEventFocusChanged,
+ NPCocoaEventWindowFocusChanged,
+ NPCocoaEventScrollWheel,
+ NPCocoaEventTextInput
+} NPCocoaEventType;
+
+typedef struct _NPCocoaEvent {
+ NPCocoaEventType type;
+ uint32_t version;
+ union {
+ struct {
+ uint32_t modifierFlags;
+ double pluginX;
+ double pluginY;
+ int32_t buttonNumber;
+ int32_t clickCount;
+ double deltaX;
+ double deltaY;
+ double deltaZ;
+ } mouse;
+ struct {
+ uint32_t modifierFlags;
+ NPNSString *characters;
+ NPNSString *charactersIgnoringModifiers;
+ NPBool isARepeat;
+ uint16_t keyCode;
+ } key;
+ struct {
+ CGContextRef context;
+ double x;
+ double y;
+ double width;
+ double height;
+ } draw;
+ struct {
+ NPBool hasFocus;
+ } focus;
+ struct {
+ NPNSString *text;
+ } text;
+ } data;
+} NPCocoaEvent;
+
+#ifndef NP_NO_CARBON
+/* Non-standard event types that can be passed to HandleEvent */
+enum NPEventType {
+ NPEventType_GetFocusEvent = (osEvt + 16),
+ NPEventType_LoseFocusEvent,
+ NPEventType_AdjustCursorEvent,
+ NPEventType_MenuCommandEvent,
+ NPEventType_ClippingChangedEvent,
+ NPEventType_ScrollingBeginsEvent = 1000,
+ NPEventType_ScrollingEndsEvent
+};
+#endif /* NP_NO_CARBON */
+
+#endif /* XP_MACOSX */
+
/*
* Values for mode passed to NPP_New:
*/
@@ -541,6 +736,12 @@ typedef enum {
#define NP_CLEAR_ALL 0
#define NP_CLEAR_CACHE (1 << 0)
+#if !defined(__LP64__)
+#if defined(XP_MACOSX)
+#pragma options align=reset
+#endif
+#endif /* __LP64__ */
+
/*----------------------------------------------------------------------*/
/* Error and Reason Code definitions */
/*----------------------------------------------------------------------*/
diff --git a/dom/plugins/base/npfunctions.h b/dom/plugins/base/npfunctions.h
index 83c8a9762c..dca542b620 100644
--- a/dom/plugins/base/npfunctions.h
+++ b/dom/plugins/base/npfunctions.h
@@ -182,6 +182,31 @@ typedef struct _NPNetscapeFuncs {
NPN_SetCurrentAsyncSurfacePtr setcurrentasyncsurface;
} NPNetscapeFuncs;
+#ifdef XP_MACOSX
+/*
+ * Mac OS X version(s) of NP_GetMIMEDescription(const char *)
+ * These can be called to retreive MIME information from the plugin dynamically
+ *
+ * Note: For compatibility with Quicktime, BPSupportedMIMEtypes is another way
+ * to get mime info from the plugin only on OSX and may not be supported
+ * in furture version -- use NP_GetMIMEDescription instead
+ */
+enum
+{
+ kBPSupportedMIMETypesStructVers_1 = 1
+};
+typedef struct _BPSupportedMIMETypes
+{
+ SInt32 structVersion; /* struct version */
+ Handle typeStrings; /* STR# formated handle, allocated by plug-in */
+ Handle infoStrings; /* STR# formated handle, allocated by plug-in */
+} BPSupportedMIMETypes;
+OSErr BP_GetSupportedMIMETypes(BPSupportedMIMETypes *mimeInfo, UInt32 flags);
+#define NP_GETMIMEDESCRIPTION_NAME "NP_GetMIMEDescription"
+typedef const char* (*NP_GetMIMEDescriptionProcPtr)(void);
+typedef OSErr (*BP_GetSupportedMIMETypesProcPtr)(BPSupportedMIMETypes*, UInt32);
+#endif
+
#if defined(_WIN32)
#define OSCALL WINAPI
#else
@@ -226,8 +251,15 @@ typedef char* (*NP_GetPluginVersionFunc)(void);
NP_EXPORT(char*) NP_GetPluginVersion(void);
typedef const char* (*NP_GetMIMEDescriptionFunc)(void);
NP_EXPORT(const char*) NP_GetMIMEDescription(void);
+#ifdef XP_MACOSX
+typedef NPError (*NP_InitializeFunc)(NPNetscapeFuncs*);
+NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs);
+typedef NPError (*NP_GetEntryPointsFunc)(NPPluginFuncs*);
+NP_EXPORT(NPError) NP_GetEntryPoints(NPPluginFuncs* pFuncs);
+#else
typedef NPError (*NP_InitializeFunc)(NPNetscapeFuncs*, NPPluginFuncs*);
NP_EXPORT(NPError) NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs);
+#endif
typedef NPError (*NP_ShutdownFunc)(void);
NP_EXPORT(NPError) NP_Shutdown(void);
typedef NPError (*NP_GetValueFunc)(void *, NPPVariable, void *);
diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp
index b2e931483f..da4f09914f 100644
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -269,6 +269,14 @@ nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
return NS_ERROR_FAILURE;
}
+#ifdef XP_MACOSX
+ if (!pluginLib->HasRequiredFunctions()) {
+ NS_WARNING("Not all necessary functions exposed by plugin, it will not load.");
+ delete pluginLib;
+ return NS_ERROR_FAILURE;
+ }
+#endif
+
plugin->mLibrary = pluginLib;
pluginLib->SetPlugin(plugin);
@@ -286,6 +294,19 @@ nsNPAPIPlugin::CreatePlugin(nsPluginTag *aPluginTag, nsNPAPIPlugin** aResult)
if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) {
return NS_ERROR_FAILURE;
}
+#elif defined(XP_MACOSX)
+ // NP_Initialize must be called before NP_GetEntryPoints on Mac OS X.
+ // We need to match WebKit's behavior.
+ NPError pluginCallError;
+ nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &pluginCallError);
+ if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = pluginLib->NP_GetEntryPoints(&plugin->mPluginFuncs, &pluginCallError);
+ if (rv != NS_OK || pluginCallError != NPERR_NO_ERROR) {
+ return NS_ERROR_FAILURE;
+ }
#else
NPError pluginCallError;
nsresult rv = pluginLib->NP_Initialize(&sBrowserFuncs, &plugin->mPluginFuncs, &pluginCallError);
@@ -1708,7 +1729,7 @@ _getvalue(NPP npp, NPNVariable variable, void *result)
// cases for android_npapi.h's non-standard ANPInterface values.
switch (static_cast<int>(variable)) {
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
case NPNVxDisplay : {
#if defined(MOZ_X11)
if (npp) {
@@ -1833,7 +1854,8 @@ _getvalue(NPP npp, NPNVariable variable, void *result)
}
case NPNVSupportsWindowless: {
-#if defined(XP_WIN) || (defined(MOZ_X11) && defined(MOZ_WIDGET_GTK))
+#if defined(XP_WIN) || defined(XP_MACOSX) || \
+ (defined(MOZ_X11) && defined(MOZ_WIDGET_GTK))
*(NPBool*)result = true;
#else
*(NPBool*)result = false;
@@ -1901,7 +1923,72 @@ _getvalue(NPP npp, NPNVariable variable, void *result)
return *(char**)result ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR;
}
-#if defined(XP_WIN)
+#ifdef XP_MACOSX
+ case NPNVpluginDrawingModel: {
+ if (npp) {
+ nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance*)npp->ndata;
+ if (inst) {
+ NPDrawingModel drawingModel;
+ inst->GetDrawingModel((int32_t*)&drawingModel);
+ *(NPDrawingModel*)result = drawingModel;
+ return NPERR_NO_ERROR;
+ }
+ }
+ return NPERR_GENERIC_ERROR;
+ }
+
+#ifndef NP_NO_QUICKDRAW
+ case NPNVsupportsQuickDrawBool: {
+ *(NPBool*)result = false;
+
+ return NPERR_NO_ERROR;
+ }
+#endif
+
+ case NPNVsupportsCoreGraphicsBool: {
+ *(NPBool*)result = true;
+
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsCoreAnimationBool: {
+ *(NPBool*)result = true;
+
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsInvalidatingCoreAnimationBool: {
+ *(NPBool*)result = true;
+
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsCompositingCoreAnimationPluginsBool: {
+ *(NPBool*)result = PR_TRUE;
+
+ return NPERR_NO_ERROR;
+ }
+
+#ifndef NP_NO_CARBON
+ case NPNVsupportsCarbonBool: {
+ *(NPBool*)result = false;
+
+ return NPERR_NO_ERROR;
+ }
+#endif
+ case NPNVsupportsCocoaBool: {
+ *(NPBool*)result = true;
+
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsUpdatedCocoaTextInputBool: {
+ *(NPBool*)result = true;
+ return NPERR_NO_ERROR;
+ }
+#endif
+
+#if defined(XP_MACOSX) || defined(XP_WIN)
case NPNVcontentsScaleFactor: {
nsNPAPIPluginInstance *inst =
(nsNPAPIPluginInstance *) (npp ? npp->ndata : nullptr);
@@ -1964,8 +2051,16 @@ _setvalue(NPP npp, NPPVariable variable, void *result)
// actual pointer value is checked rather than its content
// when passing booleans
case NPPVpluginWindowBool: {
+#ifdef XP_MACOSX
+ // This setting doesn't apply to OS X (only to Windows and Unix/Linux).
+ // See https://developer.mozilla.org/En/NPN_SetValue#section_5. Return
+ // NPERR_NO_ERROR here to conform to other browsers' behavior on OS X
+ // (e.g. Safari and Opera).
+ return NPERR_NO_ERROR;
+#else
NPBool bWindowless = (result == nullptr);
return inst->SetWindowless(bWindowless);
+#endif
}
case NPPVpluginTransparentBool: {
NPBool bTransparent = (result != nullptr);
@@ -2050,6 +2145,18 @@ _setvalue(NPP npp, NPPVariable variable, void *result)
}
}
+#ifdef XP_MACOSX
+ case NPPVpluginEventModel: {
+ if (inst) {
+ inst->SetEventModel((NPEventModel)NS_PTR_TO_INT32(result));
+ return NPERR_NO_ERROR;
+ }
+ else {
+ return NPERR_GENERIC_ERROR;
+ }
+ }
+#endif
+
default:
return NPERR_GENERIC_ERROR;
}
diff --git a/dom/plugins/base/nsNPAPIPlugin.h b/dom/plugins/base/nsNPAPIPlugin.h
index 7727bf77fb..96e630b629 100644
--- a/dom/plugins/base/nsNPAPIPlugin.h
+++ b/dom/plugins/base/nsNPAPIPlugin.h
@@ -45,6 +45,10 @@ public:
// PluginFuncs() can't fail but results are only valid if GetLibrary() succeeds
NPPluginFuncs* PluginFuncs();
+#if defined(XP_MACOSX) && !defined(__LP64__)
+ void SetPluginRefNum(short aRefNum);
+#endif
+
// The IPC mechanism notifies the nsNPAPIPlugin if the plugin
// crashes and is no longer usable. pluginDumpID/browserDumpID are
// the IDs of respective minidumps that were written, or empty if no
diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp
index 6e35bf3662..170f42b28e 100644
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -60,6 +60,9 @@ nsNPAPIPluginInstance::nsNPAPIPluginInstance()
, mPlugin(nullptr)
, mMIMEType(nullptr)
, mOwner(nullptr)
+#ifdef XP_MACOSX
+ , mCurrentPluginEvent(nullptr)
+#endif
, mHaveJavaC2PJSObjectQuirk(false)
, mCachedParamLength(0)
, mCachedParamNames(nullptr)
@@ -500,6 +503,9 @@ nsresult nsNPAPIPluginInstance::HandleEvent(void* event, int16_t* result,
int16_t tmpResult = kNPEventNotHandled;
if (pluginFunctions->event) {
+#ifdef XP_MACOSX
+ mCurrentPluginEvent = event;
+#endif
#if defined(XP_WIN)
NS_TRY_SAFE_CALL_RETURN(tmpResult, (*pluginFunctions->event)(&mNPP, event), this,
aSafeToReenterGecko);
@@ -513,6 +519,9 @@ nsresult nsNPAPIPluginInstance::HandleEvent(void* event, int16_t* result,
if (result)
*result = tmpResult;
+#ifdef XP_MACOSX
+ mCurrentPluginEvent = nullptr;
+#endif
}
return NS_OK;
@@ -608,6 +617,19 @@ void nsNPAPIPluginInstance::RedrawPlugin()
mOwner->RedrawPlugin();
}
+#if defined(XP_MACOSX)
+void nsNPAPIPluginInstance::SetEventModel(NPEventModel aModel)
+{
+ // the event model needs to be set for the object frame immediately
+ if (!mOwner) {
+ NS_WARNING("Trying to set event model without a plugin instance owner!");
+ return;
+ }
+
+ mOwner->SetEventModel(aModel);
+}
+#endif
+
nsresult nsNPAPIPluginInstance::GetDrawingModel(int32_t* aModel)
{
*aModel = (int32_t)mDrawingModel;
@@ -616,14 +638,24 @@ nsresult nsNPAPIPluginInstance::GetDrawingModel(int32_t* aModel)
nsresult nsNPAPIPluginInstance::IsRemoteDrawingCoreAnimation(bool* aDrawing)
{
- /** Mac Stub **/
+#ifdef XP_MACOSX
+ if (!mPlugin)
+ return NS_ERROR_FAILURE;
+
+ PluginLibrary* library = mPlugin->GetLibrary();
+ if (!library)
+ return NS_ERROR_FAILURE;
+
+ return library->IsRemoteDrawingCoreAnimation(&mNPP, aDrawing);
+#else
return NS_ERROR_FAILURE;
+#endif
}
nsresult
nsNPAPIPluginInstance::ContentsScaleFactorChanged(double aContentsScaleFactor)
{
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
if (!mPlugin)
return NS_ERROR_FAILURE;
@@ -700,7 +732,12 @@ nsNPAPIPluginInstance::ShouldCache()
nsresult
nsNPAPIPluginInstance::IsWindowless(bool* isWindowless)
{
+#ifdef XP_MACOSX
+ // All OS X plugins are windowless.
+ *isWindowless = true;
+#else
*isWindowless = mWindowless;
+#endif
return NS_OK;
}
diff --git a/dom/plugins/base/nsNPAPIPluginInstance.h b/dom/plugins/base/nsNPAPIPluginInstance.h
index c3272e0a87..48e62517de 100644
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -34,6 +34,12 @@ class nsPluginInstanceOwner;
const NPDrawingModel kDefaultDrawingModel = NPDrawingModelSyncWin;
#elif defined(MOZ_X11)
const NPDrawingModel kDefaultDrawingModel = NPDrawingModelSyncX;
+#elif defined(XP_MACOSX)
+#ifndef NP_NO_QUICKDRAW
+const NPDrawingModel kDefaultDrawingModel = NPDrawingModelQuickDraw; // Not supported
+#else
+const NPDrawingModel kDefaultDrawingModel = NPDrawingModelCoreGraphics;
+#endif
#else
const NPDrawingModel kDefaultDrawingModel = static_cast<NPDrawingModel>(0);
#endif
@@ -140,6 +146,13 @@ public:
void SetDrawingModel(NPDrawingModel aModel);
void RedrawPlugin();
+#ifdef XP_MACOSX
+ void SetEventModel(NPEventModel aModel);
+
+ void* GetCurrentEvent() {
+ return mCurrentPluginEvent;
+ }
+#endif
nsresult NewStreamListener(const char* aURL, void* notifyData,
nsNPAPIPluginStreamListener** listener);
@@ -278,6 +291,11 @@ private:
nsTArray<nsNPAPITimer*> mTimers;
+#ifdef XP_MACOSX
+ // non-null during a HandleEvent call
+ void* mCurrentPluginEvent;
+#endif
+
// Timestamp for the last time this plugin was stopped.
// This is only valid when the plugin is actually stopped!
mozilla::TimeStamp mStopTime;
diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp
index b36bf96331..c9c1b71fe0 100644
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -33,6 +33,9 @@
#include "nsProtocolProxyService.h"
#include "nsIStreamConverterService.h"
#include "nsIFile.h"
+#if defined(XP_MACOSX)
+#include "nsILocalFileMac.h"
+#endif
#include "nsISeekableStream.h"
#include "nsNetUtil.h"
#include "nsIFileStreams.h"
@@ -1921,7 +1924,19 @@ int64_t GetPluginLastModifiedTime(const nsCOMPtr<nsIFile>& localfile)
{
PRTime fileModTime = 0;
+#if defined(XP_MACOSX)
+ // On OS X the date of a bundle's "contents" (i.e. of its Info.plist file)
+ // is a much better guide to when it was last modified than the date of
+ // its package directory. See bug 313700.
+ nsCOMPtr<nsILocalFileMac> localFileMac = do_QueryInterface(localfile);
+ if (localFileMac) {
+ localFileMac->GetBundleContentsLastModifiedTime(&fileModTime);
+ } else {
+ localfile->GetLastModifiedTime(&fileModTime);
+ }
+#else
localfile->GetLastModifiedTime(&fileModTime);
+#endif
return fileModTime;
}
diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp
index 60a9bc0caa..0d4dc68ccc 100644
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -77,6 +77,12 @@ static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
#include "mozilla/widget/WinMessages.h"
#endif // #ifdef XP_WIN
+#ifdef XP_MACOSX
+#include "ComplexTextInputPanel.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMXULCommandDispatcher.h"
+#endif
+
#ifdef MOZ_WIDGET_GTK
#include <gdk/gdk.h>
#include <gtk/gtk.h>
@@ -223,13 +229,27 @@ nsPluginInstanceOwner::EndUpdateBackground(const nsIntRect& aRect)
bool
nsPluginInstanceOwner::UseAsyncRendering()
{
+#ifdef XP_MACOSX
+ if (mUseAsyncRendering) {
+ return true;
+ }
+#endif
+
bool isOOP;
bool result = (mInstance &&
NS_SUCCEEDED(mInstance->GetIsOOP(&isOOP)) && isOOP
+#ifndef XP_MACOSX
&& (!mPluginWindow ||
mPluginWindow->type == NPWindowTypeDrawable)
+#endif
);
+#ifdef XP_MACOSX
+ if (result) {
+ mUseAsyncRendering = true;
+ }
+#endif
+
return result;
}
@@ -255,6 +275,13 @@ nsPluginInstanceOwner::nsPluginInstanceOwner()
mPluginFrame = nullptr;
mWidgetCreationComplete = false;
+#ifdef XP_MACOSX
+ mSentInitialTopLevelWindowEvent = false;
+ mLastWindowIsActive = false;
+ mLastContentFocused = false;
+ mLastScaleFactor = 1.0;
+ mShouldBlurOnActivate = false;
+#endif
mLastCSSZoomFactor = 1.0;
mContentFocused = false;
mWidgetVisible = true;
@@ -262,6 +289,16 @@ nsPluginInstanceOwner::nsPluginInstanceOwner()
mPluginDocumentActiveState = true;
mLastMouseDownButtonType = -1;
+#ifdef XP_MACOSX
+#ifndef NP_NO_CARBON
+ // We don't support Carbon, but it is still the default model for i386 NPAPI.
+ mEventModel = NPEventModelCarbon;
+#else
+ mEventModel = NPEventModelCocoa;
+#endif
+ mUseAsyncRendering = false;
+#endif
+
mWaitingForPaint = false;
#ifdef XP_WIN
@@ -494,6 +531,15 @@ NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRect(NPRect *invalidRect)
if (!mPluginFrame || !invalidRect || !mWidgetVisible)
return NS_ERROR_FAILURE;
+#ifdef XP_MACOSX
+ // Each time an asynchronously-drawing plugin sends a new surface to display,
+ // the image in the ImageContainer is updated and InvalidateRect is called.
+ // There are different side effects for (sync) Android plugins.
+ RefPtr<ImageContainer> container;
+ mInstance->GetImageContainer(getter_AddRefs(container));
+#endif
+
+#ifndef XP_MACOSX
// Silverlight calls invalidate for windowed plugins so this needs to work.
if (mWidget) {
mWidget->Invalidate(
@@ -507,6 +553,7 @@ NS_IMETHODIMP nsPluginInstanceOwner::InvalidateRect(NPRect *invalidRect)
return NS_OK;
}
}
+#endif
nsIntRect rect(invalidRect->left,
invalidRect->top,
@@ -917,14 +964,252 @@ nsPluginInstanceOwner::OnWindowedPluginKeyEvent(
NS_IMETHODIMP nsPluginInstanceOwner::SetEventModel(int32_t eventModel)
{
+#ifdef XP_MACOSX
+ mEventModel = static_cast<NPEventModel>(eventModel);
+ return NS_OK;
+#else
return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+#ifdef XP_MACOSX
+NPBool nsPluginInstanceOwner::ConvertPointPuppet(PuppetWidget *widget,
+ nsPluginFrame* pluginFrame,
+ double sourceX, double sourceY,
+ NPCoordinateSpace sourceSpace,
+ double *destX, double *destY,
+ NPCoordinateSpace destSpace)
+{
+ NS_ENSURE_TRUE(widget && widget->GetOwningTabChild() && pluginFrame, false);
+ // Caller has to want a result.
+ NS_ENSURE_TRUE(destX || destY, false);
+
+ if (sourceSpace == destSpace) {
+ if (destX) {
+ *destX = sourceX;
+ }
+ if (destY) {
+ *destY = sourceY;
+ }
+ return true;
+ }
+
+ nsPresContext* presContext = pluginFrame->PresContext();
+ double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/
+ presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+
+ PuppetWidget *puppetWidget = static_cast<PuppetWidget*>(widget);
+ PuppetWidget *rootWidget = static_cast<PuppetWidget*>(widget->GetTopLevelWidget());
+ if (!rootWidget) {
+ return false;
+ }
+ nsPoint chromeSize = AsNsPoint(rootWidget->GetChromeDimensions()) / scaleFactor;
+ nsIntSize intScreenDims = rootWidget->GetScreenDimensions();
+ nsSize screenDims = nsSize(intScreenDims.width / scaleFactor,
+ intScreenDims.height / scaleFactor);
+ int32_t screenH = screenDims.height;
+ nsPoint windowPosition = AsNsPoint(rootWidget->GetWindowPosition()) / scaleFactor;
+
+ // Window size is tab size + chrome size.
+ LayoutDeviceIntRect tabContentBounds = puppetWidget->GetBounds();
+ tabContentBounds.ScaleInverseRoundOut(scaleFactor);
+ int32_t windowH = tabContentBounds.height + int(chromeSize.y);
+
+ nsPoint pluginPosition = AsNsPoint(pluginFrame->GetScreenRect().TopLeft());
+
+ // Convert (sourceX, sourceY) to 'real' (not PuppetWidget) screen space.
+ // In OSX, the Y-axis increases upward, which is the reverse of ours.
+ // We want OSX coordinates for window and screen so those equations are swapped.
+ nsPoint sourcePoint(sourceX, sourceY);
+ nsPoint screenPoint;
+ switch (sourceSpace) {
+ case NPCoordinateSpacePlugin:
+ screenPoint = sourcePoint + pluginPosition +
+ pluginFrame->GetContentRectRelativeToSelf().TopLeft() / nsPresContext::AppUnitsPerCSSPixel();
+ break;
+ case NPCoordinateSpaceWindow:
+ screenPoint = nsPoint(sourcePoint.x, windowH-sourcePoint.y) +
+ windowPosition;
+ break;
+ case NPCoordinateSpaceFlippedWindow:
+ screenPoint = sourcePoint + windowPosition;
+ break;
+ case NPCoordinateSpaceScreen:
+ screenPoint = nsPoint(sourcePoint.x, screenH-sourcePoint.y);
+ break;
+ case NPCoordinateSpaceFlippedScreen:
+ screenPoint = sourcePoint;
+ break;
+ default:
+ return false;
+ }
+
+ // Convert from screen to dest space.
+ nsPoint destPoint;
+ switch (destSpace) {
+ case NPCoordinateSpacePlugin:
+ destPoint = screenPoint - pluginPosition -
+ pluginFrame->GetContentRectRelativeToSelf().TopLeft() / nsPresContext::AppUnitsPerCSSPixel();
+ break;
+ case NPCoordinateSpaceWindow:
+ destPoint = screenPoint - windowPosition;
+ destPoint.y = windowH - destPoint.y;
+ break;
+ case NPCoordinateSpaceFlippedWindow:
+ destPoint = screenPoint - windowPosition;
+ break;
+ case NPCoordinateSpaceScreen:
+ destPoint = nsPoint(screenPoint.x, screenH-screenPoint.y);
+ break;
+ case NPCoordinateSpaceFlippedScreen:
+ destPoint = screenPoint;
+ break;
+ default:
+ return false;
+ }
+
+ if (destX) {
+ *destX = destPoint.x;
+ }
+ if (destY) {
+ *destY = destPoint.y;
+ }
+
+ return true;
}
+NPBool nsPluginInstanceOwner::ConvertPointNoPuppet(nsIWidget *widget,
+ nsPluginFrame* pluginFrame,
+ double sourceX, double sourceY,
+ NPCoordinateSpace sourceSpace,
+ double *destX, double *destY,
+ NPCoordinateSpace destSpace)
+{
+ NS_ENSURE_TRUE(widget && pluginFrame, false);
+ // Caller has to want a result.
+ NS_ENSURE_TRUE(destX || destY, false);
+
+ if (sourceSpace == destSpace) {
+ if (destX) {
+ *destX = sourceX;
+ }
+ if (destY) {
+ *destY = sourceY;
+ }
+ return true;
+ }
+
+ nsPresContext* presContext = pluginFrame->PresContext();
+ double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/
+ presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+
+ nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenMgr) {
+ return false;
+ }
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForNativeWidget(widget->GetNativeData(NS_NATIVE_WINDOW), getter_AddRefs(screen));
+ if (!screen) {
+ return false;
+ }
+
+ int32_t screenX, screenY, screenWidth, screenHeight;
+ screen->GetRect(&screenX, &screenY, &screenWidth, &screenHeight);
+ screenHeight /= scaleFactor;
+
+ LayoutDeviceIntRect windowScreenBounds = widget->GetScreenBounds();
+ windowScreenBounds.ScaleInverseRoundOut(scaleFactor);
+ int32_t windowX = windowScreenBounds.x;
+ int32_t windowY = windowScreenBounds.y;
+ int32_t windowHeight = windowScreenBounds.height;
+
+ nsIntRect pluginScreenRect = pluginFrame->GetScreenRect();
+
+ double screenXGecko, screenYGecko;
+ switch (sourceSpace) {
+ case NPCoordinateSpacePlugin:
+ screenXGecko = pluginScreenRect.x + sourceX;
+ screenYGecko = pluginScreenRect.y + sourceY;
+ break;
+ case NPCoordinateSpaceWindow:
+ screenXGecko = windowX + sourceX;
+ screenYGecko = windowY + (windowHeight - sourceY);
+ break;
+ case NPCoordinateSpaceFlippedWindow:
+ screenXGecko = windowX + sourceX;
+ screenYGecko = windowY + sourceY;
+ break;
+ case NPCoordinateSpaceScreen:
+ screenXGecko = sourceX;
+ screenYGecko = screenHeight - sourceY;
+ break;
+ case NPCoordinateSpaceFlippedScreen:
+ screenXGecko = sourceX;
+ screenYGecko = sourceY;
+ break;
+ default:
+ return false;
+ }
+
+ double destXCocoa, destYCocoa;
+ switch (destSpace) {
+ case NPCoordinateSpacePlugin:
+ destXCocoa = screenXGecko - pluginScreenRect.x;
+ destYCocoa = screenYGecko - pluginScreenRect.y;
+ break;
+ case NPCoordinateSpaceWindow:
+ destXCocoa = screenXGecko - windowX;
+ destYCocoa = windowHeight - (screenYGecko - windowY);
+ break;
+ case NPCoordinateSpaceFlippedWindow:
+ destXCocoa = screenXGecko - windowX;
+ destYCocoa = screenYGecko - windowY;
+ break;
+ case NPCoordinateSpaceScreen:
+ destXCocoa = screenXGecko;
+ destYCocoa = screenHeight - screenYGecko;
+ break;
+ case NPCoordinateSpaceFlippedScreen:
+ destXCocoa = screenXGecko;
+ destYCocoa = screenYGecko;
+ break;
+ default:
+ return false;
+ }
+
+ if (destX) {
+ *destX = destXCocoa;
+ }
+ if (destY) {
+ *destY = destYCocoa;
+ }
+
+ return true;
+}
+#endif // XP_MACOSX
+
NPBool nsPluginInstanceOwner::ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
double *destX, double *destY, NPCoordinateSpace destSpace)
{
- /** Mac Stub **/
+#ifdef XP_MACOSX
+ if (!mPluginFrame) {
+ return false;
+ }
+
+ MOZ_ASSERT(mPluginFrame->GetNearestWidget());
+
+ if (nsIWidget::UsePuppetWidgets()) {
+ return ConvertPointPuppet(static_cast<PuppetWidget*>(mPluginFrame->GetNearestWidget()),
+ mPluginFrame, sourceX, sourceY, sourceSpace,
+ destX, destY, destSpace);
+ }
+
+ return ConvertPointNoPuppet(mPluginFrame->GetNearestWidget(),
+ mPluginFrame, sourceX, sourceY, sourceSpace,
+ destX, destY, destSpace);
+#else
return false;
+#endif
}
NPError nsPluginInstanceOwner::InitAsyncSurface(NPSize *size, NPImageFormat format,
@@ -968,7 +1253,133 @@ void nsPluginInstanceOwner::GetParameters(nsTArray<MozPluginParameter>& paramete
loadingContent->GetPluginParameters(parameters);
}
-#if defined(XP_WIN)
+#ifdef XP_MACOSX
+
+static void InitializeNPCocoaEvent(NPCocoaEvent* event)
+{
+ memset(event, 0, sizeof(NPCocoaEvent));
+}
+
+NPDrawingModel nsPluginInstanceOwner::GetDrawingModel()
+{
+#ifndef NP_NO_QUICKDRAW
+ // We don't support the Quickdraw drawing model any more but it's still
+ // the default model for i386 per NPAPI.
+ NPDrawingModel drawingModel = NPDrawingModelQuickDraw;
+#else
+ NPDrawingModel drawingModel = NPDrawingModelCoreGraphics;
+#endif
+
+ if (!mInstance)
+ return drawingModel;
+
+ mInstance->GetDrawingModel((int32_t*)&drawingModel);
+ return drawingModel;
+}
+
+bool nsPluginInstanceOwner::IsRemoteDrawingCoreAnimation()
+{
+ if (!mInstance)
+ return false;
+
+ bool coreAnimation;
+ if (!NS_SUCCEEDED(mInstance->IsRemoteDrawingCoreAnimation(&coreAnimation)))
+ return false;
+
+ return coreAnimation;
+}
+
+NPEventModel nsPluginInstanceOwner::GetEventModel()
+{
+ return mEventModel;
+}
+
+#define DEFAULT_REFRESH_RATE 20 // 50 FPS
+
+nsCOMPtr<nsITimer> *nsPluginInstanceOwner::sCATimer = nullptr;
+nsTArray<nsPluginInstanceOwner*> *nsPluginInstanceOwner::sCARefreshListeners = nullptr;
+
+void nsPluginInstanceOwner::CARefresh(nsITimer *aTimer, void *aClosure) {
+ if (!sCARefreshListeners) {
+ return;
+ }
+ for (size_t i = 0; i < sCARefreshListeners->Length(); i++) {
+ nsPluginInstanceOwner* instanceOwner = (*sCARefreshListeners)[i];
+ NPWindow *window;
+ instanceOwner->GetWindow(window);
+ if (!window) {
+ continue;
+ }
+ NPRect r;
+ r.left = 0;
+ r.top = 0;
+ r.right = window->width;
+ r.bottom = window->height;
+ instanceOwner->InvalidateRect(&r);
+ }
+}
+
+void nsPluginInstanceOwner::AddToCARefreshTimer() {
+ if (!mInstance) {
+ return;
+ }
+
+ // Flash invokes InvalidateRect for us.
+ const char* mime = nullptr;
+ if (NS_SUCCEEDED(mInstance->GetMIMEType(&mime)) && mime &&
+ nsPluginHost::GetSpecialType(nsDependentCString(mime)) ==
+ nsPluginHost::eSpecialType_Flash) {
+ return;
+ }
+
+ if (!sCARefreshListeners) {
+ sCARefreshListeners = new nsTArray<nsPluginInstanceOwner*>();
+ }
+
+ if (sCARefreshListeners->Contains(this)) {
+ return;
+ }
+
+ sCARefreshListeners->AppendElement(this);
+
+ if (!sCATimer) {
+ sCATimer = new nsCOMPtr<nsITimer>();
+ }
+
+ if (sCARefreshListeners->Length() == 1) {
+ *sCATimer = do_CreateInstance("@mozilla.org/timer;1");
+ (*sCATimer)->InitWithFuncCallback(CARefresh, nullptr,
+ DEFAULT_REFRESH_RATE, nsITimer::TYPE_REPEATING_SLACK);
+ }
+}
+
+void nsPluginInstanceOwner::RemoveFromCARefreshTimer() {
+ if (!sCARefreshListeners || sCARefreshListeners->Contains(this) == false) {
+ return;
+ }
+
+ sCARefreshListeners->RemoveElement(this);
+
+ if (sCARefreshListeners->Length() == 0) {
+ if (sCATimer) {
+ (*sCATimer)->Cancel();
+ delete sCATimer;
+ sCATimer = nullptr;
+ }
+ delete sCARefreshListeners;
+ sCARefreshListeners = nullptr;
+ }
+}
+
+void nsPluginInstanceOwner::SetPluginPort()
+{
+ void* pluginPort = GetPluginPort();
+ if (!pluginPort || !mPluginWindow)
+ return;
+ mPluginWindow->window = pluginPort;
+}
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsresult nsPluginInstanceOwner::ContentsScaleFactorChanged(double aContentsScaleFactor)
{
if (!mInstance) {
@@ -987,6 +1398,11 @@ nsPluginInstanceOwner::GetEventloopNestingLevel()
uint32_t currentLevel = 0;
if (appShell) {
appShell->GetEventloopNestingLevel(&currentLevel);
+#ifdef XP_MACOSX
+ // Cocoa widget code doesn't process UI events through the normal
+ // appshell event loop, so it needs an additional count here.
+ currentLevel++;
+#endif
}
// No idea how this happens... but Linux doesn't consistently
@@ -1011,11 +1427,15 @@ void
nsPluginInstanceOwner::NotifyHostCreateWidget()
{
mPluginHost->CreateWidget(this);
+#ifdef XP_MACOSX
+ FixUpPluginWindow(ePluginPaintEnable);
+#else
if (mPluginFrame) {
mPluginFrame->InvalidateFrame();
} else {
CallSetWindow();
}
+#endif
}
void
@@ -1037,10 +1457,12 @@ nsPluginInstanceOwner::NotifyDestroyPending()
nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent)
{
+#ifndef XP_MACOSX
if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) {
// continue only for cases without child window
return aFocusEvent->PreventDefault(); // consume event
}
+#endif
WidgetEvent* theEvent = aFocusEvent->WidgetEventPtr();
if (theEvent) {
@@ -1058,6 +1480,9 @@ nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent)
nsresult nsPluginInstanceOwner::ProcessKeyPress(nsIDOMEvent* aKeyEvent)
{
+#ifdef XP_MACOSX
+ return DispatchKeyToPlugin(aKeyEvent);
+#else
if (SendNativeEvents())
DispatchKeyToPlugin(aKeyEvent);
@@ -1068,13 +1493,16 @@ nsresult nsPluginInstanceOwner::ProcessKeyPress(nsIDOMEvent* aKeyEvent)
aKeyEvent->StopPropagation();
}
return NS_OK;
+#endif
}
nsresult nsPluginInstanceOwner::DispatchKeyToPlugin(nsIDOMEvent* aKeyEvent)
{
+#if !defined(XP_MACOSX)
if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow))
return aKeyEvent->PreventDefault(); // consume event
// continue only for cases without child window
+#endif
if (mInstance) {
WidgetKeyboardEvent* keyEvent =
@@ -1094,9 +1522,11 @@ nsresult nsPluginInstanceOwner::DispatchKeyToPlugin(nsIDOMEvent* aKeyEvent)
nsresult
nsPluginInstanceOwner::ProcessMouseDown(nsIDOMEvent* aMouseEvent)
{
+#if !defined(XP_MACOSX)
if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow))
return aMouseEvent->PreventDefault(); // consume event
// continue only for cases without child window
+#endif
// if the plugin is windowless, we need to set focus ourselves
// otherwise, we might not get key events
@@ -1126,9 +1556,11 @@ nsPluginInstanceOwner::ProcessMouseDown(nsIDOMEvent* aMouseEvent)
nsresult nsPluginInstanceOwner::DispatchMouseToPlugin(nsIDOMEvent* aMouseEvent,
bool aAllowPropagate)
{
+#if !defined(XP_MACOSX)
if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow))
return aMouseEvent->PreventDefault(); // consume event
// continue only for cases without child window
+#endif
// don't send mouse events if we are hidden
if (!mWidgetVisible)
return NS_OK;
@@ -1338,6 +1770,22 @@ nsPluginInstanceOwner::HandleEvent(nsIDOMEvent* aEvent)
nsAutoString eventType;
aEvent->GetType(eventType);
+#ifdef XP_MACOSX
+ if (eventType.EqualsLiteral("activate") ||
+ eventType.EqualsLiteral("deactivate")) {
+ WindowFocusMayHaveChanged();
+ return NS_OK;
+ }
+ if (eventType.EqualsLiteral("MozPerformDelayedBlur")) {
+ if (mShouldBlurOnActivate) {
+ WidgetGUIEvent blurEvent(true, eBlur, nullptr);
+ ProcessEvent(blurEvent);
+ mShouldBlurOnActivate = false;
+ }
+ return NS_OK;
+ }
+#endif
+
if (eventType.EqualsLiteral("focus")) {
mContentFocused = true;
return DispatchFocusToPlugin(aEvent);
@@ -1400,6 +1848,191 @@ static unsigned int XInputEventState(const WidgetInputEvent& anEvent)
}
#endif
+#ifdef XP_MACOSX
+
+// Returns whether or not content is the content that is or would be
+// focused if the top-level chrome window was active.
+static bool
+ContentIsFocusedWithinWindow(nsIContent* aContent)
+{
+ nsPIDOMWindowOuter* outerWindow = aContent->OwnerDoc()->GetWindow();
+ if (!outerWindow) {
+ return false;
+ }
+
+ nsPIDOMWindowOuter* rootWindow = outerWindow->GetPrivateRoot();
+ if (!rootWindow) {
+ return false;
+ }
+
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> focusedFrame;
+ nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedDescendant(rootWindow, true, getter_AddRefs(focusedFrame));
+ return (focusedContent.get() == aContent);
+}
+
+static NPCocoaEventType
+CocoaEventTypeForEvent(const WidgetGUIEvent& anEvent, nsIFrame* aObjectFrame)
+{
+ const NPCocoaEvent* event = static_cast<const NPCocoaEvent*>(anEvent.mPluginEvent);
+ if (event) {
+ return event->type;
+ }
+
+ switch (anEvent.mMessage) {
+ case eMouseOver:
+ return NPCocoaEventMouseEntered;
+ case eMouseOut:
+ return NPCocoaEventMouseExited;
+ case eMouseMove: {
+ // We don't know via information on events from the widget code whether or not
+ // we're dragging. The widget code just generates mouse move events from native
+ // drag events. If anybody is capturing, this is a drag event.
+ if (nsIPresShell::GetCapturingContent()) {
+ return NPCocoaEventMouseDragged;
+ }
+
+ return NPCocoaEventMouseMoved;
+ }
+ case eMouseDown:
+ return NPCocoaEventMouseDown;
+ case eMouseUp:
+ return NPCocoaEventMouseUp;
+ case eKeyDown:
+ return NPCocoaEventKeyDown;
+ case eKeyUp:
+ return NPCocoaEventKeyUp;
+ case eFocus:
+ case eBlur:
+ return NPCocoaEventFocusChanged;
+ case eLegacyMouseLineOrPageScroll:
+ return NPCocoaEventScrollWheel;
+ default:
+ return (NPCocoaEventType)0;
+ }
+}
+
+static NPCocoaEvent
+TranslateToNPCocoaEvent(WidgetGUIEvent* anEvent, nsIFrame* aObjectFrame)
+{
+ NPCocoaEvent cocoaEvent;
+ InitializeNPCocoaEvent(&cocoaEvent);
+ cocoaEvent.type = CocoaEventTypeForEvent(*anEvent, aObjectFrame);
+
+ if (anEvent->mMessage == eMouseMove ||
+ anEvent->mMessage == eMouseDown ||
+ anEvent->mMessage == eMouseUp ||
+ anEvent->mMessage == eLegacyMouseLineOrPageScroll ||
+ anEvent->mMessage == eMouseOver ||
+ anEvent->mMessage == eMouseOut)
+ {
+ nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(anEvent, aObjectFrame) -
+ aObjectFrame->GetContentRectRelativeToSelf().TopLeft();
+ nsPresContext* presContext = aObjectFrame->PresContext();
+ // Plugin event coordinates need to be translated from device pixels
+ // into "display pixels" in HiDPI modes.
+ double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/
+ aObjectFrame->PresContext()->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+ size_t intScaleFactor = ceil(scaleFactor);
+ nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x) / intScaleFactor,
+ presContext->AppUnitsToDevPixels(pt.y) / intScaleFactor);
+ cocoaEvent.data.mouse.pluginX = double(ptPx.x);
+ cocoaEvent.data.mouse.pluginY = double(ptPx.y);
+ }
+
+ switch (anEvent->mMessage) {
+ case eMouseDown:
+ case eMouseUp: {
+ WidgetMouseEvent* mouseEvent = anEvent->AsMouseEvent();
+ if (mouseEvent) {
+ switch (mouseEvent->button) {
+ case WidgetMouseEvent::eLeftButton:
+ cocoaEvent.data.mouse.buttonNumber = 0;
+ break;
+ case WidgetMouseEvent::eRightButton:
+ cocoaEvent.data.mouse.buttonNumber = 1;
+ break;
+ case WidgetMouseEvent::eMiddleButton:
+ cocoaEvent.data.mouse.buttonNumber = 2;
+ break;
+ default:
+ NS_WARNING("Mouse button we don't know about?");
+ }
+ cocoaEvent.data.mouse.clickCount = mouseEvent->mClickCount;
+ } else {
+ NS_WARNING("eMouseUp/DOWN is not a WidgetMouseEvent?");
+ }
+ break;
+ }
+ case eLegacyMouseLineOrPageScroll: {
+ WidgetWheelEvent* wheelEvent = anEvent->AsWheelEvent();
+ if (wheelEvent) {
+ cocoaEvent.data.mouse.deltaX = wheelEvent->mLineOrPageDeltaX;
+ cocoaEvent.data.mouse.deltaY = wheelEvent->mLineOrPageDeltaY;
+ } else {
+ NS_WARNING("eLegacyMouseLineOrPageScroll is not a WidgetWheelEvent? "
+ "(could be, haven't checked)");
+ }
+ break;
+ }
+ case eKeyDown:
+ case eKeyUp:
+ {
+ WidgetKeyboardEvent* keyEvent = anEvent->AsKeyboardEvent();
+
+ // That keyEvent->mPluginTextEventString is non-empty is a signal that we should
+ // create a text event for the plugin, instead of a key event.
+ if (anEvent->mMessage == eKeyDown &&
+ !keyEvent->mPluginTextEventString.IsEmpty()) {
+ cocoaEvent.type = NPCocoaEventTextInput;
+ const char16_t* pluginTextEventString = keyEvent->mPluginTextEventString.get();
+ cocoaEvent.data.text.text = (NPNSString*)
+ ::CFStringCreateWithCharacters(NULL,
+ reinterpret_cast<const UniChar*>(pluginTextEventString),
+ keyEvent->mPluginTextEventString.Length());
+ } else {
+ cocoaEvent.data.key.keyCode = keyEvent->mNativeKeyCode;
+ cocoaEvent.data.key.isARepeat = keyEvent->mIsRepeat;
+ cocoaEvent.data.key.modifierFlags = keyEvent->mNativeModifierFlags;
+ const char16_t* nativeChars = keyEvent->mNativeCharacters.get();
+ cocoaEvent.data.key.characters = (NPNSString*)
+ ::CFStringCreateWithCharacters(NULL,
+ reinterpret_cast<const UniChar*>(nativeChars),
+ keyEvent->mNativeCharacters.Length());
+ const char16_t* nativeCharsIgnoringModifiers = keyEvent->mNativeCharactersIgnoringModifiers.get();
+ cocoaEvent.data.key.charactersIgnoringModifiers = (NPNSString*)
+ ::CFStringCreateWithCharacters(NULL,
+ reinterpret_cast<const UniChar*>(nativeCharsIgnoringModifiers),
+ keyEvent->mNativeCharactersIgnoringModifiers.Length());
+ }
+ break;
+ }
+ case eFocus:
+ case eBlur:
+ cocoaEvent.data.focus.hasFocus = (anEvent->mMessage == eFocus);
+ break;
+ default:
+ break;
+ }
+ return cocoaEvent;
+}
+
+void nsPluginInstanceOwner::PerformDelayedBlurs()
+{
+ nsCOMPtr<nsIContent> content = do_QueryReferent(mContent);
+ nsCOMPtr<EventTarget> windowRoot = content->OwnerDoc()->GetWindow()->GetTopWindowRoot();
+ nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(),
+ windowRoot,
+ NS_LITERAL_STRING("MozPerformDelayedBlur"),
+ false, false, nullptr);
+}
+
+#endif
+
nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
{
nsEventStatus rv = nsEventStatus_eIgnore;
@@ -1408,6 +2041,85 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
return nsEventStatus_eIgnore;
}
+#ifdef XP_MACOSX
+ NPEventModel eventModel = GetEventModel();
+ if (eventModel != NPEventModelCocoa) {
+ return nsEventStatus_eIgnore;
+ }
+
+ // In the Cocoa event model, focus is per-window. Don't tell a plugin it lost
+ // focus unless it lost focus within the window. For example, ignore a blur
+ // event if it's coming due to the plugin's window deactivating.
+ nsCOMPtr<nsIContent> content = do_QueryReferent(mContent);
+ if (anEvent.mMessage == eBlur && ContentIsFocusedWithinWindow(content)) {
+ mShouldBlurOnActivate = true;
+ return nsEventStatus_eIgnore;
+ }
+
+ // Also, don't tell the plugin it gained focus again after we've already given
+ // it focus. This might happen if it has focus, its window is blurred, then the
+ // window is made active again. The plugin never lost in-window focus, so it
+ // shouldn't get a focus event again.
+ if (anEvent.mMessage == eFocus && mLastContentFocused == true) {
+ mShouldBlurOnActivate = false;
+ return nsEventStatus_eIgnore;
+ }
+
+ // Now, if we're going to send a focus event, update mLastContentFocused and
+ // tell any plugins in our window that we have taken focus, so they should
+ // perform any delayed blurs.
+ if (anEvent.mMessage == eFocus || anEvent.mMessage == eBlur) {
+ mLastContentFocused = (anEvent.mMessage == eFocus);
+ mShouldBlurOnActivate = false;
+ PerformDelayedBlurs();
+ }
+
+ NPCocoaEvent cocoaEvent = TranslateToNPCocoaEvent(const_cast<WidgetGUIEvent*>(&anEvent), mPluginFrame);
+ if (cocoaEvent.type == (NPCocoaEventType)0) {
+ return nsEventStatus_eIgnore;
+ }
+
+ if (cocoaEvent.type == NPCocoaEventTextInput) {
+ mInstance->HandleEvent(&cocoaEvent, nullptr);
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ int16_t response = kNPEventNotHandled;
+ mInstance->HandleEvent(&cocoaEvent,
+ &response,
+ NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
+ if ((response == kNPEventStartIME) && (cocoaEvent.type == NPCocoaEventKeyDown)) {
+ nsIWidget* widget = mPluginFrame->GetNearestWidget();
+ if (widget) {
+ const WidgetKeyboardEvent* keyEvent = anEvent.AsKeyboardEvent();
+ double screenX, screenY;
+ ConvertPoint(0.0, mPluginFrame->GetScreenRect().height,
+ NPCoordinateSpacePlugin, &screenX, &screenY,
+ NPCoordinateSpaceScreen);
+ nsAutoString outText;
+ if (NS_SUCCEEDED(widget->StartPluginIME(*keyEvent, screenX, screenY, outText)) &&
+ !outText.IsEmpty()) {
+ CFStringRef cfString =
+ ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+ reinterpret_cast<const UniChar*>(outText.get()),
+ outText.Length());
+ NPCocoaEvent textEvent;
+ InitializeNPCocoaEvent(&textEvent);
+ textEvent.type = NPCocoaEventTextInput;
+ textEvent.data.text.text = (NPNSString*)cfString;
+ mInstance->HandleEvent(&textEvent, nullptr);
+ }
+ }
+ }
+
+ bool handled = (response == kNPEventHandled || response == kNPEventStartIME);
+ bool leftMouseButtonDown = (anEvent.mMessage == eMouseDown) &&
+ (anEvent.AsMouseEvent()->button == WidgetMouseEvent::eLeftButton);
+ if (handled && !(leftMouseButtonDown && !mContentFocused)) {
+ rv = nsEventStatus_eConsumeNoDefault;
+ }
+#endif
+
#ifdef XP_WIN
// this code supports windowless plugins
const NPEvent *pPluginEvent = static_cast<const NPEvent*>(anEvent.mPluginEvent);
@@ -1813,6 +2525,10 @@ nsPluginInstanceOwner::Destroy()
{
SetFrame(nullptr);
+#ifdef XP_MACOSX
+ RemoveFromCARefreshTimer();
+#endif
+
nsCOMPtr<nsIContent> content = do_QueryReferent(mContent);
// unregister context menu listener
@@ -1864,6 +2580,44 @@ nsPluginInstanceOwner::Destroy()
// Paints are handled differently, so we just simulate an update event.
+#ifdef XP_MACOSX
+void nsPluginInstanceOwner::Paint(const gfxRect& aDirtyRect, CGContextRef cgContext)
+{
+ if (!mInstance || !mPluginFrame)
+ return;
+
+ gfxRect dirtyRectCopy = aDirtyRect;
+ double scaleFactor = 1.0;
+ GetContentsScaleFactor(&scaleFactor);
+ if (scaleFactor != 1.0) {
+ ::CGContextScaleCTM(cgContext, scaleFactor, scaleFactor);
+ // Convert aDirtyRect from device pixels to "display pixels"
+ // for HiDPI modes
+ dirtyRectCopy.ScaleRoundOut(1.0 / scaleFactor);
+ }
+
+ DoCocoaEventDrawRect(dirtyRectCopy, cgContext);
+}
+
+void nsPluginInstanceOwner::DoCocoaEventDrawRect(const gfxRect& aDrawRect, CGContextRef cgContext)
+{
+ if (!mInstance || !mPluginFrame)
+ return;
+
+ // The context given here is only valid during the HandleEvent call.
+ NPCocoaEvent updateEvent;
+ InitializeNPCocoaEvent(&updateEvent);
+ updateEvent.type = NPCocoaEventDrawRect;
+ updateEvent.data.draw.context = cgContext;
+ updateEvent.data.draw.x = aDrawRect.X();
+ updateEvent.data.draw.y = aDrawRect.Y();
+ updateEvent.data.draw.width = aDrawRect.Width();
+ updateEvent.data.draw.height = aDrawRect.Height();
+
+ mInstance->HandleEvent(&updateEvent, nullptr);
+}
+#endif
+
#ifdef XP_WIN
void nsPluginInstanceOwner::Paint(const RECT& aDirty, HDC aDC)
{
@@ -2180,6 +2934,7 @@ NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void)
if (content) {
doc = content->OwnerDoc();
parentWidget = nsContentUtils::WidgetForDocument(doc);
+#ifndef XP_MACOSX
// If we're running in the content process, we need a remote widget created in chrome.
if (XRE_IsContentProcess()) {
if (nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow()) {
@@ -2195,13 +2950,16 @@ NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void)
}
}
}
+#endif // XP_MACOSX
}
+#ifndef XP_MACOSX
// A failure here is terminal since we can't fall back on the non-e10s code
// path below.
if (!mWidget && XRE_IsContentProcess()) {
return NS_ERROR_UNEXPECTED;
}
+#endif // XP_MACOSX
if (!mWidget) {
// native (single process)
@@ -2264,11 +3022,136 @@ NS_IMETHODIMP nsPluginInstanceOwner::CreateWidget(void)
}
}
+#ifdef XP_MACOSX
+ if (GetDrawingModel() == NPDrawingModelCoreAnimation) {
+ AddToCARefreshTimer();
+ }
+#endif
+
mWidgetCreationComplete = true;
return NS_OK;
}
+// Mac specific code to fix up the port location and clipping region
+#ifdef XP_MACOSX
+
+void nsPluginInstanceOwner::FixUpPluginWindow(int32_t inPaintState)
+{
+ if (!mPluginWindow || !mInstance || !mPluginFrame) {
+ return;
+ }
+
+ SetPluginPort();
+
+ LayoutDeviceIntSize widgetClip = mPluginFrame->GetWidgetlessClipRect().Size();
+
+ mPluginWindow->x = 0;
+ mPluginWindow->y = 0;
+
+ NPRect oldClipRect = mPluginWindow->clipRect;
+
+ // fix up the clipping region
+ mPluginWindow->clipRect.top = 0;
+ mPluginWindow->clipRect.left = 0;
+
+ if (inPaintState == ePluginPaintDisable) {
+ mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top;
+ mPluginWindow->clipRect.right = mPluginWindow->clipRect.left;
+ }
+ else if (inPaintState == ePluginPaintEnable)
+ {
+ mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top + widgetClip.height;
+ mPluginWindow->clipRect.right = mPluginWindow->clipRect.left + widgetClip.width;
+ }
+
+ // if the clip rect changed, call SetWindow()
+ // (RealPlayer needs this to draw correctly)
+ if (mPluginWindow->clipRect.left != oldClipRect.left ||
+ mPluginWindow->clipRect.top != oldClipRect.top ||
+ mPluginWindow->clipRect.right != oldClipRect.right ||
+ mPluginWindow->clipRect.bottom != oldClipRect.bottom)
+ {
+ if (UseAsyncRendering()) {
+ mInstance->AsyncSetWindow(mPluginWindow);
+ }
+ else {
+ mPluginWindow->CallSetWindow(mInstance);
+ }
+ }
+
+ // After the first NPP_SetWindow call we need to send an initial
+ // top-level window focus event.
+ if (!mSentInitialTopLevelWindowEvent) {
+ // Set this before calling ProcessEvent to avoid endless recursion.
+ mSentInitialTopLevelWindowEvent = true;
+
+ bool isActive = WindowIsActive();
+ SendWindowFocusChanged(isActive);
+ mLastWindowIsActive = isActive;
+ }
+}
+
+void
+nsPluginInstanceOwner::WindowFocusMayHaveChanged()
+{
+ if (!mSentInitialTopLevelWindowEvent) {
+ return;
+ }
+
+ bool isActive = WindowIsActive();
+ if (isActive != mLastWindowIsActive) {
+ SendWindowFocusChanged(isActive);
+ mLastWindowIsActive = isActive;
+ }
+}
+
+bool
+nsPluginInstanceOwner::WindowIsActive()
+{
+ if (!mPluginFrame) {
+ return false;
+ }
+
+ EventStates docState = mPluginFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+}
+
+void
+nsPluginInstanceOwner::SendWindowFocusChanged(bool aIsActive)
+{
+ if (!mInstance) {
+ return;
+ }
+
+ NPCocoaEvent cocoaEvent;
+ InitializeNPCocoaEvent(&cocoaEvent);
+ cocoaEvent.type = NPCocoaEventWindowFocusChanged;
+ cocoaEvent.data.focus.hasFocus = aIsActive;
+ mInstance->HandleEvent(&cocoaEvent,
+ nullptr,
+ NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
+}
+
+void
+nsPluginInstanceOwner::HidePluginWindow()
+{
+ if (!mPluginWindow || !mInstance) {
+ return;
+ }
+
+ mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top;
+ mPluginWindow->clipRect.right = mPluginWindow->clipRect.left;
+ mWidgetVisible = false;
+ if (UseAsyncRendering()) {
+ mInstance->AsyncSetWindow(mPluginWindow);
+ } else {
+ mInstance->SetWindow(mPluginWindow);
+ }
+}
+
+#else // XP_MACOSX
+
void nsPluginInstanceOwner::UpdateWindowPositionAndClipRect(bool aSetWindow)
{
if (!mPluginWindow)
@@ -2318,11 +3201,12 @@ nsPluginInstanceOwner::UpdateWindowVisibility(bool aVisible)
mPluginWindowVisible = aVisible;
UpdateWindowPositionAndClipRect(true);
}
+#endif // XP_MACOSX
void
nsPluginInstanceOwner::ResolutionMayHaveChanged()
{
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
double scaleFactor = 1.0;
GetContentsScaleFactor(&scaleFactor);
if (scaleFactor != mLastScaleFactor) {
@@ -2347,6 +3231,7 @@ nsPluginInstanceOwner::UpdateDocumentActiveState(bool aIsActive)
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
mPluginDocumentActiveState = aIsActive;
+#ifndef XP_MACOSX
UpdateWindowPositionAndClipRect(true);
// We don't have a connection to PluginWidgetParent in the chrome
@@ -2358,6 +3243,7 @@ nsPluginInstanceOwner::UpdateDocumentActiveState(bool aIsActive)
mWidget->Show(aIsActive);
mWidget->Enable(aIsActive);
}
+#endif // #ifndef XP_MACOSX
}
NS_IMETHODIMP
@@ -2385,10 +3271,10 @@ nsPluginInstanceOwner::GetContentsScaleFactor(double *result)
{
NS_ENSURE_ARG_POINTER(result);
double scaleFactor = 1.0;
- // On Windows, device pixels need to be translated to (and from) "display pixels"
+ // On Mac, device pixels need to be translated to (and from) "display pixels"
// for plugins. On other platforms, plugin coordinates are always in device
// pixels.
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsCOMPtr<nsIContent> content = do_QueryReferent(mContent);
nsIPresShell* presShell = nsContentUtils::FindPresShellForDocument(content->OwnerDoc());
if (presShell) {
diff --git a/dom/plugins/base/nsPluginInstanceOwner.h b/dom/plugins/base/nsPluginInstanceOwner.h
index ba16cf288c..2fa67c86e0 100644
--- a/dom/plugins/base/nsPluginInstanceOwner.h
+++ b/dom/plugins/base/nsPluginInstanceOwner.h
@@ -19,6 +19,11 @@
#include "nsWeakReference.h"
#include "gfxRect.h"
+#ifdef XP_MACOSX
+#include "mozilla/gfx/QuartzSupport.h"
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
class nsIInputStream;
class nsPluginDOMContextMenuListener;
class nsPluginFrame;
@@ -99,6 +104,10 @@ public:
#ifdef XP_WIN
void Paint(const RECT& aDirty, HDC aDC);
+#elif defined(XP_MACOSX)
+ void Paint(const gfxRect& aDirtyRect, CGContextRef cgContext);
+ void RenderCoreAnimation(CGContextRef aCGContext, int aWidth, int aHeight);
+ void DoCocoaEventDrawRect(const gfxRect& aDrawRect, CGContextRef cgContext);
#elif defined(MOZ_X11)
void Paint(gfxContext* aContext,
const gfxRect& aFrameRect,
@@ -123,11 +132,33 @@ public:
nsresult SetNetscapeWindowAsParent(HWND aWindowToAdopt);
#endif
+#ifdef XP_MACOSX
+ enum { ePluginPaintEnable, ePluginPaintDisable };
+
+ void WindowFocusMayHaveChanged();
+
+ bool WindowIsActive();
+ void SendWindowFocusChanged(bool aIsActive);
+ NPDrawingModel GetDrawingModel();
+ bool IsRemoteDrawingCoreAnimation();
+
+ NPEventModel GetEventModel();
+ static void CARefresh(nsITimer *aTimer, void *aClosure);
+ void AddToCARefreshTimer();
+ void RemoveFromCARefreshTimer();
+ // This calls into the plugin (NPP_SetWindow) and can run script.
+ void FixUpPluginWindow(int32_t inPaintState);
+ void HidePluginWindow();
+ // Set plugin port info in the plugin (in the 'window' member of the
+ // NPWindow structure passed to the plugin by SetWindow()).
+ void SetPluginPort();
+#else // XP_MACOSX
void UpdateWindowPositionAndClipRect(bool aSetWindow);
void UpdateWindowVisibility(bool aVisible);
+#endif // XP_MACOSX
void ResolutionMayHaveChanged();
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsresult ContentsScaleFactorChanged(double aContentsScaleFactor);
#endif
@@ -183,7 +214,7 @@ public:
return mPluginWindow->type == NPWindowTypeDrawable &&
(MatchPluginName("Shockwave Flash") ||
MatchPluginName("Test Plug-in"));
-#elif defined(MOZ_X11)
+#elif defined(MOZ_X11) || defined(XP_MACOSX)
return true;
#else
return false;
@@ -280,6 +311,15 @@ private:
nsCOMPtr<nsIWidget> mWidget;
RefPtr<nsPluginHost> mPluginHost;
+#ifdef XP_MACOSX
+ static nsCOMPtr<nsITimer> *sCATimer;
+ static nsTArray<nsPluginInstanceOwner*> *sCARefreshListeners;
+ bool mSentInitialTopLevelWindowEvent;
+ bool mLastWindowIsActive;
+ bool mLastContentFocused;
+ // True if, the next time the window is activated, we should blur ourselves.
+ bool mShouldBlurOnActivate;
+#endif
double mLastScaleFactor;
double mLastCSSZoomFactor;
// Initially, the event loop nesting level we were created on, it's updated
@@ -295,6 +335,15 @@ private:
bool mPluginWindowVisible;
bool mPluginDocumentActiveState;
+#ifdef XP_MACOSX
+ NPEventModel mEventModel;
+ // This is a hack! UseAsyncRendering() can incorrectly return false
+ // when we don't have an object frame (possible as of bug 90268).
+ // We hack around this by always returning true if we've ever
+ // returned true.
+ bool mUseAsyncRendering;
+#endif
+
// pointer to wrapper for nsIDOMContextMenuListener
RefPtr<nsPluginDOMContextMenuListener> mCXMenuListener;
@@ -308,6 +357,16 @@ private:
void CallDefaultProc(const mozilla::WidgetGUIEvent* aEvent);
#endif
+#ifdef XP_MACOSX
+ static NPBool ConvertPointPuppet(PuppetWidget *widget, nsPluginFrame* pluginFrame,
+ double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
+ double *destX, double *destY, NPCoordinateSpace destSpace);
+ static NPBool ConvertPointNoPuppet(nsIWidget *widget, nsPluginFrame* pluginFrame,
+ double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
+ double *destX, double *destY, NPCoordinateSpace destSpace);
+ void PerformDelayedBlurs();
+#endif // XP_MACOSX
+
int mLastMouseDownButtonType;
#ifdef MOZ_X11
diff --git a/dom/plugins/base/nsPluginNativeWindow.cpp b/dom/plugins/base/nsPluginNativeWindow.cpp
index 1c251936a5..f9baf5b813 100644
--- a/dom/plugins/base/nsPluginNativeWindow.cpp
+++ b/dom/plugins/base/nsPluginNativeWindow.cpp
@@ -27,7 +27,7 @@ nsPluginNativeWindowPLATFORM::nsPluginNativeWindowPLATFORM() : nsPluginNativeWin
width = 0;
height = 0;
memset(&clipRect, 0, sizeof(clipRect));
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
ws_info = nullptr;
#endif
type = NPWindowTypeWindow;
diff --git a/dom/plugins/base/nsPluginTags.cpp b/dom/plugins/base/nsPluginTags.cpp
index ae5ba87586..99e243a203 100644
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -424,7 +424,7 @@ void nsPluginTag::InitMime(const char* const* aMimeTypes,
}
}
-#if !defined(XP_WIN)
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
static nsresult ConvertToUTF8(nsIUnicodeDecoder *aUnicodeDecoder,
nsAFlatCString& aString)
{
@@ -448,7 +448,7 @@ static nsresult ConvertToUTF8(nsIUnicodeDecoder *aUnicodeDecoder,
nsresult nsPluginTag::EnsureMembersAreUTF8()
{
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
return NS_OK;
#else
nsresult rv;
diff --git a/dom/plugins/base/nsPluginsDirDarwin.cpp b/dom/plugins/base/nsPluginsDirDarwin.cpp
new file mode 100644
index 0000000000..0085eec0d6
--- /dev/null
+++ b/dom/plugins/base/nsPluginsDirDarwin.cpp
@@ -0,0 +1,572 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+ nsPluginsDirDarwin.cpp
+
+ Mac OS X implementation of the nsPluginsDir/nsPluginsFile classes.
+
+ by Patrick C. Beard.
+ */
+
+#include "GeckoChildProcessHost.h"
+#include "base/process_util.h"
+
+#include "prlink.h"
+#include "prnetdb.h"
+#include "nsXPCOM.h"
+
+#include "nsPluginsDir.h"
+#include "nsNPAPIPlugin.h"
+#include "nsPluginsDirUtils.h"
+
+#include "nsILocalFileMac.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCocoaFeatures.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <Carbon/Carbon.h>
+#include <CoreServices/CoreServices.h>
+#include <mach-o/loader.h>
+#include <mach-o/fat.h>
+
+typedef NS_NPAPIPLUGIN_CALLBACK(const char *, NP_GETMIMEDESCRIPTION) ();
+typedef NS_NPAPIPLUGIN_CALLBACK(OSErr, BP_GETSUPPORTEDMIMETYPES) (BPSupportedMIMETypes *mimeInfo, UInt32 flags);
+
+/*
+** Returns a CFBundleRef if the path refers to a Mac OS X bundle directory.
+** The caller is responsible for calling CFRelease() to deallocate.
+*/
+static CFBundleRef getPluginBundle(const char* path)
+{
+ CFBundleRef bundle = nullptr;
+ CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, path,
+ kCFStringEncodingUTF8);
+ if (pathRef) {
+ CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef,
+ kCFURLPOSIXPathStyle,
+ true);
+ if (bundleURL) {
+ bundle = ::CFBundleCreate(nullptr, bundleURL);
+ ::CFRelease(bundleURL);
+ }
+ ::CFRelease(pathRef);
+ }
+ return bundle;
+}
+
+static nsresult toCFURLRef(nsIFile* file, CFURLRef& outURL)
+{
+ nsCOMPtr<nsILocalFileMac> lfm = do_QueryInterface(file);
+ if (!lfm)
+ return NS_ERROR_FAILURE;
+ CFURLRef url;
+ nsresult rv = lfm->GetCFURL(&url);
+ if (NS_SUCCEEDED(rv))
+ outURL = url;
+
+ return rv;
+}
+
+bool nsPluginsDir::IsPluginFile(nsIFile* file)
+{
+ nsCString fileName;
+ file->GetNativeLeafName(fileName);
+ /*
+ * Don't load the VDP fake plugin, to avoid tripping a bad bug in OS X
+ * 10.5.3 (see bug 436575).
+ */
+ if (!strcmp(fileName.get(), "VerifiedDownloadPlugin.plugin")) {
+ NS_WARNING("Preventing load of VerifiedDownloadPlugin.plugin (see bug 436575)");
+ return false;
+ }
+ return true;
+}
+
+// Caller is responsible for freeing returned buffer.
+static char* CFStringRefToUTF8Buffer(CFStringRef cfString)
+{
+ const char* buffer = ::CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
+ if (buffer) {
+ return PL_strdup(buffer);
+ }
+
+ int bufferLength =
+ ::CFStringGetMaximumSizeForEncoding(::CFStringGetLength(cfString),
+ kCFStringEncodingUTF8) + 1;
+ char* newBuffer = static_cast<char*>(moz_xmalloc(bufferLength));
+ if (!newBuffer) {
+ return nullptr;
+ }
+
+ if (!::CFStringGetCString(cfString, newBuffer, bufferLength,
+ kCFStringEncodingUTF8)) {
+ free(newBuffer);
+ return nullptr;
+ }
+
+ newBuffer = static_cast<char*>(moz_xrealloc(newBuffer,
+ strlen(newBuffer) + 1));
+ return newBuffer;
+}
+
+class AutoCFTypeObject {
+public:
+ explicit AutoCFTypeObject(CFTypeRef aObject)
+ {
+ mObject = aObject;
+ }
+ ~AutoCFTypeObject()
+ {
+ ::CFRelease(mObject);
+ }
+private:
+ CFTypeRef mObject;
+};
+
+static Boolean MimeTypeEnabled(CFDictionaryRef mimeDict) {
+ if (!mimeDict) {
+ return true;
+ }
+
+ CFTypeRef value;
+ if (::CFDictionaryGetValueIfPresent(mimeDict, CFSTR("WebPluginTypeEnabled"), &value)) {
+ if (value && ::CFGetTypeID(value) == ::CFBooleanGetTypeID()) {
+ return ::CFBooleanGetValue(static_cast<CFBooleanRef>(value));
+ }
+ }
+ return true;
+}
+
+static CFDictionaryRef ParsePlistForMIMETypesFilename(CFBundleRef bundle)
+{
+ CFTypeRef mimeFileName = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypesFilename"));
+ if (!mimeFileName || ::CFGetTypeID(mimeFileName) != ::CFStringGetTypeID()) {
+ return nullptr;
+ }
+
+ FSRef homeDir;
+ if (::FSFindFolder(kUserDomain, kCurrentUserFolderType, kDontCreateFolder, &homeDir) != noErr) {
+ return nullptr;
+ }
+
+ CFURLRef userDirURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, &homeDir);
+ if (!userDirURL) {
+ return nullptr;
+ }
+
+ AutoCFTypeObject userDirURLAutorelease(userDirURL);
+ CFStringRef mimeFilePath = ::CFStringCreateWithFormat(kCFAllocatorDefault, nullptr, CFSTR("Library/Preferences/%@"), static_cast<CFStringRef>(mimeFileName));
+ if (!mimeFilePath) {
+ return nullptr;
+ }
+
+ AutoCFTypeObject mimeFilePathAutorelease(mimeFilePath);
+ CFURLRef mimeFileURL = ::CFURLCreateWithFileSystemPathRelativeToBase(kCFAllocatorDefault, mimeFilePath, kCFURLPOSIXPathStyle, false, userDirURL);
+ if (!mimeFileURL) {
+ return nullptr;
+ }
+
+ AutoCFTypeObject mimeFileURLAutorelease(mimeFileURL);
+ SInt32 errorCode = 0;
+ CFDataRef mimeFileData = nullptr;
+ Boolean result = ::CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, mimeFileURL, &mimeFileData, nullptr, nullptr, &errorCode);
+ if (!result) {
+ return nullptr;
+ }
+
+ AutoCFTypeObject mimeFileDataAutorelease(mimeFileData);
+ if (errorCode != 0) {
+ return nullptr;
+ }
+
+ CFPropertyListRef propertyList = ::CFPropertyListCreateFromXMLData(kCFAllocatorDefault, mimeFileData, kCFPropertyListImmutable, nullptr);
+ if (!propertyList) {
+ return nullptr;
+ }
+
+ AutoCFTypeObject propertyListAutorelease(propertyList);
+ if (::CFGetTypeID(propertyList) != ::CFDictionaryGetTypeID()) {
+ return nullptr;
+ }
+
+ CFTypeRef mimeTypes = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(propertyList), CFSTR("WebPluginMIMETypes"));
+ if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0) {
+ return nullptr;
+ }
+
+ return static_cast<CFDictionaryRef>(::CFRetain(mimeTypes));
+}
+
+static void ParsePlistPluginInfo(nsPluginInfo& info, CFBundleRef bundle)
+{
+ CFDictionaryRef mimeDict = ParsePlistForMIMETypesFilename(bundle);
+
+ if (!mimeDict) {
+ CFTypeRef mimeTypes = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginMIMETypes"));
+ if (!mimeTypes || ::CFGetTypeID(mimeTypes) != ::CFDictionaryGetTypeID() || ::CFDictionaryGetCount(static_cast<CFDictionaryRef>(mimeTypes)) == 0)
+ return;
+ mimeDict = static_cast<CFDictionaryRef>(::CFRetain(mimeTypes));
+ }
+
+ AutoCFTypeObject mimeDictAutorelease(mimeDict);
+ int mimeDictKeyCount = ::CFDictionaryGetCount(mimeDict);
+
+ // Allocate memory for mime data
+ int mimeDataArraySize = mimeDictKeyCount * sizeof(char*);
+ info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize));
+ if (!info.fMimeTypeArray)
+ return;
+ memset(info.fMimeTypeArray, 0, mimeDataArraySize);
+ info.fExtensionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize));
+ if (!info.fExtensionArray)
+ return;
+ memset(info.fExtensionArray, 0, mimeDataArraySize);
+ info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(mimeDataArraySize));
+ if (!info.fMimeDescriptionArray)
+ return;
+ memset(info.fMimeDescriptionArray, 0, mimeDataArraySize);
+
+ // Allocate memory for mime dictionary keys and values
+ mozilla::UniquePtr<CFTypeRef[]> keys(new CFTypeRef[mimeDictKeyCount]);
+ if (!keys)
+ return;
+ mozilla::UniquePtr<CFTypeRef[]> values(new CFTypeRef[mimeDictKeyCount]);
+ if (!values)
+ return;
+
+ info.fVariantCount = 0;
+
+ ::CFDictionaryGetKeysAndValues(mimeDict, keys.get(), values.get());
+ for (int i = 0; i < mimeDictKeyCount; i++) {
+ CFTypeRef mimeString = keys[i];
+ if (!mimeString || ::CFGetTypeID(mimeString) != ::CFStringGetTypeID()) {
+ continue;
+ }
+ CFTypeRef mimeDict = values[i];
+ if (mimeDict && ::CFGetTypeID(mimeDict) == ::CFDictionaryGetTypeID()) {
+ if (!MimeTypeEnabled(static_cast<CFDictionaryRef>(mimeDict))) {
+ continue;
+ }
+ info.fMimeTypeArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(mimeString));
+ if (!info.fMimeTypeArray[info.fVariantCount]) {
+ continue;
+ }
+ CFTypeRef extensions = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginExtensions"));
+ if (extensions && ::CFGetTypeID(extensions) == ::CFArrayGetTypeID()) {
+ int extensionCount = ::CFArrayGetCount(static_cast<CFArrayRef>(extensions));
+ CFMutableStringRef extensionList = ::CFStringCreateMutable(kCFAllocatorDefault, 0);
+ for (int j = 0; j < extensionCount; j++) {
+ CFTypeRef extension = ::CFArrayGetValueAtIndex(static_cast<CFArrayRef>(extensions), j);
+ if (extension && ::CFGetTypeID(extension) == ::CFStringGetTypeID()) {
+ if (j > 0)
+ ::CFStringAppend(extensionList, CFSTR(","));
+ ::CFStringAppend(static_cast<CFMutableStringRef>(extensionList), static_cast<CFStringRef>(extension));
+ }
+ }
+ info.fExtensionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(extensionList));
+ ::CFRelease(extensionList);
+ }
+ CFTypeRef description = ::CFDictionaryGetValue(static_cast<CFDictionaryRef>(mimeDict), CFSTR("WebPluginTypeDescription"));
+ if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID())
+ info.fMimeDescriptionArray[info.fVariantCount] = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description));
+ }
+ info.fVariantCount++;
+ }
+}
+
+nsPluginFile::nsPluginFile(nsIFile *spec)
+ : mPlugin(spec)
+{
+}
+
+nsPluginFile::~nsPluginFile() {}
+
+nsresult nsPluginFile::LoadPlugin(PRLibrary **outLibrary)
+{
+ if (!mPlugin)
+ return NS_ERROR_NULL_POINTER;
+
+ // 64-bit NSPR does not (yet) support bundles. So in 64-bit builds we need
+ // (for now) to load the bundle's executable. However this can cause
+ // problems: CFBundleCreate() doesn't run the bundle's executable's
+ // initialization code, while NSAddImage() and dlopen() do run it. So using
+ // NSPR's dyld loading mechanisms here (NSAddImage() or dlopen()) can cause
+ // a bundle's initialization code to run earlier than expected, and lead to
+ // crashes. See bug 577967.
+#ifdef __LP64__
+ char executablePath[PATH_MAX];
+ executablePath[0] = '\0';
+ nsAutoCString bundlePath;
+ mPlugin->GetNativePath(bundlePath);
+ CFStringRef pathRef = ::CFStringCreateWithCString(nullptr, bundlePath.get(),
+ kCFStringEncodingUTF8);
+ if (pathRef) {
+ CFURLRef bundleURL = ::CFURLCreateWithFileSystemPath(nullptr, pathRef,
+ kCFURLPOSIXPathStyle,
+ true);
+ if (bundleURL) {
+ CFBundleRef bundle = ::CFBundleCreate(nullptr, bundleURL);
+ if (bundle) {
+ CFURLRef executableURL = ::CFBundleCopyExecutableURL(bundle);
+ if (executableURL) {
+ if (!::CFURLGetFileSystemRepresentation(executableURL, true, (UInt8*)&executablePath, PATH_MAX))
+ executablePath[0] = '\0';
+ ::CFRelease(executableURL);
+ }
+ ::CFRelease(bundle);
+ }
+ ::CFRelease(bundleURL);
+ }
+ ::CFRelease(pathRef);
+ }
+#else
+ nsAutoCString bundlePath;
+ mPlugin->GetNativePath(bundlePath);
+ const char *executablePath = bundlePath.get();
+#endif
+
+ *outLibrary = PR_LoadLibrary(executablePath);
+ pLibrary = *outLibrary;
+ if (!pLibrary) {
+ return NS_ERROR_FAILURE;
+ }
+#ifdef DEBUG
+ printf("[loaded plugin %s]\n", bundlePath.get());
+#endif
+ return NS_OK;
+}
+
+static char* p2cstrdup(StringPtr pstr)
+{
+ int len = pstr[0];
+ char* cstr = static_cast<char*>(moz_xmalloc(len + 1));
+ if (cstr) {
+ memmove(cstr, pstr + 1, len);
+ cstr[len] = '\0';
+ }
+ return cstr;
+}
+
+static char* GetNextPluginStringFromHandle(Handle h, short *index)
+{
+ char *ret = p2cstrdup((unsigned char*)(*h + *index));
+ *index += (ret ? strlen(ret) : 0) + 1;
+ return ret;
+}
+
+static bool IsCompatibleArch(nsIFile *file)
+{
+ CFURLRef pluginURL = nullptr;
+ if (NS_FAILED(toCFURLRef(file, pluginURL)))
+ return false;
+
+ bool isPluginFile = false;
+
+ CFBundleRef pluginBundle = ::CFBundleCreate(kCFAllocatorDefault, pluginURL);
+ if (pluginBundle) {
+ UInt32 packageType, packageCreator;
+ ::CFBundleGetPackageInfo(pluginBundle, &packageType, &packageCreator);
+ if (packageType == 'BRPL' || packageType == 'IEPL' || packageType == 'NSPL') {
+ // Get path to plugin as a C string.
+ char executablePath[PATH_MAX];
+ executablePath[0] = '\0';
+ if (!::CFURLGetFileSystemRepresentation(pluginURL, true, (UInt8*)&executablePath, PATH_MAX)) {
+ executablePath[0] = '\0';
+ }
+
+ uint32_t pluginLibArchitectures;
+ nsresult rv = mozilla::ipc::GeckoChildProcessHost::GetArchitecturesForBinary(executablePath, &pluginLibArchitectures);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ uint32_t supportedArchitectures =
+#ifdef __LP64__
+ mozilla::ipc::GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin);
+#else
+ base::GetCurrentProcessArchitecture();
+#endif
+
+ // Consider the plugin architecture valid if there is any overlap in the masks.
+ isPluginFile = !!(supportedArchitectures & pluginLibArchitectures);
+ }
+ ::CFRelease(pluginBundle);
+ }
+
+ ::CFRelease(pluginURL);
+ return isPluginFile;
+}
+
+/**
+ * Obtains all of the information currently available for this plugin.
+ */
+nsresult nsPluginFile::GetPluginInfo(nsPluginInfo& info, PRLibrary **outLibrary)
+{
+ *outLibrary = nullptr;
+
+ nsresult rv = NS_OK;
+
+ if (!IsCompatibleArch(mPlugin)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // clear out the info, except for the first field.
+ memset(&info, 0, sizeof(info));
+
+ // Try to get a bundle reference.
+ nsAutoCString path;
+ if (NS_FAILED(rv = mPlugin->GetNativePath(path)))
+ return rv;
+ CFBundleRef bundle = getPluginBundle(path.get());
+
+ // fill in full path
+ info.fFullPath = PL_strdup(path.get());
+
+ // fill in file name
+ nsAutoCString fileName;
+ if (NS_FAILED(rv = mPlugin->GetNativeLeafName(fileName)))
+ return rv;
+ info.fFileName = PL_strdup(fileName.get());
+
+ // Get fName
+ if (bundle) {
+ CFTypeRef name = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginName"));
+ if (name && ::CFGetTypeID(name) == ::CFStringGetTypeID())
+ info.fName = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(name));
+ }
+
+ // Get fDescription
+ if (bundle) {
+ CFTypeRef description = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("WebPluginDescription"));
+ if (description && ::CFGetTypeID(description) == ::CFStringGetTypeID())
+ info.fDescription = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(description));
+ }
+
+ // Get fVersion
+ if (bundle) {
+ // Look for the release version first
+ CFTypeRef version = ::CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("CFBundleShortVersionString"));
+ if (!version) // try the build version
+ version = ::CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleVersionKey);
+ if (version && ::CFGetTypeID(version) == ::CFStringGetTypeID())
+ info.fVersion = CFStringRefToUTF8Buffer(static_cast<CFStringRef>(version));
+ }
+
+ // The last thing we need to do is get MIME data
+ // fVariantCount, fMimeTypeArray, fExtensionArray, fMimeDescriptionArray
+
+ // First look for data in a bundle plist
+ if (bundle) {
+ ParsePlistPluginInfo(info, bundle);
+ ::CFRelease(bundle);
+ if (info.fVariantCount > 0)
+ return NS_OK;
+ }
+
+ // Don't load "fbplugin" or any plugins whose name starts with "fbplugin_"
+ // (Facebook plugins) if we're running on OS X 10.10 (Yosemite) or later.
+ // A "fbplugin" file crashes on load, in the call to LoadPlugin() below.
+ // See bug 1086977.
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ if (fileName.EqualsLiteral("fbplugin") ||
+ StringBeginsWith(fileName, NS_LITERAL_CSTRING("fbplugin_"))) {
+ nsAutoCString msg;
+ msg.AppendPrintf("Preventing load of %s (see bug 1086977)",
+ fileName.get());
+ NS_WARNING(msg.get());
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // It's possible that our plugin has 2 entry points that'll give us mime type
+ // info. Quicktime does this to get around the need of having admin rights to
+ // change mime info in the resource fork. We need to use this info instead of
+ // the resource. See bug 113464.
+
+ // Sadly we have to load the library for this to work.
+ rv = LoadPlugin(outLibrary);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // Try to get data from NP_GetMIMEDescription
+ if (pLibrary) {
+ NP_GETMIMEDESCRIPTION pfnGetMimeDesc = (NP_GETMIMEDESCRIPTION)PR_FindFunctionSymbol(pLibrary, NP_GETMIMEDESCRIPTION_NAME);
+ if (pfnGetMimeDesc)
+ ParsePluginMimeDescription(pfnGetMimeDesc(), info);
+ if (info.fVariantCount)
+ return NS_OK;
+ }
+
+ // We'll fill this in using BP_GetSupportedMIMETypes and/or resource fork data
+ BPSupportedMIMETypes mi = {kBPSupportedMIMETypesStructVers_1, nullptr, nullptr};
+
+ // Try to get data from BP_GetSupportedMIMETypes
+ if (pLibrary) {
+ BP_GETSUPPORTEDMIMETYPES pfnMime = (BP_GETSUPPORTEDMIMETYPES)PR_FindFunctionSymbol(pLibrary, "BP_GetSupportedMIMETypes");
+ if (pfnMime && noErr == pfnMime(&mi, 0) && mi.typeStrings) {
+ info.fVariantCount = (**(short**)mi.typeStrings) / 2;
+ ::HLock(mi.typeStrings);
+ if (mi.infoStrings) // it's possible some plugins have infoStrings missing
+ ::HLock(mi.infoStrings);
+ }
+ }
+
+ // Fill in the info struct based on the data in the BPSupportedMIMETypes struct
+ int variantCount = info.fVariantCount;
+ info.fMimeTypeArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*)));
+ if (!info.fMimeTypeArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+ info.fExtensionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*)));
+ if (!info.fExtensionArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+ if (mi.infoStrings) {
+ info.fMimeDescriptionArray = static_cast<char**>(moz_xmalloc(variantCount * sizeof(char*)));
+ if (!info.fMimeDescriptionArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ short mimeIndex = 2;
+ short descriptionIndex = 2;
+ for (int i = 0; i < variantCount; i++) {
+ info.fMimeTypeArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex);
+ info.fExtensionArray[i] = GetNextPluginStringFromHandle(mi.typeStrings, &mimeIndex);
+ if (mi.infoStrings)
+ info.fMimeDescriptionArray[i] = GetNextPluginStringFromHandle(mi.infoStrings, &descriptionIndex);
+ }
+
+ ::HUnlock(mi.typeStrings);
+ ::DisposeHandle(mi.typeStrings);
+ if (mi.infoStrings) {
+ ::HUnlock(mi.infoStrings);
+ ::DisposeHandle(mi.infoStrings);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsPluginFile::FreePluginInfo(nsPluginInfo& info)
+{
+ free(info.fName);
+ free(info.fDescription);
+ int variantCount = info.fVariantCount;
+ for (int i = 0; i < variantCount; i++) {
+ free(info.fMimeTypeArray[i]);
+ free(info.fExtensionArray[i]);
+ free(info.fMimeDescriptionArray[i]);
+ }
+ free(info.fMimeTypeArray);
+ free(info.fMimeDescriptionArray);
+ free(info.fExtensionArray);
+ free(info.fFileName);
+ free(info.fFullPath);
+ free(info.fVersion);
+
+ return NS_OK;
+}
diff --git a/dom/plugins/ipc/NPEventOSX.h b/dom/plugins/ipc/NPEventOSX.h
new file mode 100644
index 0000000000..efb6845d04
--- /dev/null
+++ b/dom/plugins/ipc/NPEventOSX.h
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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/. */
+
+#ifndef mozilla_dom_plugins_NPEventOSX_h
+#define mozilla_dom_plugins_NPEventOSX_h 1
+
+
+#include "npapi.h"
+
+namespace mozilla {
+
+namespace plugins {
+
+struct NPRemoteEvent {
+ NPCocoaEvent event;
+ double contentsScaleFactor;
+};
+
+} // namespace plugins
+
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::plugins::NPRemoteEvent>
+{
+ typedef mozilla::plugins::NPRemoteEvent paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ aMsg->WriteInt(aParam.event.type);
+ aMsg->WriteUInt32(aParam.event.version);
+ switch (aParam.event.type) {
+ case NPCocoaEventMouseDown:
+ case NPCocoaEventMouseUp:
+ case NPCocoaEventMouseMoved:
+ case NPCocoaEventMouseEntered:
+ case NPCocoaEventMouseExited:
+ case NPCocoaEventMouseDragged:
+ case NPCocoaEventScrollWheel:
+ aMsg->WriteUInt32(aParam.event.data.mouse.modifierFlags);
+ aMsg->WriteDouble(aParam.event.data.mouse.pluginX);
+ aMsg->WriteDouble(aParam.event.data.mouse.pluginY);
+ aMsg->WriteInt32(aParam.event.data.mouse.buttonNumber);
+ aMsg->WriteInt32(aParam.event.data.mouse.clickCount);
+ aMsg->WriteDouble(aParam.event.data.mouse.deltaX);
+ aMsg->WriteDouble(aParam.event.data.mouse.deltaY);
+ aMsg->WriteDouble(aParam.event.data.mouse.deltaZ);
+ break;
+ case NPCocoaEventKeyDown:
+ case NPCocoaEventKeyUp:
+ case NPCocoaEventFlagsChanged:
+ aMsg->WriteUInt32(aParam.event.data.key.modifierFlags);
+ WriteParam(aMsg, aParam.event.data.key.characters);
+ WriteParam(aMsg, aParam.event.data.key.charactersIgnoringModifiers);
+ aMsg->WriteUnsignedChar(aParam.event.data.key.isARepeat);
+ aMsg->WriteUInt16(aParam.event.data.key.keyCode);
+ break;
+ case NPCocoaEventFocusChanged:
+ case NPCocoaEventWindowFocusChanged:
+ aMsg->WriteUnsignedChar(aParam.event.data.focus.hasFocus);
+ break;
+ case NPCocoaEventDrawRect:
+ // We don't write out the context pointer, it would always be
+ // nullptr and is just filled in as such on the read.
+ aMsg->WriteDouble(aParam.event.data.draw.x);
+ aMsg->WriteDouble(aParam.event.data.draw.y);
+ aMsg->WriteDouble(aParam.event.data.draw.width);
+ aMsg->WriteDouble(aParam.event.data.draw.height);
+ break;
+ case NPCocoaEventTextInput:
+ WriteParam(aMsg, aParam.event.data.text.text);
+ break;
+ default:
+ NS_NOTREACHED("Attempted to serialize unknown event type.");
+ return;
+ }
+ aMsg->WriteDouble(aParam.contentsScaleFactor);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ int type = 0;
+ if (!aMsg->ReadInt(aIter, &type)) {
+ return false;
+ }
+ aResult->event.type = static_cast<NPCocoaEventType>(type);
+
+ if (!aMsg->ReadUInt32(aIter, &aResult->event.version)) {
+ return false;
+ }
+
+ switch (aResult->event.type) {
+ case NPCocoaEventMouseDown:
+ case NPCocoaEventMouseUp:
+ case NPCocoaEventMouseMoved:
+ case NPCocoaEventMouseEntered:
+ case NPCocoaEventMouseExited:
+ case NPCocoaEventMouseDragged:
+ case NPCocoaEventScrollWheel:
+ if (!aMsg->ReadUInt32(aIter, &aResult->event.data.mouse.modifierFlags)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginX)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.pluginY)) {
+ return false;
+ }
+ if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.buttonNumber)) {
+ return false;
+ }
+ if (!aMsg->ReadInt32(aIter, &aResult->event.data.mouse.clickCount)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaX)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaY)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.mouse.deltaZ)) {
+ return false;
+ }
+ break;
+ case NPCocoaEventKeyDown:
+ case NPCocoaEventKeyUp:
+ case NPCocoaEventFlagsChanged:
+ if (!aMsg->ReadUInt32(aIter, &aResult->event.data.key.modifierFlags)) {
+ return false;
+ }
+ if (!ReadParam(aMsg, aIter, &aResult->event.data.key.characters)) {
+ return false;
+ }
+ if (!ReadParam(aMsg, aIter, &aResult->event.data.key.charactersIgnoringModifiers)) {
+ return false;
+ }
+ if (!aMsg->ReadUnsignedChar(aIter, &aResult->event.data.key.isARepeat)) {
+ return false;
+ }
+ if (!aMsg->ReadUInt16(aIter, &aResult->event.data.key.keyCode)) {
+ return false;
+ }
+ break;
+ case NPCocoaEventFocusChanged:
+ case NPCocoaEventWindowFocusChanged:
+ if (!aMsg->ReadUnsignedChar(aIter, &aResult->event.data.focus.hasFocus)) {
+ return false;
+ }
+ break;
+ case NPCocoaEventDrawRect:
+ aResult->event.data.draw.context = nullptr;
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.x)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.y)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.width)) {
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->event.data.draw.height)) {
+ return false;
+ }
+ break;
+ case NPCocoaEventTextInput:
+ if (!ReadParam(aMsg, aIter, &aResult->event.data.text.text)) {
+ return false;
+ }
+ break;
+ default:
+ NS_NOTREACHED("Attempted to de-serialize unknown event type.");
+ return false;
+ }
+ if (!aMsg->ReadDouble(aIter, &aResult->contentsScaleFactor)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ aLog->append(L"(NPCocoaEvent)");
+ }
+};
+
+} // namespace IPC
+
+#endif // ifndef mozilla_dom_plugins_NPEventOSX_h
diff --git a/dom/plugins/ipc/PluginInstanceChild.cpp b/dom/plugins/ipc/PluginInstanceChild.cpp
index 03f7fa1611..a4f6b6b517 100644
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -103,7 +103,11 @@ using mozilla::gfx::SharedDIB;
const int kFlashWMUSERMessageThrottleDelayMs = 5;
static const TCHAR kPluginIgnoreSubclassProperty[] = TEXT("PluginIgnoreSubclassProperty");
-#endif
+
+#elif defined(XP_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#include "PluginUtilsOSX.h"
+#endif // defined(XP_MACOSX)
/**
* We can't use gfxPlatform::CreateDrawTargetForSurface() because calling
@@ -137,7 +141,7 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
, mMode(aMode)
, mNames(aNames)
, mValues(aValues)
-#if defined (XP_WIN)
+#if defined(XP_DARWIN) || defined (XP_WIN)
, mContentsScaleFactor(1.0)
#endif
, mPostingKeyEvents(0)
@@ -194,7 +198,7 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
mWindow.type = NPWindowTypeWindow;
mData.ndata = (void*) this;
mData.pdata = nullptr;
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
mWindow.ws_info = &mWsInfo;
memset(&mWsInfo, 0, sizeof(mWsInfo));
#ifdef MOZ_WIDGET_GTK
@@ -203,7 +207,7 @@ PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface,
#else
mWsInfo.display = DefaultXDisplay();
#endif
-#endif // MOZ_X11 && XP_UNIX
+#endif // MOZ_X11 && XP_UNIX && !XP_MACOSX
#if defined(OS_WIN)
InitPopupMenuHook();
if (GetQuirks() & QUIRK_UNITY_FIXUP_MOUSE_CAPTURE) {
@@ -270,6 +274,23 @@ PluginInstanceChild::DoNPP_New()
Initialize();
+#if defined(XP_MACOSX) && defined(__i386__)
+ // If an i386 Mac OS X plugin has selected the Carbon event model then
+ // we have to fail. We do not support putting Carbon event model plugins
+ // out of process. Note that Carbon is the default model so out of process
+ // plugins need to actively negotiate something else in order to work
+ // out of process.
+ if (EventModel() == NPEventModelCarbon) {
+ // Send notification that a plugin tried to negotiate Carbon NPAPI so that
+ // users can be notified that restarting the browser in i386 mode may allow
+ // them to use the plugin.
+ SendNegotiatedCarbon();
+
+ // Fail to instantiate.
+ rv = NPERR_MODULE_LOAD_FAILED_ERROR;
+ }
+#endif
+
return rv;
}
@@ -477,12 +498,58 @@ PluginInstanceChild::NPN_GetValue(NPNVariable aVar,
}
#endif
-#if defined(XP_WIN)
+#ifdef XP_MACOSX
+ case NPNVsupportsCoreGraphicsBool: {
+ *((NPBool*)aValue) = true;
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsCoreAnimationBool: {
+ *((NPBool*)aValue) = true;
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsInvalidatingCoreAnimationBool: {
+ *((NPBool*)aValue) = true;
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsCompositingCoreAnimationPluginsBool: {
+ *((NPBool*)aValue) = true;
+ return NPERR_NO_ERROR;
+ }
+
+ case NPNVsupportsCocoaBool: {
+ *((NPBool*)aValue) = true;
+ return NPERR_NO_ERROR;
+ }
+
+#ifndef NP_NO_CARBON
+ case NPNVsupportsCarbonBool: {
+ *((NPBool*)aValue) = false;
+ return NPERR_NO_ERROR;
+ }
+#endif
+
+ case NPNVsupportsUpdatedCocoaTextInputBool: {
+ *static_cast<NPBool*>(aValue) = true;
+ return NPERR_NO_ERROR;
+ }
+
+#ifndef NP_NO_QUICKDRAW
+ case NPNVsupportsQuickDrawBool: {
+ *((NPBool*)aValue) = false;
+ return NPERR_NO_ERROR;
+ }
+#endif /* NP_NO_QUICKDRAW */
+#endif /* XP_MACOSX */
+
+#if defined(XP_MACOSX) || defined(XP_WIN)
case NPNVcontentsScaleFactor: {
*static_cast<double*>(aValue) = mContentsScaleFactor;
return NPERR_NO_ERROR;
}
-#endif /* defined(XP_WIN) */
+#endif /* defined(XP_MACOSX) || defined(XP_WIN) */
case NPNVCSSZoomFactor: {
*static_cast<double*>(aValue) = mCSSZoomFactor;
@@ -587,12 +654,36 @@ PluginInstanceChild::NPN_SetValue(NPPVariable aVar, void* aValue)
mDrawingModel = drawingModel;
+#ifdef XP_MACOSX
+ if (drawingModel == NPDrawingModelCoreAnimation) {
+ mCARefreshTimer = ScheduleTimer(DEFAULT_REFRESH_MS, true, CAUpdate);
+ }
+#endif
+
PLUGIN_LOG_DEBUG((" Plugin requested drawing model id #%i\n",
mDrawingModel));
return rv;
}
+#ifdef XP_MACOSX
+ case NPPVpluginEventModel: {
+ NPError rv;
+ int eventModel = (int16_t) (intptr_t) aValue;
+
+ if (!CallNPN_SetValue_NPPVpluginEventModel(eventModel, &rv))
+ return NPERR_GENERIC_ERROR;
+#if defined(__i386__)
+ mEventModel = static_cast<NPEventModel>(eventModel);
+#endif
+
+ PLUGIN_LOG_DEBUG((" Plugin requested event model id # %i\n",
+ eventModel));
+
+ return rv;
+ }
+#endif
+
case NPPVpluginIsPlayingAudio: {
NPError rv = NPERR_GENERIC_ERROR;
if (!CallNPN_SetValue_NPPVpluginIsPlayingAudio((NPBool)(intptr_t)aValue, &rv)) {
@@ -816,10 +907,21 @@ PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event,
event.event.xgraphicsexpose.drawable));
#endif
+#ifdef XP_MACOSX
+ // Mac OS X does not define an NPEvent structure. It defines more specific types.
+ NPCocoaEvent evcopy = event.event;
+
+ // Make sure we reset mCurrentEvent in case of an exception
+ AutoRestore<const NPCocoaEvent*> savePreviousEvent(mCurrentEvent);
+
+ // Track the current event for NPN_PopUpContextMenu.
+ mCurrentEvent = &event.event;
+#else
// Make a copy since we may modify values.
NPEvent evcopy = event.event;
+#endif
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
// event.contentsScaleFactor <= 0 is a signal we shouldn't use it,
// for example when AnswerNPP_HandleEvent() is called from elsewhere
// in the child process (not via rpc code from the parent process).
@@ -845,6 +947,18 @@ PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event,
else
*handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy));
+#ifdef XP_MACOSX
+ // Release any reference counted objects created in the child process.
+ if (evcopy.type == NPCocoaEventKeyDown ||
+ evcopy.type == NPCocoaEventKeyUp) {
+ ::CFRelease((CFStringRef)evcopy.data.key.characters);
+ ::CFRelease((CFStringRef)evcopy.data.key.charactersIgnoringModifiers);
+ }
+ else if (evcopy.type == NPCocoaEventTextInput) {
+ ::CFRelease((CFStringRef)evcopy.data.text.text);
+ }
+#endif
+
#ifdef MOZ_X11
if (GraphicsExpose == event.event.type) {
// Make sure the X server completes the drawing before the parent
@@ -861,6 +975,73 @@ PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event,
return true;
}
+#ifdef XP_MACOSX
+
+bool
+PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event,
+ Shmem&& mem,
+ int16_t* handled,
+ Shmem* rtnmem)
+{
+ PLUGIN_LOG_DEBUG_FUNCTION;
+ AssertPluginThread();
+ AutoStackHelper guard(this);
+
+ PaintTracker pt;
+
+ NPCocoaEvent evcopy = event.event;
+ mContentsScaleFactor = event.contentsScaleFactor;
+
+ if (evcopy.type == NPCocoaEventDrawRect) {
+ int scaleFactor = ceil(mContentsScaleFactor);
+ if (!mShColorSpace) {
+ mShColorSpace = CreateSystemColorSpace();
+ if (!mShColorSpace) {
+ PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace."));
+ *handled = false;
+ *rtnmem = mem;
+ return true;
+ }
+ }
+ if (!mShContext) {
+ void* cgContextByte = mem.get<char>();
+ mShContext = ::CGBitmapContextCreate(cgContextByte,
+ mWindow.width * scaleFactor,
+ mWindow.height * scaleFactor, 8,
+ mWindow.width * 4 * scaleFactor, mShColorSpace,
+ kCGImageAlphaPremultipliedFirst |
+ kCGBitmapByteOrder32Host);
+
+ if (!mShContext) {
+ PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext."));
+ *handled = false;
+ *rtnmem = mem;
+ return true;
+ }
+ }
+ CGRect clearRect = ::CGRectMake(0, 0, mWindow.width, mWindow.height);
+ ::CGContextClearRect(mShContext, clearRect);
+ evcopy.data.draw.context = mShContext;
+ } else {
+ PLUGIN_LOG_DEBUG(("Invalid event type for AnswerNNP_HandleEvent_Shmem."));
+ *handled = false;
+ *rtnmem = mem;
+ return true;
+ }
+
+ if (!mPluginIface->event) {
+ *handled = false;
+ } else {
+ ::CGContextSaveGState(evcopy.data.draw.context);
+ *handled = mPluginIface->event(&mData, reinterpret_cast<void*>(&evcopy));
+ ::CGContextRestoreGState(evcopy.data.draw.context);
+ }
+
+ *rtnmem = mem;
+ return true;
+}
+
+#else
bool
PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event,
Shmem&& mem,
@@ -871,7 +1052,101 @@ PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event,
*rtnmem = mem;
return true;
}
+#endif
+
+#ifdef XP_MACOSX
+
+void CallCGDraw(CGContextRef ref, void* aPluginInstance, nsIntRect aUpdateRect) {
+ PluginInstanceChild* pluginInstance = (PluginInstanceChild*)aPluginInstance;
+
+ pluginInstance->CGDraw(ref, aUpdateRect);
+}
+
+bool
+PluginInstanceChild::CGDraw(CGContextRef ref, nsIntRect aUpdateRect) {
+
+ NPCocoaEvent drawEvent;
+ drawEvent.type = NPCocoaEventDrawRect;
+ drawEvent.version = 0;
+ drawEvent.data.draw.x = aUpdateRect.x;
+ drawEvent.data.draw.y = aUpdateRect.y;
+ drawEvent.data.draw.width = aUpdateRect.width;
+ drawEvent.data.draw.height = aUpdateRect.height;
+ drawEvent.data.draw.context = ref;
+
+ NPRemoteEvent remoteDrawEvent = {drawEvent};
+ // Signal to AnswerNPP_HandleEvent() not to use this value
+ remoteDrawEvent.contentsScaleFactor = -1.0;
+
+ int16_t handled;
+ AnswerNPP_HandleEvent(remoteDrawEvent, &handled);
+ return handled == true;
+}
+
+bool
+PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface(const NPRemoteEvent& event,
+ const uint32_t &surfaceid,
+ int16_t* handled)
+{
+ PLUGIN_LOG_DEBUG_FUNCTION;
+ AssertPluginThread();
+ AutoStackHelper guard(this);
+
+ PaintTracker pt;
+
+ NPCocoaEvent evcopy = event.event;
+ mContentsScaleFactor = event.contentsScaleFactor;
+ RefPtr<MacIOSurface> surf = MacIOSurface::LookupSurface(surfaceid,
+ mContentsScaleFactor);
+ if (!surf) {
+ NS_ERROR("Invalid IOSurface.");
+ *handled = false;
+ return false;
+ }
+
+ if (!mCARenderer) {
+ mCARenderer = new nsCARenderer();
+ }
+
+ if (evcopy.type == NPCocoaEventDrawRect) {
+ mCARenderer->AttachIOSurface(surf);
+ if (!mCARenderer->isInit()) {
+ void *caLayer = nullptr;
+ NPError result = mPluginIface->getvalue(GetNPP(),
+ NPPVpluginCoreAnimationLayer,
+ &caLayer);
+
+ if (result != NPERR_NO_ERROR || !caLayer) {
+ PLUGIN_LOG_DEBUG(("Plugin requested CoreAnimation but did not "
+ "provide CALayer."));
+ *handled = false;
+ return false;
+ }
+
+ mCARenderer->SetupRenderer(caLayer, mWindow.width, mWindow.height,
+ mContentsScaleFactor,
+ GetQuirks() & QUIRK_ALLOW_OFFLINE_RENDERER ?
+ ALLOW_OFFLINE_RENDERER : DISALLOW_OFFLINE_RENDERER);
+
+ // Flash needs to have the window set again after this step
+ if (mPluginIface->setwindow)
+ (void) mPluginIface->setwindow(&mData, &mWindow);
+ }
+ } else {
+ PLUGIN_LOG_DEBUG(("Invalid event type for "
+ "AnswerNNP_HandleEvent_IOSurface."));
+ *handled = false;
+ return false;
+ }
+
+ mCARenderer->Render(mWindow.width, mWindow.height,
+ mContentsScaleFactor, nullptr);
+
+ return true;
+
+}
+#else
bool
PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface(const NPRemoteEvent& event,
const uint32_t &surfaceid,
@@ -880,6 +1155,7 @@ PluginInstanceChild::AnswerNPP_HandleEvent_IOSurface(const NPRemoteEvent& event,
NS_RUNTIMEABORT("NPP_HandleEvent_IOSurface is a OSX-only message");
return false;
}
+#endif
bool
PluginInstanceChild::RecvWindowPosChanged(const NPRemoteEvent& event)
@@ -899,16 +1175,24 @@ PluginInstanceChild::RecvWindowPosChanged(const NPRemoteEvent& event)
bool
PluginInstanceChild::RecvContentsScaleFactorChanged(const double& aContentsScaleFactor)
{
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
mContentsScaleFactor = aContentsScaleFactor;
+#if defined(XP_MACOSX)
+ if (mShContext) {
+ // Release the shared context so that it is reallocated
+ // with the new size.
+ ::CGContextRelease(mShContext);
+ mShContext = nullptr;
+ }
+#endif
return true;
#else
- NS_RUNTIMEABORT("ContentsScaleFactorChanged is a Windows-only message");
+ NS_RUNTIMEABORT("ContentsScaleFactorChanged is an Windows or OSX only message");
return false;
#endif
}
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
// Create a new window from NPWindow
bool PluginInstanceChild::CreateWindow(const NPRemoteWindow& aWindow)
{
@@ -1006,7 +1290,7 @@ PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow)
AssertPluginThread();
AutoStackHelper guard(this);
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
NS_ASSERTION(mWsInfo.display, "We should have a valid display!");
// The minimum info is sent over IPC to allow this
@@ -1123,6 +1407,26 @@ PluginInstanceChild::AnswerNPP_SetWindow(const NPRemoteWindow& aWindow)
break;
}
+#elif defined(XP_MACOSX)
+
+ mWindow.x = aWindow.x;
+ mWindow.y = aWindow.y;
+ mWindow.width = aWindow.width;
+ mWindow.height = aWindow.height;
+ mWindow.clipRect = aWindow.clipRect;
+ mWindow.type = aWindow.type;
+ mContentsScaleFactor = aWindow.contentsScaleFactor;
+
+ if (mShContext) {
+ // Release the shared context so that it is reallocated
+ // with the new size.
+ ::CGContextRelease(mShContext);
+ mShContext = nullptr;
+ }
+
+ if (mPluginIface->setwindow)
+ (void) mPluginIface->setwindow(&mData, &mWindow);
+
#elif defined(MOZ_WIDGET_UIKIT)
// Don't care
#else
@@ -3068,7 +3372,7 @@ PluginInstanceChild::DoAsyncSetWindow(const gfxSurfaceType& aSurfaceType,
mWindow.height = aWindow.height;
mWindow.clipRect = aWindow.clipRect;
mWindow.type = aWindow.type;
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
mContentsScaleFactor = aWindow.contentsScaleFactor;
#endif
@@ -3216,6 +3520,7 @@ PluginInstanceChild::MaybeCreatePlatformHelperSurface(void)
bool
PluginInstanceChild::EnsureCurrentBuffer(void)
{
+#ifndef XP_DARWIN
nsIntRect toInvalidate(0, 0, 0, 0);
IntSize winSize = IntSize(mWindow.width, mWindow.height);
@@ -3259,6 +3564,61 @@ PluginInstanceChild::EnsureCurrentBuffer(void)
NS_ERROR("Cannot create helper surface");
return false;
}
+
+ return true;
+#elif defined(XP_MACOSX)
+
+ if (!mDoubleBufferCARenderer.HasCALayer()) {
+ void *caLayer = nullptr;
+ if (mDrawingModel == NPDrawingModelCoreGraphics) {
+ if (!mCGLayer) {
+ caLayer = mozilla::plugins::PluginUtilsOSX::GetCGLayer(CallCGDraw,
+ this,
+ mContentsScaleFactor);
+
+ if (!caLayer) {
+ PLUGIN_LOG_DEBUG(("GetCGLayer failed."));
+ return false;
+ }
+ }
+ mCGLayer = caLayer;
+ } else {
+ NPError result = mPluginIface->getvalue(GetNPP(),
+ NPPVpluginCoreAnimationLayer,
+ &caLayer);
+ if (result != NPERR_NO_ERROR || !caLayer) {
+ PLUGIN_LOG_DEBUG(("Plugin requested CoreAnimation but did not "
+ "provide CALayer."));
+ return false;
+ }
+ }
+ mDoubleBufferCARenderer.SetCALayer(caLayer);
+ }
+
+ if (mDoubleBufferCARenderer.HasFrontSurface() &&
+ (mDoubleBufferCARenderer.GetFrontSurfaceWidth() != mWindow.width ||
+ mDoubleBufferCARenderer.GetFrontSurfaceHeight() != mWindow.height ||
+ mDoubleBufferCARenderer.GetContentsScaleFactor() != mContentsScaleFactor)) {
+ mDoubleBufferCARenderer.ClearFrontSurface();
+ }
+
+ if (!mDoubleBufferCARenderer.HasFrontSurface()) {
+ bool allocSurface = mDoubleBufferCARenderer.InitFrontSurface(
+ mWindow.width, mWindow.height, mContentsScaleFactor,
+ GetQuirks() & QUIRK_ALLOW_OFFLINE_RENDERER ?
+ ALLOW_OFFLINE_RENDERER : DISALLOW_OFFLINE_RENDERER);
+ if (!allocSurface) {
+ PLUGIN_LOG_DEBUG(("Fail to allocate front IOSurface"));
+ return false;
+ }
+
+ if (mPluginIface->setwindow)
+ (void) mPluginIface->setwindow(&mData, &mWindow);
+
+ nsIntRect toInvalidate(0, 0, mWindow.width, mWindow.height);
+ mAccumulatedInvalidRect.UnionRect(mAccumulatedInvalidRect, toInvalidate);
+ }
+#endif
return true;
}
@@ -3302,6 +3662,8 @@ PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow)
return;
}
+#ifndef XP_MACOSX
+ // Adjusting the window isn't needed for OSX
#ifndef XP_WIN
// On Windows, we translate the device context, in order for the window
// origin to be correct.
@@ -3322,6 +3684,7 @@ PluginInstanceChild::UpdateWindowAttributes(bool aForceSetWindow)
mWindow.clipRect.right = clipRect.XMost();
mWindow.clipRect.bottom = clipRect.YMost();
}
+#endif // XP_MACOSX
#ifdef XP_WIN
// Windowless plugins on Windows need a WM_WINDOWPOSCHANGED event to update
@@ -4292,7 +4655,7 @@ PluginInstanceChild::Destroy()
xt_client_xloop_destroy();
}
#endif
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
DeleteWindow();
#endif
}
diff --git a/dom/plugins/ipc/PluginInstanceChild.h b/dom/plugins/ipc/PluginInstanceChild.h
index 627d37c367..0ad6e145d7 100644
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -206,7 +206,7 @@ protected:
virtual bool
RecvNPP_DidComposite() override;
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
bool CreateWindow(const NPRemoteWindow& aWindow);
void DeleteWindow();
#endif
@@ -414,7 +414,7 @@ private:
InfallibleTArray<nsCString> mValues;
NPP_t mData;
NPWindow mWindow;
-#if defined(XP_WIN)
+#if defined(XP_DARWIN) || defined(XP_WIN)
double mContentsScaleFactor;
#endif
double mCSSZoomFactor;
@@ -456,7 +456,7 @@ private:
PluginScriptableObjectChild* mCachedWindowActor;
PluginScriptableObjectChild* mCachedElementActor;
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
NPSetWindowCallbackStruct mWsInfo;
#ifdef MOZ_WIDGET_GTK
bool mXEmbed;
@@ -520,10 +520,15 @@ private:
bool CanPaintOnBackground();
bool IsVisible() {
+#ifdef XP_MACOSX
+ return mWindow.clipRect.top != mWindow.clipRect.bottom &&
+ mWindow.clipRect.left != mWindow.clipRect.right;
+#else
return mWindow.clipRect.top != 0 ||
mWindow.clipRect.left != 0 ||
mWindow.clipRect.bottom != 0 ||
mWindow.clipRect.right != 0;
+#endif
}
// ShowPluginFrame - in general does four things:
@@ -604,6 +609,12 @@ private:
// surface which is on ParentProcess side
RefPtr<gfxASurface> mBackSurface;
+#ifdef XP_MACOSX
+ // Current IOSurface available for rendering
+ // We can't use thebes gfxASurface like other platforms.
+ PluginUtilsOSX::nsDoubleBufferCARenderer mDoubleBufferCARenderer;
+#endif
+
// (Not to be confused with mBackSurface). This is a recent copy
// of the opaque pixels under our object frame, if
// |mIsTransparent|. We ask the plugin render directly onto a
diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp
index d72c9de49b..372a7a238b 100644
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -50,6 +50,10 @@
# include "mozilla/layers/TextureD3D11.h"
#endif
+#ifdef XP_MACOSX
+#include "MacIOSurfaceImage.h"
+#endif
+
#if defined(OS_WIN)
#include <windowsx.h>
#include "gfxWindowsPlatform.h"
@@ -63,7 +67,9 @@ extern const wchar_t* kFlashFullscreenClass;
#elif defined(MOZ_WIDGET_GTK)
#include "mozilla/dom/ContentChild.h"
#include <gdk/gdk.h>
-#endif
+#elif defined(XP_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#endif // defined(XP_MACOSX)
using namespace mozilla;
using namespace mozilla::plugins;
@@ -128,6 +134,11 @@ PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent,
, mPluginWndProc(nullptr)
, mNestedEventState(false)
#endif // defined(XP_WIN)
+#if defined(XP_MACOSX)
+ , mShWidth(0)
+ , mShHeight(0)
+ , mShColorSpace(nullptr)
+#endif
{
#if defined(OS_WIN)
if (!sPluginInstanceList) {
@@ -256,6 +267,8 @@ PluginInstanceParent::AnswerNPN_GetValue_NPNVnetscapeWindow(NativeWindowHandle*
HWND id;
#elif defined(MOZ_X11)
XID id;
+#elif defined(XP_DARWIN)
+ intptr_t id;
#else
#warning Implement me
#endif
@@ -445,7 +458,14 @@ PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginDrawingModel(
bool allowed = false;
switch (drawingModel) {
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+ case NPDrawingModelCoreAnimation:
+ case NPDrawingModelInvalidatingCoreAnimation:
+ case NPDrawingModelOpenGL:
+ case NPDrawingModelCoreGraphics:
+ allowed = true;
+ break;
+#elif defined(XP_WIN)
case NPDrawingModelSyncWin:
allowed = true;
break;
@@ -474,6 +494,16 @@ PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginDrawingModel(
int requestModel = drawingModel;
+#ifdef XP_MACOSX
+ if (drawingModel == NPDrawingModelCoreAnimation ||
+ drawingModel == NPDrawingModelInvalidatingCoreAnimation) {
+ // We need to request CoreGraphics otherwise
+ // the nsPluginFrame will try to draw a CALayer
+ // that can not be shared across process.
+ requestModel = NPDrawingModelCoreGraphics;
+ }
+#endif
+
*result = mNPNIface->setvalue(mNPP, NPPVpluginDrawingModel,
(void*)(intptr_t)requestModel);
@@ -484,8 +514,14 @@ bool
PluginInstanceParent::AnswerNPN_SetValue_NPPVpluginEventModel(
const int& eventModel, NPError* result)
{
+#ifdef XP_MACOSX
+ *result = mNPNIface->setvalue(mNPP, NPPVpluginEventModel,
+ (void*)(intptr_t)eventModel);
+ return true;
+#else
*result = NPERR_GENERIC_ERROR;
return true;
+#endif
}
bool
@@ -839,6 +875,35 @@ PluginInstanceParent::RecvShow(const NPRect& updatedRect,
}
surface = gfxSharedImageSurface::Open(newSurface.get_Shmem());
}
+#ifdef XP_MACOSX
+ else if (newSurface.type() == SurfaceDescriptor::TIOSurfaceDescriptor) {
+ IOSurfaceDescriptor iodesc = newSurface.get_IOSurfaceDescriptor();
+
+ RefPtr<MacIOSurface> newIOSurface =
+ MacIOSurface::LookupSurface(iodesc.surfaceId(),
+ iodesc.contentsScaleFactor());
+
+ if (!newIOSurface) {
+ NS_WARNING("Got bad IOSurfaceDescriptor in RecvShow");
+ return false;
+ }
+
+ if (mFrontIOSurface)
+ *prevSurface = IOSurfaceDescriptor(mFrontIOSurface->GetIOSurfaceID(),
+ mFrontIOSurface->GetContentsScaleFactor());
+ else
+ *prevSurface = null_t();
+
+ mFrontIOSurface = newIOSurface;
+
+ RecvNPN_InvalidateRect(updatedRect);
+
+ PLUGIN_LOG_DEBUG((" (RecvShow invalidated for surface %p)",
+ mFrontSurface.get()));
+
+ return true;
+ }
+#endif
#ifdef MOZ_X11
else if (newSurface.type() == SurfaceDescriptor::TSurfaceDescriptorX11) {
surface = newSurface.get_SurfaceDescriptorX11().OpenForeign();
@@ -920,7 +985,7 @@ PluginInstanceParent::AsyncSetWindow(NPWindow* aWindow)
window.height = aWindow->height;
window.clipRect = aWindow->clipRect;
window.type = aWindow->type;
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
double scaleFactor = 1.0;
mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor);
window.contentsScaleFactor = scaleFactor;
@@ -950,7 +1015,19 @@ PluginInstanceParent::GetImageContainer(ImageContainer** aContainer)
return NS_OK;
}
+#ifdef XP_MACOSX
+ MacIOSurface* ioSurface = nullptr;
+
+ if (mFrontIOSurface) {
+ ioSurface = mFrontIOSurface;
+ } else if (mIOSurface) {
+ ioSurface = mIOSurface;
+ }
+
+ if (!mFrontSurface && !ioSurface)
+#else
if (!mFrontSurface)
+#endif
return NS_ERROR_NOT_AVAILABLE;
ImageContainer *container = GetImageContainer();
@@ -959,6 +1036,17 @@ PluginInstanceParent::GetImageContainer(ImageContainer** aContainer)
return NS_ERROR_FAILURE;
}
+#ifdef XP_MACOSX
+ if (ioSurface) {
+ RefPtr<Image> image = new MacIOSurfaceImage(ioSurface);
+ container->SetCurrentImageInTransaction(image);
+
+ NS_IF_ADDREF(container);
+ *aContainer = container;
+ return NS_OK;
+ }
+#endif
+
NS_IF_ADDREF(container);
*aContainer = container;
return NS_OK;
@@ -981,6 +1069,16 @@ PluginInstanceParent::GetImageSize(nsIntSize* aSize)
return NS_OK;
}
+#ifdef XP_MACOSX
+ if (mFrontIOSurface) {
+ *aSize = nsIntSize(mFrontIOSurface->GetWidth(), mFrontIOSurface->GetHeight());
+ return NS_OK;
+ } else if (mIOSurface) {
+ *aSize = nsIntSize(mIOSurface->GetWidth(), mIOSurface->GetHeight());
+ return NS_OK;
+ }
+#endif
+
return NS_ERROR_NOT_AVAILABLE;
}
@@ -993,14 +1091,23 @@ PluginInstanceParent::DidComposite()
Unused << SendNPP_DidComposite();
}
-#if defined(XP_WIN)
+#ifdef XP_MACOSX
+nsresult
+PluginInstanceParent::IsRemoteDrawingCoreAnimation(bool *aDrawing)
+{
+ *aDrawing = (NPDrawingModelCoreAnimation == (NPDrawingModel)mDrawingModel ||
+ NPDrawingModelInvalidatingCoreAnimation == (NPDrawingModel)mDrawingModel);
+ return NS_OK;
+}
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsresult
PluginInstanceParent::ContentsScaleFactorChanged(double aContentsScaleFactor)
{
bool rv = SendContentsScaleFactorChanged(aContentsScaleFactor);
return rv ? NS_OK : NS_ERROR_FAILURE;
}
-#endif // #ifdef XP_WIN
+#endif // #ifdef XP_MACOSX
nsresult
PluginInstanceParent::SetBackgroundUnknown()
@@ -1252,14 +1359,40 @@ PluginInstanceParent::NPP_SetWindow(const NPWindow* aWindow)
window.type = aWindow->type;
#endif
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
double floatScaleFactor = 1.0;
mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &floatScaleFactor);
int scaleFactor = ceil(floatScaleFactor);
window.contentsScaleFactor = floatScaleFactor;
#endif
+#if defined(XP_MACOSX)
+ if (mShWidth != window.width * scaleFactor || mShHeight != window.height * scaleFactor) {
+ if (mDrawingModel == NPDrawingModelCoreAnimation ||
+ mDrawingModel == NPDrawingModelInvalidatingCoreAnimation) {
+ mIOSurface = MacIOSurface::CreateIOSurface(window.width, window.height,
+ floatScaleFactor);
+ } else if (uint32_t(mShWidth * mShHeight) !=
+ window.width * scaleFactor * window.height * scaleFactor) {
+ if (mShWidth != 0 && mShHeight != 0) {
+ DeallocShmem(mShSurface);
+ mShWidth = 0;
+ mShHeight = 0;
+ }
+
+ if (window.width != 0 && window.height != 0) {
+ if (!AllocShmem(window.width * scaleFactor * window.height*4 * scaleFactor,
+ SharedMemory::TYPE_BASIC, &mShSurface)) {
+ PLUGIN_LOG_DEBUG(("Shared memory could not be allocated."));
+ return NPERR_GENERIC_ERROR;
+ }
+ }
+ }
+ mShWidth = window.width * scaleFactor;
+ mShHeight = window.height * scaleFactor;
+ }
+#endif
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
const NPSetWindowCallbackStruct* ws_info =
static_cast<NPSetWindowCallbackStruct*>(aWindow->ws_info);
window.visualID = ws_info->visual ? ws_info->visual->visualid : 0;
@@ -1419,10 +1552,14 @@ PluginInstanceParent::NPP_HandleEvent(void* event)
{
PLUGIN_LOG_DEBUG_FUNCTION;
+#if defined(XP_MACOSX)
+ NPCocoaEvent* npevent = reinterpret_cast<NPCocoaEvent*>(event);
+#else
NPEvent* npevent = reinterpret_cast<NPEvent*>(event);
+#endif
NPRemoteEvent npremoteevent;
npremoteevent.event = *npevent;
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
double scaleFactor = 1.0;
mNPNIface->getvalue(mNPP, NPNVcontentsScaleFactor, &scaleFactor);
npremoteevent.contentsScaleFactor = scaleFactor;
@@ -1512,6 +1649,111 @@ PluginInstanceParent::NPP_HandleEvent(void* event)
}
#endif
+#ifdef XP_MACOSX
+ if (npevent->type == NPCocoaEventDrawRect) {
+ if (mDrawingModel == NPDrawingModelCoreAnimation ||
+ mDrawingModel == NPDrawingModelInvalidatingCoreAnimation) {
+ if (!mIOSurface) {
+ NS_ERROR("No IOSurface allocated.");
+ return false;
+ }
+ if (!CallNPP_HandleEvent_IOSurface(npremoteevent,
+ mIOSurface->GetIOSurfaceID(),
+ &handled))
+ return false; // no good way to handle errors here...
+
+ CGContextRef cgContext = npevent->data.draw.context;
+ if (!mShColorSpace) {
+ mShColorSpace = CreateSystemColorSpace();
+ }
+ if (!mShColorSpace) {
+ PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace."));
+ return false;
+ }
+ if (cgContext) {
+ nsCARenderer::DrawSurfaceToCGContext(cgContext, mIOSurface,
+ mShColorSpace,
+ npevent->data.draw.x,
+ npevent->data.draw.y,
+ npevent->data.draw.width,
+ npevent->data.draw.height);
+ }
+ return true;
+ } else if (mFrontIOSurface) {
+ CGContextRef cgContext = npevent->data.draw.context;
+ if (!mShColorSpace) {
+ mShColorSpace = CreateSystemColorSpace();
+ }
+ if (!mShColorSpace) {
+ PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace."));
+ return false;
+ }
+ if (cgContext) {
+ nsCARenderer::DrawSurfaceToCGContext(cgContext, mFrontIOSurface,
+ mShColorSpace,
+ npevent->data.draw.x,
+ npevent->data.draw.y,
+ npevent->data.draw.width,
+ npevent->data.draw.height);
+ }
+ return true;
+ } else {
+ if (mShWidth == 0 && mShHeight == 0) {
+ PLUGIN_LOG_DEBUG(("NPCocoaEventDrawRect on window of size 0."));
+ return false;
+ }
+ if (!mShSurface.IsReadable()) {
+ PLUGIN_LOG_DEBUG(("Shmem is not readable."));
+ return false;
+ }
+
+ if (!CallNPP_HandleEvent_Shmem(npremoteevent, mShSurface,
+ &handled, &mShSurface))
+ return false; // no good way to handle errors here...
+
+ if (!mShSurface.IsReadable()) {
+ PLUGIN_LOG_DEBUG(("Shmem not returned. Either the plugin crashed "
+ "or we have a bug."));
+ return false;
+ }
+
+ char* shContextByte = mShSurface.get<char>();
+
+ if (!mShColorSpace) {
+ mShColorSpace = CreateSystemColorSpace();
+ }
+ if (!mShColorSpace) {
+ PLUGIN_LOG_DEBUG(("Could not allocate ColorSpace."));
+ return false;
+ }
+ CGContextRef shContext = ::CGBitmapContextCreate(shContextByte,
+ mShWidth, mShHeight, 8,
+ mShWidth*4, mShColorSpace,
+ kCGImageAlphaPremultipliedFirst |
+ kCGBitmapByteOrder32Host);
+ if (!shContext) {
+ PLUGIN_LOG_DEBUG(("Could not allocate CGBitmapContext."));
+ return false;
+ }
+
+ CGImageRef shImage = ::CGBitmapContextCreateImage(shContext);
+ if (shImage) {
+ CGContextRef cgContext = npevent->data.draw.context;
+
+ ::CGContextDrawImage(cgContext,
+ CGRectMake(0,0,mShWidth,mShHeight),
+ shImage);
+ ::CGImageRelease(shImage);
+ } else {
+ ::CGContextRelease(shContext);
+ return false;
+ }
+ ::CGContextRelease(shContext);
+ return true;
+ }
+ }
+#endif
+
if (!CallNPP_HandleEvent(npremoteevent, &handled))
return 0; // no good way to handle errors here...
diff --git a/dom/plugins/ipc/PluginInstanceParent.h b/dom/plugins/ipc/PluginInstanceParent.h
index 7c9febb242..cb85378db6 100644
--- a/dom/plugins/ipc/PluginInstanceParent.h
+++ b/dom/plugins/ipc/PluginInstanceParent.h
@@ -321,7 +321,10 @@ public:
nsresult AsyncSetWindow(NPWindow* window);
nsresult GetImageContainer(mozilla::layers::ImageContainer** aContainer);
nsresult GetImageSize(nsIntSize* aSize);
-#if defined(XP_WIN)
+#ifdef XP_MACOSX
+ nsresult IsRemoteDrawingCoreAnimation(bool *aDrawing);
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsresult ContentsScaleFactorChanged(double aContentsScaleFactor);
#endif
nsresult SetBackgroundUnknown();
diff --git a/dom/plugins/ipc/PluginInterposeOSX.h b/dom/plugins/ipc/PluginInterposeOSX.h
new file mode 100644
index 0000000000..2a742b8aa2
--- /dev/null
+++ b/dom/plugins/ipc/PluginInterposeOSX.h
@@ -0,0 +1,137 @@
+// vim:set ts=2 sts=2 sw=2 et cin:
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H
+#define DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H
+
+#include "base/basictypes.h"
+#include "nsPoint.h"
+#include "npapi.h"
+
+// Make this includable from non-Objective-C code.
+#ifndef __OBJC__
+class NSCursor;
+#else
+#import <Cocoa/Cocoa.h>
+#endif
+
+// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the
+// QuickDraw APIs defined in it are still present) -- so we need to supply the
+// relevant parts of its contents here. It's likely that Apple will eventually
+// remove the APIs themselves (probably in OS X 10.8), so we need to make them
+// weak imports, and test for their presence before using them.
+#if !defined(__QUICKDRAWAPI__)
+
+typedef short Bits16[16];
+struct Cursor {
+ Bits16 data;
+ Bits16 mask;
+ Point hotSpot;
+};
+typedef struct Cursor Cursor;
+
+#endif /* __QUICKDRAWAPI__ */
+
+namespace mac_plugin_interposing {
+
+// Class used to serialize NSCursor objects over IPC between processes.
+class NSCursorInfo {
+public:
+ enum Type {
+ TypeCustom,
+ TypeArrow,
+ TypeClosedHand,
+ TypeContextualMenu, // Only supported on OS X 10.6 and up
+ TypeCrosshair,
+ TypeDisappearingItem,
+ TypeDragCopy, // Only supported on OS X 10.6 and up
+ TypeDragLink, // Only supported on OS X 10.6 and up
+ TypeIBeam,
+ TypeNotAllowed, // Only supported on OS X 10.6 and up
+ TypeOpenHand,
+ TypePointingHand,
+ TypeResizeDown,
+ TypeResizeLeft,
+ TypeResizeLeftRight,
+ TypeResizeRight,
+ TypeResizeUp,
+ TypeResizeUpDown,
+ TypeTransparent // Special type
+ };
+
+ NSCursorInfo();
+ explicit NSCursorInfo(NSCursor* aCursor);
+ explicit NSCursorInfo(const Cursor* aCursor);
+ ~NSCursorInfo();
+
+ NSCursor* GetNSCursor() const;
+ Type GetType() const;
+ const char* GetTypeName() const;
+ nsPoint GetHotSpot() const;
+ uint8_t* GetCustomImageData() const;
+ uint32_t GetCustomImageDataLength() const;
+
+ void SetType(Type aType);
+ void SetHotSpot(nsPoint aHotSpot);
+ void SetCustomImageData(uint8_t* aData, uint32_t aDataLength);
+
+ static bool GetNativeCursorsSupported();
+
+private:
+ NSCursor* GetTransparentCursor() const;
+
+ Type mType;
+ // The hot spot's coordinate system is the cursor's coordinate system, and
+ // has an upper-left origin (in both Cocoa and pre-Cocoa systems).
+ nsPoint mHotSpot;
+ uint8_t* mCustomImageData;
+ uint32_t mCustomImageDataLength;
+ static int32_t mNativeCursorsSupported;
+};
+
+namespace parent {
+
+void OnPluginShowWindow(uint32_t window_id, CGRect window_bounds, bool modal);
+void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid);
+void OnSetCursor(const NSCursorInfo& cursorInfo);
+void OnShowCursor(bool show);
+void OnPushCursor(const NSCursorInfo& cursorInfo);
+void OnPopCursor();
+
+} // namespace parent
+
+namespace child {
+
+void SetUpCocoaInterposing();
+
+} // namespace child
+
+} // namespace mac_plugin_interposing
+
+#endif /* DOM_PLUGINS_IPC_PLUGININTERPOSEOSX_H */
diff --git a/dom/plugins/ipc/PluginInterposeOSX.mm b/dom/plugins/ipc/PluginInterposeOSX.mm
new file mode 100644
index 0000000000..bf24d2b0d8
--- /dev/null
+++ b/dom/plugins/ipc/PluginInterposeOSX.mm
@@ -0,0 +1,1158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "nsCocoaUtils.h"
+#include "PluginModuleChild.h"
+#include "nsDebug.h"
+#include "PluginInterposeOSX.h"
+#include <set>
+#import <AppKit/AppKit.h>
+#import <objc/runtime.h>
+#import <Carbon/Carbon.h>
+
+using namespace mozilla::plugins;
+
+namespace mac_plugin_interposing {
+
+int32_t NSCursorInfo::mNativeCursorsSupported = -1;
+
+// This constructor may be called from the browser process or the plugin
+// process.
+NSCursorInfo::NSCursorInfo()
+ : mType(TypeArrow)
+ , mHotSpot(nsPoint(0, 0))
+ , mCustomImageData(NULL)
+ , mCustomImageDataLength(0)
+{
+}
+
+NSCursorInfo::NSCursorInfo(NSCursor* aCursor)
+ : mType(TypeArrow)
+ , mHotSpot(nsPoint(0, 0))
+ , mCustomImageData(NULL)
+ , mCustomImageDataLength(0)
+{
+ // This constructor is only ever called from the plugin process, so the
+ // following is safe.
+ if (!GetNativeCursorsSupported()) {
+ return;
+ }
+
+ NSPoint hotSpotCocoa = [aCursor hotSpot];
+ mHotSpot = nsPoint(hotSpotCocoa.x, hotSpotCocoa.y);
+
+ Class nsCursorClass = [NSCursor class];
+ if ([aCursor isEqual:[NSCursor arrowCursor]]) {
+ mType = TypeArrow;
+ } else if ([aCursor isEqual:[NSCursor closedHandCursor]]) {
+ mType = TypeClosedHand;
+ } else if ([aCursor isEqual:[NSCursor crosshairCursor]]) {
+ mType = TypeCrosshair;
+ } else if ([aCursor isEqual:[NSCursor disappearingItemCursor]]) {
+ mType = TypeDisappearingItem;
+ } else if ([aCursor isEqual:[NSCursor IBeamCursor]]) {
+ mType = TypeIBeam;
+ } else if ([aCursor isEqual:[NSCursor openHandCursor]]) {
+ mType = TypeOpenHand;
+ } else if ([aCursor isEqual:[NSCursor pointingHandCursor]]) {
+ mType = TypePointingHand;
+ } else if ([aCursor isEqual:[NSCursor resizeDownCursor]]) {
+ mType = TypeResizeDown;
+ } else if ([aCursor isEqual:[NSCursor resizeLeftCursor]]) {
+ mType = TypeResizeLeft;
+ } else if ([aCursor isEqual:[NSCursor resizeLeftRightCursor]]) {
+ mType = TypeResizeLeftRight;
+ } else if ([aCursor isEqual:[NSCursor resizeRightCursor]]) {
+ mType = TypeResizeRight;
+ } else if ([aCursor isEqual:[NSCursor resizeUpCursor]]) {
+ mType = TypeResizeUp;
+ } else if ([aCursor isEqual:[NSCursor resizeUpDownCursor]]) {
+ mType = TypeResizeUpDown;
+ // The following cursor types are only supported on OS X 10.6 and up.
+ } else if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)] &&
+ [aCursor isEqual:[nsCursorClass performSelector:@selector(contextualMenuCursor)]]) {
+ mType = TypeContextualMenu;
+ } else if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)] &&
+ [aCursor isEqual:[nsCursorClass performSelector:@selector(dragCopyCursor)]]) {
+ mType = TypeDragCopy;
+ } else if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)] &&
+ [aCursor isEqual:[nsCursorClass performSelector:@selector(dragLinkCursor)]]) {
+ mType = TypeDragLink;
+ } else if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)] &&
+ [aCursor isEqual:[nsCursorClass performSelector:@selector(operationNotAllowedCursor)]]) {
+ mType = TypeNotAllowed;
+ } else {
+ NSImage* image = [aCursor image];
+ NSArray* reps = image ? [image representations] : nil;
+ NSUInteger repsCount = reps ? [reps count] : 0;
+ if (!repsCount) {
+ // If we have a custom cursor with no image representations, assume we
+ // need a transparent cursor.
+ mType = TypeTransparent;
+ } else {
+ CGImageRef cgImage = nil;
+ // XXX We don't know how to deal with a cursor that doesn't have a
+ // bitmap image representation. For now we fall back to an arrow
+ // cursor.
+ for (NSUInteger i = 0; i < repsCount; ++i) {
+ id rep = [reps objectAtIndex:i];
+ if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
+ cgImage = [(NSBitmapImageRep*)rep CGImage];
+ break;
+ }
+ }
+ if (cgImage) {
+ CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0);
+ if (data) {
+ CGImageDestinationRef dest = ::CGImageDestinationCreateWithData(data,
+ kUTTypePNG,
+ 1,
+ NULL);
+ if (dest) {
+ ::CGImageDestinationAddImage(dest, cgImage, NULL);
+ if (::CGImageDestinationFinalize(dest)) {
+ uint32_t dataLength = (uint32_t) ::CFDataGetLength(data);
+ mCustomImageData = (uint8_t*) moz_xmalloc(dataLength);
+ ::CFDataGetBytes(data, ::CFRangeMake(0, dataLength), mCustomImageData);
+ mCustomImageDataLength = dataLength;
+ mType = TypeCustom;
+ }
+ ::CFRelease(dest);
+ }
+ ::CFRelease(data);
+ }
+ }
+ if (!mCustomImageData) {
+ mType = TypeArrow;
+ }
+ }
+ }
+}
+
+NSCursorInfo::NSCursorInfo(const Cursor* aCursor)
+ : mType(TypeArrow)
+ , mHotSpot(nsPoint(0, 0))
+ , mCustomImageData(NULL)
+ , mCustomImageDataLength(0)
+{
+ // This constructor is only ever called from the plugin process, so the
+ // following is safe.
+ if (!GetNativeCursorsSupported()) {
+ return;
+ }
+
+ mHotSpot = nsPoint(aCursor->hotSpot.h, aCursor->hotSpot.v);
+
+ int width = 16, height = 16;
+ int bytesPerPixel = 4;
+ int rowBytes = width * bytesPerPixel;
+ int bitmapSize = height * rowBytes;
+
+ bool isTransparent = true;
+
+ uint8_t* bitmap = (uint8_t*) moz_xmalloc(bitmapSize);
+ // The way we create 'bitmap' is largely "borrowed" from Chrome's
+ // WebCursor::InitFromCursor().
+ for (int y = 0; y < height; ++y) {
+ unsigned short data = aCursor->data[y];
+ unsigned short mask = aCursor->mask[y];
+ // Change 'data' and 'mask' from big-endian to little-endian, but output
+ // big-endian data below.
+ data = ((data << 8) & 0xFF00) | ((data >> 8) & 0x00FF);
+ mask = ((mask << 8) & 0xFF00) | ((mask >> 8) & 0x00FF);
+ // It'd be nice to use a gray-scale bitmap. But
+ // CGBitmapContextCreateImage() (used below) won't work with one that also
+ // has alpha values.
+ for (int x = 0; x < width; ++x) {
+ int offset = (y * rowBytes) + (x * bytesPerPixel);
+ // Color value
+ if (data & 0x8000) {
+ bitmap[offset] = 0x0;
+ bitmap[offset + 1] = 0x0;
+ bitmap[offset + 2] = 0x0;
+ } else {
+ bitmap[offset] = 0xFF;
+ bitmap[offset + 1] = 0xFF;
+ bitmap[offset + 2] = 0xFF;
+ }
+ // Mask value
+ if (mask & 0x8000) {
+ bitmap[offset + 3] = 0xFF;
+ isTransparent = false;
+ } else {
+ bitmap[offset + 3] = 0x0;
+ }
+ data <<= 1;
+ mask <<= 1;
+ }
+ }
+
+ if (isTransparent) {
+ // If aCursor is transparent, we don't need to serialize custom cursor
+ // data over IPC.
+ mType = TypeTransparent;
+ } else {
+ CGColorSpaceRef color = ::CGColorSpaceCreateDeviceRGB();
+ if (color) {
+ CGContextRef context =
+ ::CGBitmapContextCreate(bitmap,
+ width,
+ height,
+ 8,
+ rowBytes,
+ color,
+ kCGImageAlphaPremultipliedLast |
+ kCGBitmapByteOrder32Big);
+ if (context) {
+ CGImageRef image = ::CGBitmapContextCreateImage(context);
+ if (image) {
+ ::CFMutableDataRef data = ::CFDataCreateMutable(kCFAllocatorDefault, 0);
+ if (data) {
+ CGImageDestinationRef dest =
+ ::CGImageDestinationCreateWithData(data,
+ kUTTypePNG,
+ 1,
+ NULL);
+ if (dest) {
+ ::CGImageDestinationAddImage(dest, image, NULL);
+ if (::CGImageDestinationFinalize(dest)) {
+ uint32_t dataLength = (uint32_t) ::CFDataGetLength(data);
+ mCustomImageData = (uint8_t*) moz_xmalloc(dataLength);
+ ::CFDataGetBytes(data,
+ ::CFRangeMake(0, dataLength),
+ mCustomImageData);
+ mCustomImageDataLength = dataLength;
+ mType = TypeCustom;
+ }
+ ::CFRelease(dest);
+ }
+ ::CFRelease(data);
+ }
+ ::CGImageRelease(image);
+ }
+ ::CGContextRelease(context);
+ }
+ ::CGColorSpaceRelease(color);
+ }
+ }
+
+ free(bitmap);
+}
+
+NSCursorInfo::~NSCursorInfo()
+{
+ if (mCustomImageData) {
+ free(mCustomImageData);
+ }
+}
+
+NSCursor* NSCursorInfo::GetNSCursor() const
+{
+ NSCursor* retval = nil;
+
+ Class nsCursorClass = [NSCursor class];
+ switch(mType) {
+ case TypeArrow:
+ retval = [NSCursor arrowCursor];
+ break;
+ case TypeClosedHand:
+ retval = [NSCursor closedHandCursor];
+ break;
+ case TypeCrosshair:
+ retval = [NSCursor crosshairCursor];
+ break;
+ case TypeDisappearingItem:
+ retval = [NSCursor disappearingItemCursor];
+ break;
+ case TypeIBeam:
+ retval = [NSCursor IBeamCursor];
+ break;
+ case TypeOpenHand:
+ retval = [NSCursor openHandCursor];
+ break;
+ case TypePointingHand:
+ retval = [NSCursor pointingHandCursor];
+ break;
+ case TypeResizeDown:
+ retval = [NSCursor resizeDownCursor];
+ break;
+ case TypeResizeLeft:
+ retval = [NSCursor resizeLeftCursor];
+ break;
+ case TypeResizeLeftRight:
+ retval = [NSCursor resizeLeftRightCursor];
+ break;
+ case TypeResizeRight:
+ retval = [NSCursor resizeRightCursor];
+ break;
+ case TypeResizeUp:
+ retval = [NSCursor resizeUpCursor];
+ break;
+ case TypeResizeUpDown:
+ retval = [NSCursor resizeUpDownCursor];
+ break;
+ // The following four cursor types are only supported on OS X 10.6 and up.
+ case TypeContextualMenu: {
+ if ([nsCursorClass respondsToSelector:@selector(contextualMenuCursor)]) {
+ retval = [nsCursorClass performSelector:@selector(contextualMenuCursor)];
+ }
+ break;
+ }
+ case TypeDragCopy: {
+ if ([nsCursorClass respondsToSelector:@selector(dragCopyCursor)]) {
+ retval = [nsCursorClass performSelector:@selector(dragCopyCursor)];
+ }
+ break;
+ }
+ case TypeDragLink: {
+ if ([nsCursorClass respondsToSelector:@selector(dragLinkCursor)]) {
+ retval = [nsCursorClass performSelector:@selector(dragLinkCursor)];
+ }
+ break;
+ }
+ case TypeNotAllowed: {
+ if ([nsCursorClass respondsToSelector:@selector(operationNotAllowedCursor)]) {
+ retval = [nsCursorClass performSelector:@selector(operationNotAllowedCursor)];
+ }
+ break;
+ }
+ case TypeTransparent:
+ retval = GetTransparentCursor();
+ break;
+ default:
+ break;
+ }
+
+ if (!retval && mCustomImageData && mCustomImageDataLength) {
+ CGDataProviderRef provider = ::CGDataProviderCreateWithData(NULL,
+ (const void*)mCustomImageData,
+ mCustomImageDataLength,
+ NULL);
+ if (provider) {
+ CGImageRef cgImage = ::CGImageCreateWithPNGDataProvider(provider,
+ NULL,
+ false,
+ kCGRenderingIntentDefault);
+ if (cgImage) {
+ NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
+ if (rep) {
+ NSImage* image = [[NSImage alloc] init];
+ if (image) {
+ [image addRepresentation:rep];
+ retval = [[[NSCursor alloc] initWithImage:image
+ hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)]
+ autorelease];
+ [image release];
+ }
+ [rep release];
+ }
+ ::CGImageRelease(cgImage);
+ }
+ ::CFRelease(provider);
+ }
+ }
+
+ // Fall back to an arrow cursor if need be.
+ if (!retval) {
+ retval = [NSCursor arrowCursor];
+ }
+
+ return retval;
+}
+
+// Get a transparent cursor with the appropriate hot spot. We need one if
+// (for example) we have a custom cursor with no image data.
+NSCursor* NSCursorInfo::GetTransparentCursor() const
+{
+ NSCursor* retval = nil;
+
+ int width = 16, height = 16;
+ int bytesPerPixel = 2;
+ int rowBytes = width * bytesPerPixel;
+ int dataSize = height * rowBytes;
+
+ uint8_t* data = (uint8_t*) moz_xmalloc(dataSize);
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ int offset = (y * rowBytes) + (x * bytesPerPixel);
+ data[offset] = 0x7E; // Arbitrary gray-scale value
+ data[offset + 1] = 0; // Alpha value to make us transparent
+ }
+ }
+
+ NSBitmapImageRep* imageRep =
+ [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:2
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSCalibratedWhiteColorSpace
+ bytesPerRow:rowBytes
+ bitsPerPixel:16]
+ autorelease];
+ if (imageRep) {
+ uint8_t* repDataPtr = [imageRep bitmapData];
+ if (repDataPtr) {
+ memcpy(repDataPtr, data, dataSize);
+ NSImage *image =
+ [[[NSImage alloc] initWithSize:NSMakeSize(width, height)]
+ autorelease];
+ if (image) {
+ [image addRepresentation:imageRep];
+ retval =
+ [[[NSCursor alloc] initWithImage:image
+ hotSpot:NSMakePoint(mHotSpot.x, mHotSpot.y)]
+ autorelease];
+ }
+ }
+ }
+
+ free(data);
+
+ // Fall back to an arrow cursor if (for some reason) the above code failed.
+ if (!retval) {
+ retval = [NSCursor arrowCursor];
+ }
+
+ return retval;
+}
+
+NSCursorInfo::Type NSCursorInfo::GetType() const
+{
+ return mType;
+}
+
+const char* NSCursorInfo::GetTypeName() const
+{
+ switch(mType) {
+ case TypeCustom:
+ return "TypeCustom";
+ case TypeArrow:
+ return "TypeArrow";
+ case TypeClosedHand:
+ return "TypeClosedHand";
+ case TypeContextualMenu:
+ return "TypeContextualMenu";
+ case TypeCrosshair:
+ return "TypeCrosshair";
+ case TypeDisappearingItem:
+ return "TypeDisappearingItem";
+ case TypeDragCopy:
+ return "TypeDragCopy";
+ case TypeDragLink:
+ return "TypeDragLink";
+ case TypeIBeam:
+ return "TypeIBeam";
+ case TypeNotAllowed:
+ return "TypeNotAllowed";
+ case TypeOpenHand:
+ return "TypeOpenHand";
+ case TypePointingHand:
+ return "TypePointingHand";
+ case TypeResizeDown:
+ return "TypeResizeDown";
+ case TypeResizeLeft:
+ return "TypeResizeLeft";
+ case TypeResizeLeftRight:
+ return "TypeResizeLeftRight";
+ case TypeResizeRight:
+ return "TypeResizeRight";
+ case TypeResizeUp:
+ return "TypeResizeUp";
+ case TypeResizeUpDown:
+ return "TypeResizeUpDown";
+ case TypeTransparent:
+ return "TypeTransparent";
+ default:
+ break;
+ }
+ return "TypeUnknown";
+}
+
+nsPoint NSCursorInfo::GetHotSpot() const
+{
+ return mHotSpot;
+}
+
+uint8_t* NSCursorInfo::GetCustomImageData() const
+{
+ return mCustomImageData;
+}
+
+uint32_t NSCursorInfo::GetCustomImageDataLength() const
+{
+ return mCustomImageDataLength;
+}
+
+void NSCursorInfo::SetType(Type aType)
+{
+ mType = aType;
+}
+
+void NSCursorInfo::SetHotSpot(nsPoint aHotSpot)
+{
+ mHotSpot = aHotSpot;
+}
+
+void NSCursorInfo::SetCustomImageData(uint8_t* aData, uint32_t aDataLength)
+{
+ if (mCustomImageData) {
+ free(mCustomImageData);
+ }
+ if (aDataLength) {
+ mCustomImageData = (uint8_t*) moz_xmalloc(aDataLength);
+ memcpy(mCustomImageData, aData, aDataLength);
+ } else {
+ mCustomImageData = NULL;
+ }
+ mCustomImageDataLength = aDataLength;
+}
+
+// This should never be called from the browser process -- only from the
+// plugin process.
+bool NSCursorInfo::GetNativeCursorsSupported()
+{
+ if (mNativeCursorsSupported == -1) {
+ ENSURE_PLUGIN_THREAD(false);
+ PluginModuleChild *pmc = PluginModuleChild::GetChrome();
+ if (pmc) {
+ bool result = pmc->GetNativeCursorsSupported();
+ if (result) {
+ mNativeCursorsSupported = 1;
+ } else {
+ mNativeCursorsSupported = 0;
+ }
+ }
+ }
+ return (mNativeCursorsSupported == 1);
+}
+
+} // namespace mac_plugin_interposing
+
+namespace mac_plugin_interposing {
+namespace parent {
+
+// Tracks plugin windows currently visible.
+std::set<uint32_t> plugin_visible_windows_set_;
+// Tracks full screen windows currently visible.
+std::set<uint32_t> plugin_fullscreen_windows_set_;
+// Tracks modal windows currently visible.
+std::set<uint32_t> plugin_modal_windows_set_;
+
+void OnPluginShowWindow(uint32_t window_id,
+ CGRect window_bounds,
+ bool modal) {
+ plugin_visible_windows_set_.insert(window_id);
+
+ if (modal)
+ plugin_modal_windows_set_.insert(window_id);
+
+ CGRect main_display_bounds = ::CGDisplayBounds(CGMainDisplayID());
+
+ if (CGRectEqualToRect(window_bounds, main_display_bounds) &&
+ (plugin_fullscreen_windows_set_.find(window_id) ==
+ plugin_fullscreen_windows_set_.end())) {
+ plugin_fullscreen_windows_set_.insert(window_id);
+
+ nsCocoaUtils::HideOSChromeOnScreen(true);
+ }
+}
+
+static void ActivateProcess(pid_t pid) {
+ ProcessSerialNumber process;
+ OSStatus status = ::GetProcessForPID(pid, &process);
+
+ if (status == noErr) {
+ SetFrontProcess(&process);
+ } else {
+ NS_WARNING("Unable to get process for pid.");
+ }
+}
+
+// Must be called on the UI thread.
+// If plugin_pid is -1, the browser will be the active process on return,
+// otherwise that process will be given focus back before this function returns.
+static void ReleasePluginFullScreen(pid_t plugin_pid) {
+ // Releasing full screen only works if we are the frontmost process; grab
+ // focus, but give it back to the plugin process if requested.
+ ActivateProcess(base::GetCurrentProcId());
+
+ nsCocoaUtils::HideOSChromeOnScreen(false);
+
+ if (plugin_pid != -1) {
+ ActivateProcess(plugin_pid);
+ }
+}
+
+void OnPluginHideWindow(uint32_t window_id, pid_t aPluginPid) {
+ bool had_windows = !plugin_visible_windows_set_.empty();
+ plugin_visible_windows_set_.erase(window_id);
+ bool browser_needs_activation = had_windows &&
+ plugin_visible_windows_set_.empty();
+
+ plugin_modal_windows_set_.erase(window_id);
+ if (plugin_fullscreen_windows_set_.find(window_id) !=
+ plugin_fullscreen_windows_set_.end()) {
+ plugin_fullscreen_windows_set_.erase(window_id);
+ pid_t plugin_pid = browser_needs_activation ? -1 : aPluginPid;
+ browser_needs_activation = false;
+ ReleasePluginFullScreen(plugin_pid);
+ }
+
+ if (browser_needs_activation) {
+ ActivateProcess(getpid());
+ }
+}
+
+void OnSetCursor(const NSCursorInfo& cursorInfo)
+{
+ NSCursor* aCursor = cursorInfo.GetNSCursor();
+ if (aCursor) {
+ [aCursor set];
+ }
+}
+
+void OnShowCursor(bool show)
+{
+ if (show) {
+ [NSCursor unhide];
+ } else {
+ [NSCursor hide];
+ }
+}
+
+void OnPushCursor(const NSCursorInfo& cursorInfo)
+{
+ NSCursor* aCursor = cursorInfo.GetNSCursor();
+ if (aCursor) {
+ [aCursor push];
+ }
+}
+
+void OnPopCursor()
+{
+ [NSCursor pop];
+}
+
+} // namespace parent
+} // namespace mac_plugin_interposing
+
+namespace mac_plugin_interposing {
+namespace child {
+
+// TODO(stuartmorgan): Make this an IPC to order the plugin process above the
+// browser process only if the browser is current frontmost.
+void FocusPluginProcess() {
+ ProcessSerialNumber this_process, front_process;
+ if ((GetCurrentProcess(&this_process) != noErr) ||
+ (GetFrontProcess(&front_process) != noErr)) {
+ return;
+ }
+
+ Boolean matched = false;
+ if ((SameProcess(&this_process, &front_process, &matched) == noErr) &&
+ !matched) {
+ SetFrontProcess(&this_process);
+ }
+}
+
+void NotifyBrowserOfPluginShowWindow(uint32_t window_id, CGRect bounds,
+ bool modal) {
+ ENSURE_PLUGIN_THREAD_VOID();
+
+ PluginModuleChild *pmc = PluginModuleChild::GetChrome();
+ if (pmc)
+ pmc->PluginShowWindow(window_id, modal, bounds);
+}
+
+void NotifyBrowserOfPluginHideWindow(uint32_t window_id, CGRect bounds) {
+ ENSURE_PLUGIN_THREAD_VOID();
+
+ PluginModuleChild *pmc = PluginModuleChild::GetChrome();
+ if (pmc)
+ pmc->PluginHideWindow(window_id);
+}
+
+void NotifyBrowserOfSetCursor(NSCursorInfo& aCursorInfo)
+{
+ ENSURE_PLUGIN_THREAD_VOID();
+ PluginModuleChild *pmc = PluginModuleChild::GetChrome();
+ if (pmc) {
+ pmc->SetCursor(aCursorInfo);
+ }
+}
+
+void NotifyBrowserOfShowCursor(bool show)
+{
+ ENSURE_PLUGIN_THREAD_VOID();
+ PluginModuleChild *pmc = PluginModuleChild::GetChrome();
+ if (pmc) {
+ pmc->ShowCursor(show);
+ }
+}
+
+void NotifyBrowserOfPushCursor(NSCursorInfo& aCursorInfo)
+{
+ ENSURE_PLUGIN_THREAD_VOID();
+ PluginModuleChild *pmc = PluginModuleChild::GetChrome();
+ if (pmc) {
+ pmc->PushCursor(aCursorInfo);
+ }
+}
+
+void NotifyBrowserOfPopCursor()
+{
+ ENSURE_PLUGIN_THREAD_VOID();
+ PluginModuleChild *pmc = PluginModuleChild::GetChrome();
+ if (pmc) {
+ pmc->PopCursor();
+ }
+}
+
+struct WindowInfo {
+ uint32_t window_id;
+ CGRect bounds;
+ explicit WindowInfo(NSWindow* aWindow) {
+ NSInteger window_num = [aWindow windowNumber];
+ window_id = window_num > 0 ? window_num : 0;
+ bounds = NSRectToCGRect([aWindow frame]);
+ }
+};
+
+static void OnPluginWindowClosed(const WindowInfo& window_info) {
+ if (window_info.window_id == 0)
+ return;
+ mac_plugin_interposing::child::NotifyBrowserOfPluginHideWindow(window_info.window_id,
+ window_info.bounds);
+}
+
+static void OnPluginWindowShown(const WindowInfo& window_info, BOOL is_modal) {
+ // The window id is 0 if it has never been shown (including while it is the
+ // process of being shown for the first time); when that happens, we'll catch
+ // it in _setWindowNumber instead.
+ static BOOL s_pending_display_is_modal = NO;
+ if (window_info.window_id == 0) {
+ if (is_modal)
+ s_pending_display_is_modal = YES;
+ return;
+ }
+ if (s_pending_display_is_modal) {
+ is_modal = YES;
+ s_pending_display_is_modal = NO;
+ }
+ mac_plugin_interposing::child::NotifyBrowserOfPluginShowWindow(
+ window_info.window_id, window_info.bounds, is_modal);
+}
+
+static BOOL OnSetCursor(NSCursorInfo &aInfo)
+{
+ if (NSCursorInfo::GetNativeCursorsSupported()) {
+ NotifyBrowserOfSetCursor(aInfo);
+ return YES;
+ }
+ return NO;
+}
+
+static BOOL OnHideCursor()
+{
+ if (NSCursorInfo::GetNativeCursorsSupported()) {
+ NotifyBrowserOfShowCursor(NO);
+ return YES;
+ }
+ return NO;
+}
+
+static BOOL OnUnhideCursor()
+{
+ if (NSCursorInfo::GetNativeCursorsSupported()) {
+ NotifyBrowserOfShowCursor(YES);
+ return YES;
+ }
+ return NO;
+}
+
+static BOOL OnPushCursor(NSCursorInfo &aInfo)
+{
+ if (NSCursorInfo::GetNativeCursorsSupported()) {
+ NotifyBrowserOfPushCursor(aInfo);
+ return YES;
+ }
+ return NO;
+}
+
+static BOOL OnPopCursor()
+{
+ if (NSCursorInfo::GetNativeCursorsSupported()) {
+ NotifyBrowserOfPopCursor();
+ return YES;
+ }
+ return NO;
+}
+
+} // namespace child
+} // namespace mac_plugin_interposing
+
+using namespace mac_plugin_interposing::child;
+
+@interface NSWindow (PluginInterposing)
+- (void)pluginInterpose_orderOut:(id)sender;
+- (void)pluginInterpose_orderFront:(id)sender;
+- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender;
+- (void)pluginInterpose_setWindowNumber:(NSInteger)num;
+@end
+
+@implementation NSWindow (PluginInterposing)
+
+- (void)pluginInterpose_orderOut:(id)sender {
+ WindowInfo window_info(self);
+ [self pluginInterpose_orderOut:sender];
+ OnPluginWindowClosed(window_info);
+}
+
+- (void)pluginInterpose_orderFront:(id)sender {
+ mac_plugin_interposing::child::FocusPluginProcess();
+ [self pluginInterpose_orderFront:sender];
+ OnPluginWindowShown(WindowInfo(self), NO);
+}
+
+- (void)pluginInterpose_makeKeyAndOrderFront:(id)sender {
+ mac_plugin_interposing::child::FocusPluginProcess();
+ [self pluginInterpose_makeKeyAndOrderFront:sender];
+ OnPluginWindowShown(WindowInfo(self), NO);
+}
+
+- (void)pluginInterpose_setWindowNumber:(NSInteger)num {
+ if (num > 0)
+ mac_plugin_interposing::child::FocusPluginProcess();
+ [self pluginInterpose_setWindowNumber:num];
+ if (num > 0)
+ OnPluginWindowShown(WindowInfo(self), NO);
+}
+
+@end
+
+@interface NSApplication (PluginInterposing)
+- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window;
+@end
+
+@implementation NSApplication (PluginInterposing)
+
+- (NSInteger)pluginInterpose_runModalForWindow:(NSWindow*)window {
+ mac_plugin_interposing::child::FocusPluginProcess();
+ // This is out-of-order relative to the other calls, but runModalForWindow:
+ // won't return until the window closes, and the order only matters for
+ // full-screen windows.
+ OnPluginWindowShown(WindowInfo(window), YES);
+ return [self pluginInterpose_runModalForWindow:window];
+}
+
+@end
+
+// Hook commands to manipulate the current cursor, so that they can be passed
+// from the child process to the parent process. These commands have no
+// effect unless they're performed in the parent process.
+@interface NSCursor (PluginInterposing)
+- (void)pluginInterpose_set;
+- (void)pluginInterpose_push;
+- (void)pluginInterpose_pop;
++ (NSCursor*)pluginInterpose_currentCursor;
++ (void)pluginInterpose_hide;
++ (void)pluginInterpose_unhide;
++ (void)pluginInterpose_pop;
+@end
+
+// Cache the results of [NSCursor set], [NSCursor push] and [NSCursor pop].
+// The last element is always the current cursor.
+static NSMutableArray* gCursorStack = nil;
+
+static BOOL initCursorStack()
+{
+ if (!gCursorStack) {
+ gCursorStack = [[NSMutableArray arrayWithCapacity:5] retain];
+ }
+ return (gCursorStack != NULL);
+}
+
+static NSCursor* currentCursorFromCache()
+{
+ if (!initCursorStack())
+ return nil;
+ return (NSCursor*) [gCursorStack lastObject];
+}
+
+static void setCursorInCache(NSCursor* aCursor)
+{
+ if (!initCursorStack() || !aCursor)
+ return;
+ NSUInteger count = [gCursorStack count];
+ if (count) {
+ [gCursorStack replaceObjectAtIndex:count - 1 withObject:aCursor];
+ } else {
+ [gCursorStack addObject:aCursor];
+ }
+}
+
+static void pushCursorInCache(NSCursor* aCursor)
+{
+ if (!initCursorStack() || !aCursor)
+ return;
+ [gCursorStack addObject:aCursor];
+}
+
+static void popCursorInCache()
+{
+ if (!initCursorStack())
+ return;
+ // Apple's doc on the +[NSCursor pop] method says: "If the current cursor
+ // is the only cursor on the stack, this method does nothing."
+ if ([gCursorStack count] > 1) {
+ [gCursorStack removeLastObject];
+ }
+}
+
+@implementation NSCursor (PluginInterposing)
+
+- (void)pluginInterpose_set
+{
+ NSCursorInfo info(self);
+ OnSetCursor(info);
+ setCursorInCache(self);
+ [self pluginInterpose_set];
+}
+
+- (void)pluginInterpose_push
+{
+ NSCursorInfo info(self);
+ OnPushCursor(info);
+ pushCursorInCache(self);
+ [self pluginInterpose_push];
+}
+
+- (void)pluginInterpose_pop
+{
+ OnPopCursor();
+ popCursorInCache();
+ [self pluginInterpose_pop];
+}
+
+// The currentCursor method always returns nil when running in a background
+// process. But this may confuse plugins (notably Flash, see bug 621117). So
+// if we get a nil return from the "call to super", we return a cursor that's
+// been cached by previous calls to set or push. According to Apple's docs,
+// currentCursor "only returns the cursor set by your application using
+// NSCursor methods". So we don't need to worry about changes to the cursor
+// made by other methods like SetThemeCursor().
++ (NSCursor*)pluginInterpose_currentCursor
+{
+ NSCursor* retval = [self pluginInterpose_currentCursor];
+ if (!retval) {
+ retval = currentCursorFromCache();
+ }
+ return retval;
+}
+
++ (void)pluginInterpose_hide
+{
+ OnHideCursor();
+ [self pluginInterpose_hide];
+}
+
++ (void)pluginInterpose_unhide
+{
+ OnUnhideCursor();
+ [self pluginInterpose_unhide];
+}
+
++ (void)pluginInterpose_pop
+{
+ OnPopCursor();
+ popCursorInCache();
+ [self pluginInterpose_pop];
+}
+
+@end
+
+static void ExchangeMethods(Class target_class,
+ BOOL class_method,
+ SEL original,
+ SEL replacement) {
+ Method m1;
+ Method m2;
+ if (class_method) {
+ m1 = class_getClassMethod(target_class, original);
+ m2 = class_getClassMethod(target_class, replacement);
+ } else {
+ m1 = class_getInstanceMethod(target_class, original);
+ m2 = class_getInstanceMethod(target_class, replacement);
+ }
+
+ if (m1 == m2)
+ return;
+
+ if (m1 && m2)
+ method_exchangeImplementations(m1, m2);
+ else
+ NS_NOTREACHED("Cocoa swizzling failed");
+}
+
+namespace mac_plugin_interposing {
+namespace child {
+
+void SetUpCocoaInterposing() {
+ Class nswindow_class = [NSWindow class];
+ ExchangeMethods(nswindow_class, NO, @selector(orderOut:),
+ @selector(pluginInterpose_orderOut:));
+ ExchangeMethods(nswindow_class, NO, @selector(orderFront:),
+ @selector(pluginInterpose_orderFront:));
+ ExchangeMethods(nswindow_class, NO, @selector(makeKeyAndOrderFront:),
+ @selector(pluginInterpose_makeKeyAndOrderFront:));
+ ExchangeMethods(nswindow_class, NO, @selector(_setWindowNumber:),
+ @selector(pluginInterpose_setWindowNumber:));
+
+ ExchangeMethods([NSApplication class], NO, @selector(runModalForWindow:),
+ @selector(pluginInterpose_runModalForWindow:));
+
+ Class nscursor_class = [NSCursor class];
+ ExchangeMethods(nscursor_class, NO, @selector(set),
+ @selector(pluginInterpose_set));
+ ExchangeMethods(nscursor_class, NO, @selector(push),
+ @selector(pluginInterpose_push));
+ ExchangeMethods(nscursor_class, NO, @selector(pop),
+ @selector(pluginInterpose_pop));
+ ExchangeMethods(nscursor_class, YES, @selector(currentCursor),
+ @selector(pluginInterpose_currentCursor));
+ ExchangeMethods(nscursor_class, YES, @selector(hide),
+ @selector(pluginInterpose_hide));
+ ExchangeMethods(nscursor_class, YES, @selector(unhide),
+ @selector(pluginInterpose_unhide));
+ ExchangeMethods(nscursor_class, YES, @selector(pop),
+ @selector(pluginInterpose_pop));
+}
+
+} // namespace child
+} // namespace mac_plugin_interposing
+
+// Called from plugin_child_interpose.mm, which hooks calls to
+// SetCursor() (the QuickDraw call) from the plugin child process.
+extern "C" NS_VISIBILITY_DEFAULT BOOL
+mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor)
+{
+ NSCursorInfo info(cursor);
+ return OnSetCursor(info);
+}
+
+// Called from plugin_child_interpose.mm, which hooks calls to
+// SetThemeCursor() (the Appearance Manager call) from the plugin child
+// process.
+extern "C" NS_VISIBILITY_DEFAULT BOOL
+mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor)
+{
+ NSCursorInfo info;
+ switch (cursor) {
+ case kThemeArrowCursor:
+ info.SetType(NSCursorInfo::TypeArrow);
+ break;
+ case kThemeCopyArrowCursor:
+ info.SetType(NSCursorInfo::TypeDragCopy);
+ break;
+ case kThemeAliasArrowCursor:
+ info.SetType(NSCursorInfo::TypeDragLink);
+ break;
+ case kThemeContextualMenuArrowCursor:
+ info.SetType(NSCursorInfo::TypeContextualMenu);
+ break;
+ case kThemeIBeamCursor:
+ info.SetType(NSCursorInfo::TypeIBeam);
+ break;
+ case kThemeCrossCursor:
+ case kThemePlusCursor:
+ info.SetType(NSCursorInfo::TypeCrosshair);
+ break;
+ case kThemeWatchCursor:
+ case kThemeSpinningCursor:
+ info.SetType(NSCursorInfo::TypeArrow);
+ break;
+ case kThemeClosedHandCursor:
+ info.SetType(NSCursorInfo::TypeClosedHand);
+ break;
+ case kThemeOpenHandCursor:
+ info.SetType(NSCursorInfo::TypeOpenHand);
+ break;
+ case kThemePointingHandCursor:
+ case kThemeCountingUpHandCursor:
+ case kThemeCountingDownHandCursor:
+ case kThemeCountingUpAndDownHandCursor:
+ info.SetType(NSCursorInfo::TypePointingHand);
+ break;
+ case kThemeResizeLeftCursor:
+ info.SetType(NSCursorInfo::TypeResizeLeft);
+ break;
+ case kThemeResizeRightCursor:
+ info.SetType(NSCursorInfo::TypeResizeRight);
+ break;
+ case kThemeResizeLeftRightCursor:
+ info.SetType(NSCursorInfo::TypeResizeLeftRight);
+ break;
+ case kThemeNotAllowedCursor:
+ info.SetType(NSCursorInfo::TypeNotAllowed);
+ break;
+ case kThemeResizeUpCursor:
+ info.SetType(NSCursorInfo::TypeResizeUp);
+ break;
+ case kThemeResizeDownCursor:
+ info.SetType(NSCursorInfo::TypeResizeDown);
+ break;
+ case kThemeResizeUpDownCursor:
+ info.SetType(NSCursorInfo::TypeResizeUpDown);
+ break;
+ case kThemePoofCursor:
+ info.SetType(NSCursorInfo::TypeDisappearingItem);
+ break;
+ default:
+ info.SetType(NSCursorInfo::TypeArrow);
+ break;
+ }
+ return OnSetCursor(info);
+}
+
+extern "C" NS_VISIBILITY_DEFAULT BOOL
+mac_plugin_interposing_child_OnHideCursor()
+{
+ return OnHideCursor();
+}
+
+extern "C" NS_VISIBILITY_DEFAULT BOOL
+mac_plugin_interposing_child_OnShowCursor()
+{
+ return OnUnhideCursor();
+}
diff --git a/dom/plugins/ipc/PluginLibrary.h b/dom/plugins/ipc/PluginLibrary.h
index 50182d0b0d..2f9a3f81b2 100644
--- a/dom/plugins/ipc/PluginLibrary.h
+++ b/dom/plugins/ipc/PluginLibrary.h
@@ -57,7 +57,7 @@ public:
virtual bool HasRequiredFunctions() = 0;
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) = 0;
#else
virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) = 0;
@@ -66,7 +66,7 @@ public:
virtual nsresult NP_GetMIMEDescription(const char** mimeDesc) = 0;
virtual nsresult NP_GetValue(void *future, NPPVariable aVariable,
void *aValue, NPError* error) = 0;
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) = 0;
#endif
virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance,
@@ -83,7 +83,10 @@ public:
virtual nsresult GetImageSize(NPP instance, nsIntSize* aSize) = 0;
virtual void DidComposite(NPP instance) = 0;
virtual bool IsOOP() = 0;
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+ virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) = 0;
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) = 0;
#endif
#if defined(XP_WIN)
diff --git a/dom/plugins/ipc/PluginMessageUtils.cpp b/dom/plugins/ipc/PluginMessageUtils.cpp
index 9b27c76d5a..be1b2d15a5 100644
--- a/dom/plugins/ipc/PluginMessageUtils.cpp
+++ b/dom/plugins/ipc/PluginMessageUtils.cpp
@@ -49,10 +49,13 @@ namespace plugins {
NPRemoteWindow::NPRemoteWindow() :
window(0), x(0), y(0), width(0), height(0), type(NPWindowTypeDrawable)
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
, visualID(0)
, colormap(0)
#endif /* XP_UNIX */
+#if defined(XP_MACOSX)
+ ,contentsScaleFactor(1.0)
+#endif
{
clipRect.top = 0;
clipRect.left = 0;
diff --git a/dom/plugins/ipc/PluginMessageUtils.h b/dom/plugins/ipc/PluginMessageUtils.h
index e06c2cee24..a9cd52ae29 100644
--- a/dom/plugins/ipc/PluginMessageUtils.h
+++ b/dom/plugins/ipc/PluginMessageUtils.h
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=4 ts=4 et :
* 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/. */
@@ -22,7 +23,11 @@
#include "nsTArray.h"
#include "mozilla/Logging.h"
#include "nsHashKeys.h"
+#ifdef XP_MACOSX
+#include "PluginInterposeOSX.h"
+#else
namespace mac_plugin_interposing { class NSCursorInfo { }; }
+#endif
using mac_plugin_interposing::NSCursorInfo;
namespace mozilla {
@@ -82,30 +87,32 @@ struct NPRemoteWindow
uint32_t height;
NPRect clipRect;
NPWindowType type;
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
VisualID visualID;
Colormap colormap;
#endif /* XP_UNIX */
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
double contentsScaleFactor;
#endif
};
-// This struct is like NPAudioDeviceChangeDetails, only it uses a
-// std::wstring instead of a const wchar_t* for the defaultDevice.
-// This gives us the necessary memory-ownership semantics without
-// requiring C++ objects in npapi.h.
-struct NPAudioDeviceChangeDetailsIPC
-{
- int32_t flow;
- int32_t role;
- std::wstring defaultDevice;
+// This struct is like NPAudioDeviceChangeDetails, only it uses a
+// std::wstring instead of a const wchar_t* for the defaultDevice.
+// This gives us the necessary memory-ownership semantics without
+// requiring C++ objects in npapi.h.
+struct NPAudioDeviceChangeDetailsIPC
+{
+ int32_t flow;
+ int32_t role;
+ std::wstring defaultDevice;
};
#ifdef XP_WIN
typedef HWND NativeWindowHandle;
#elif defined(MOZ_X11)
typedef XID NativeWindowHandle;
+#elif defined(XP_DARWIN)
+typedef intptr_t NativeWindowHandle; // never actually used, will always be 0
#else
#error Need NativeWindowHandle for this platform
#endif
@@ -149,6 +156,11 @@ NPPVariableToString(NPPVariable aVar)
VARSTR(NPPVpluginWantsAllNetworkStreams);
+#ifdef XP_MACOSX
+ VARSTR(NPPVpluginDrawingModel);
+ VARSTR(NPPVpluginEventModel);
+#endif
+
#ifdef XP_WIN
VARSTR(NPPVpluginRequiresAudioDeviceChanges);
#endif
@@ -345,11 +357,11 @@ struct ParamTraits<mozilla::plugins::NPRemoteWindow>
WriteParam(aMsg, aParam.height);
WriteParam(aMsg, aParam.clipRect);
WriteParam(aMsg, aParam.type);
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
aMsg->WriteULong(aParam.visualID);
aMsg->WriteULong(aParam.colormap);
#endif
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
aMsg->WriteDouble(aParam.contentsScaleFactor);
#endif
}
@@ -370,7 +382,7 @@ struct ParamTraits<mozilla::plugins::NPRemoteWindow>
ReadParam(aMsg, aIter, &type)))
return false;
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
unsigned long visualID;
unsigned long colormap;
if (!(aMsg->ReadULong(aIter, &visualID) &&
@@ -378,7 +390,7 @@ struct ParamTraits<mozilla::plugins::NPRemoteWindow>
return false;
#endif
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
double contentsScaleFactor;
if (!aMsg->ReadDouble(aIter, &contentsScaleFactor))
return false;
@@ -391,11 +403,11 @@ struct ParamTraits<mozilla::plugins::NPRemoteWindow>
aResult->height = height;
aResult->clipRect = clipRect;
aResult->type = type;
-#if defined(MOZ_X11) && defined(XP_UNIX)
+#if defined(MOZ_X11) && defined(XP_UNIX) && !defined(XP_MACOSX)
aResult->visualID = visualID;
aResult->colormap = colormap;
#endif
-#if defined(XP_WIN)
+#if defined(XP_MACOSX) || defined(XP_WIN)
aResult->contentsScaleFactor = contentsScaleFactor;
#endif
return true;
@@ -410,6 +422,161 @@ struct ParamTraits<mozilla::plugins::NPRemoteWindow>
}
};
+#ifdef XP_MACOSX
+template <>
+struct ParamTraits<NPNSString*>
+{
+ typedef NPNSString* paramType;
+
+ // Empty string writes a length of 0 and no buffer.
+ // We don't write a nullptr terminating character in buffers.
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ CFStringRef cfString = (CFStringRef)aParam;
+
+ // Write true if we have a string, false represents nullptr.
+ aMsg->WriteBool(!!cfString);
+ if (!cfString) {
+ return;
+ }
+
+ long length = ::CFStringGetLength(cfString);
+ WriteParam(aMsg, length);
+ if (length == 0) {
+ return;
+ }
+
+ // Attempt to get characters without any allocation/conversion.
+ if (::CFStringGetCharactersPtr(cfString)) {
+ aMsg->WriteBytes(::CFStringGetCharactersPtr(cfString), length * sizeof(UniChar));
+ } else {
+ UniChar *buffer = (UniChar*)moz_xmalloc(length * sizeof(UniChar));
+ ::CFStringGetCharacters(cfString, ::CFRangeMake(0, length), buffer);
+ aMsg->WriteBytes(buffer, length * sizeof(UniChar));
+ free(buffer);
+ }
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ bool haveString = false;
+ if (!aMsg->ReadBool(aIter, &haveString)) {
+ return false;
+ }
+ if (!haveString) {
+ *aResult = nullptr;
+ return true;
+ }
+
+ long length;
+ if (!ReadParam(aMsg, aIter, &length)) {
+ return false;
+ }
+
+ // Avoid integer multiplication overflow.
+ if (length > INT_MAX / static_cast<long>(sizeof(UniChar))) {
+ return false;
+ }
+
+ auto chars = mozilla::MakeUnique<UniChar[]>(length);
+ if (length != 0) {
+ if (!aMsg->ReadBytesInto(aIter, chars.get(), length * sizeof(UniChar))) {
+ return false;
+ }
+ }
+
+ *aResult = (NPNSString*)::CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*)chars.get(),
+ length * sizeof(UniChar),
+ kCFStringEncodingUTF16, false);
+ if (!*aResult) {
+ return false;
+ }
+
+ return true;
+ }
+};
+#endif
+
+#ifdef XP_MACOSX
+template <>
+struct ParamTraits<NSCursorInfo>
+{
+ typedef NSCursorInfo paramType;
+
+ static void Write(Message* aMsg, const paramType& aParam)
+ {
+ NSCursorInfo::Type type = aParam.GetType();
+
+ aMsg->WriteInt(type);
+
+ nsPoint hotSpot = aParam.GetHotSpot();
+ WriteParam(aMsg, hotSpot.x);
+ WriteParam(aMsg, hotSpot.y);
+
+ uint32_t dataLength = aParam.GetCustomImageDataLength();
+ WriteParam(aMsg, dataLength);
+ if (dataLength == 0) {
+ return;
+ }
+
+ uint8_t* buffer = (uint8_t*)moz_xmalloc(dataLength);
+ memcpy(buffer, aParam.GetCustomImageData(), dataLength);
+ aMsg->WriteBytes(buffer, dataLength);
+ free(buffer);
+ }
+
+ static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+ {
+ NSCursorInfo::Type type;
+ if (!aMsg->ReadInt(aIter, (int*)&type)) {
+ return false;
+ }
+
+ nscoord hotSpotX, hotSpotY;
+ if (!ReadParam(aMsg, aIter, &hotSpotX) ||
+ !ReadParam(aMsg, aIter, &hotSpotY)) {
+ return false;
+ }
+
+ uint32_t dataLength;
+ if (!ReadParam(aMsg, aIter, &dataLength)) {
+ return false;
+ }
+
+ auto data = mozilla::MakeUnique<uint8_t[]>(dataLength);
+ if (dataLength != 0) {
+ if (!aMsg->ReadBytesInto(aIter, data.get(), dataLength)) {
+ return false;
+ }
+ }
+
+ aResult->SetType(type);
+ aResult->SetHotSpot(nsPoint(hotSpotX, hotSpotY));
+ aResult->SetCustomImageData(data.get(), dataLength);
+
+ return true;
+ }
+
+ static void Log(const paramType& aParam, std::wstring* aLog)
+ {
+ const char* typeName = aParam.GetTypeName();
+ nsPoint hotSpot = aParam.GetHotSpot();
+ int hotSpotX, hotSpotY;
+#ifdef NS_COORD_IS_FLOAT
+ hotSpotX = rint(hotSpot.x);
+ hotSpotY = rint(hotSpot.y);
+#else
+ hotSpotX = hotSpot.x;
+ hotSpotY = hotSpot.y;
+#endif
+ uint32_t dataLength = aParam.GetCustomImageDataLength();
+ uint8_t* data = aParam.GetCustomImageData();
+
+ aLog->append(StringPrintf(L"[%s, (%i %i), %u, %p]",
+ typeName, hotSpotX, hotSpotY, dataLength, data));
+ }
+};
+#else
template<>
struct ParamTraits<NSCursorInfo>
{
@@ -422,6 +589,7 @@ struct ParamTraits<NSCursorInfo>
return false;
}
};
+#endif // #ifdef XP_MACOSX
template <>
struct ParamTraits<mozilla::plugins::IPCByteRange>
@@ -564,7 +732,9 @@ struct ParamTraits<mozilla::plugins::NPAudioDeviceChangeDetailsIPC>
//
// NB: these guards are based on those where struct NPEvent is defined
// in npapi.h. They should be kept in sync.
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+# include "mozilla/plugins/NPEventOSX.h"
+#elif defined(XP_WIN)
# include "mozilla/plugins/NPEventWindows.h"
#elif defined(XP_UNIX)
# include "mozilla/plugins/NPEventUnix.h"
diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp
index bd13b8d9a4..1e65345fda 100644
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -132,6 +132,12 @@ PluginModuleChild::PluginModuleChild(bool aIsChrome)
MOZ_ASSERT(!gChromeInstance);
gChromeInstance = this;
}
+
+#ifdef XP_MACOSX
+ if (aIsChrome) {
+ mac_plugin_interposing::child::SetUpCocoaInterposing();
+ }
+#endif
}
PluginModuleChild::~PluginModuleChild()
@@ -254,8 +260,14 @@ PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
AddQuirk(QUIRK_FLASH_EXPOSE_COORD_TRANSLATION);
}
#endif
+#if defined(XP_MACOSX)
+ const char* namePrefix = "Plugin Content";
+ char nameBuffer[80];
+ SprintfLiteral(nameBuffer, "%s (%s)", namePrefix, info.fName);
+ mozilla::plugins::PluginUtilsOSX::SetProcessName(nameBuffer);
+#endif
pluginFile.FreePluginInfo(info);
-#if defined(MOZ_X11)
+#if defined(MOZ_X11) || defined(XP_MACOSX)
if (!mLibrary)
#endif
{
diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp
index f7609bcd5e..6ea205ef0f 100755
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
* 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/. */
@@ -47,6 +48,9 @@
#ifdef MOZ_WIDGET_GTK
#include <glib.h>
+#elif XP_MACOSX
+#include "PluginInterposeOSX.h"
+#include "PluginUtilsOSX.h"
#endif
using base::KillProcess;
@@ -504,7 +508,7 @@ PluginModuleChromeParent::OnProcessLaunched(const bool aSucceeded)
if (NS_SUCCEEDED(mAsyncInitRv))
#endif
{
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
mAsyncInitRv = NP_Initialize(mNPNIface,
mNPPIface,
&mAsyncInitError);
@@ -513,6 +517,13 @@ PluginModuleChromeParent::OnProcessLaunched(const bool aSucceeded)
&mAsyncInitError);
#endif
}
+
+#if defined(XP_MACOSX)
+ if (NS_SUCCEEDED(mAsyncInitRv)) {
+ mAsyncInitRv = NP_GetEntryPoints(mNPPIface,
+ &mAsyncInitError);
+ }
+#endif
}
}
@@ -1657,8 +1668,13 @@ PluginModuleParent::GetSettings(PluginSettings* aSettings)
aSettings->supportsWindowless() = GetSetting(NPNVSupportsWindowless);
aSettings->userAgent() = NullableString(mNPNIface->uagent(nullptr));
+#if defined(XP_MACOSX)
+ aSettings->nativeCursorsSupported() =
+ Preferences::GetBool("dom.ipc.plugins.nativeCursorSupport", false);
+#else
// Need to initialize this to satisfy IPDL.
aSettings->nativeCursorsSupported() = false;
+#endif
}
void
@@ -1676,7 +1692,7 @@ PluginModuleChromeParent::CachedSettingChanged(const char* aPref, void* aModule)
module->CachedSettingChanged();
}
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
nsresult
PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error)
{
@@ -1814,7 +1830,7 @@ PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error)
return NS_OK;
}
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
nsresult
PluginModuleContentParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error)
@@ -1837,10 +1853,20 @@ PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error)
if (NS_FAILED(rv))
return rv;
+#if defined(XP_MACOSX)
+ if (!mSubprocess->IsConnected()) {
+ // The subprocess isn't connected yet. Defer NP_Initialize until
+ // OnProcessLaunched is invoked.
+ mInitOnAsyncConnect = true;
+ *error = NPERR_NO_ERROR;
+ return NS_OK;
+ }
+#else
if (mInitOnAsyncConnect) {
*error = NPERR_NO_ERROR;
return NS_OK;
}
+#endif
PluginSettings settings;
GetSettings(&settings);
@@ -2032,7 +2058,7 @@ PluginModuleParent::NP_GetValue(void *future, NPPVariable aVariable,
return NS_OK;
}
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
nsresult
PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error)
{
@@ -2041,7 +2067,16 @@ PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error)
*error = NPERR_NO_ERROR;
if (mIsStartingAsync && !IsChrome()) {
mNPPIface = pFuncs;
+#if defined(XP_MACOSX)
+ if (mNPInitialized) {
+ SetPluginFuncs(pFuncs);
+ InitAsyncSurrogates();
+ } else {
+ PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs);
+ }
+#else
PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs);
+#endif
} else {
SetPluginFuncs(pFuncs);
}
@@ -2052,6 +2087,14 @@ PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error)
nsresult
PluginModuleChromeParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error)
{
+#if defined(XP_MACOSX)
+ if (mInitOnAsyncConnect) {
+ PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs);
+ mNPPIface = pFuncs;
+ *error = NPERR_NO_ERROR;
+ return NS_OK;
+ }
+#else
if (mIsStartingAsync) {
PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs);
}
@@ -2061,6 +2104,7 @@ PluginModuleChromeParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* erro
*error = NPERR_NO_ERROR;
return NS_OK;
}
+#endif
// We need to have the plugin process update its function table here by
// actually calling NP_GetEntryPoints. The parent's function table will
@@ -2281,7 +2325,18 @@ PluginModuleParent::NPP_GetSitesWithData(nsCOMPtr<nsIGetSitesWithDataCallback> c
return NS_OK;
}
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+nsresult
+PluginModuleParent::IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing)
+{
+ PluginInstanceParent* i = PluginInstanceParent::Cast(instance);
+ if (!i)
+ return NS_ERROR_FAILURE;
+
+ return i->IsRemoteDrawingCoreAnimation(aDrawing);
+}
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsresult
PluginModuleParent::ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor)
{
@@ -2291,9 +2346,17 @@ PluginModuleParent::ContentsScaleFactorChanged(NPP instance, double aContentsSca
return i->ContentsScaleFactorChanged(aContentsScaleFactor);
}
-#endif // #if defined(XP_WIN)
+#endif // #if defined(XP_MACOSX)
+
+#if defined(XP_MACOSX)
+bool
+PluginModuleParent::AnswerProcessSomeEvents()
+{
+ mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop();
+ return true;
+}
-#if !defined(MOZ_WIDGET_GTK)
+#elif !defined(MOZ_WIDGET_GTK)
bool
PluginModuleParent::AnswerProcessSomeEvents()
{
@@ -2351,54 +2414,85 @@ PluginModuleParent::RecvPluginShowWindow(const uint32_t& aWindowId, const bool&
const size_t& aWidth, const size_t& aHeight)
{
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+#if defined(XP_MACOSX)
+ CGRect windowBound = ::CGRectMake(aX, aY, aWidth, aHeight);
+ mac_plugin_interposing::parent::OnPluginShowWindow(aWindowId, windowBound, aModal);
+ return true;
+#else
NS_NOTREACHED(
"PluginInstanceParent::RecvPluginShowWindow not implemented!");
return false;
+#endif
}
bool
PluginModuleParent::RecvPluginHideWindow(const uint32_t& aWindowId)
{
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+#if defined(XP_MACOSX)
+ mac_plugin_interposing::parent::OnPluginHideWindow(aWindowId, OtherPid());
+ return true;
+#else
NS_NOTREACHED(
"PluginInstanceParent::RecvPluginHideWindow not implemented!");
return false;
+#endif
}
bool
PluginModuleParent::RecvSetCursor(const NSCursorInfo& aCursorInfo)
{
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+#if defined(XP_MACOSX)
+ mac_plugin_interposing::parent::OnSetCursor(aCursorInfo);
+ return true;
+#else
NS_NOTREACHED(
"PluginInstanceParent::RecvSetCursor not implemented!");
return false;
+#endif
}
bool
PluginModuleParent::RecvShowCursor(const bool& aShow)
{
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+#if defined(XP_MACOSX)
+ mac_plugin_interposing::parent::OnShowCursor(aShow);
+ return true;
+#else
NS_NOTREACHED(
"PluginInstanceParent::RecvShowCursor not implemented!");
return false;
+#endif
}
bool
PluginModuleParent::RecvPushCursor(const NSCursorInfo& aCursorInfo)
{
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+#if defined(XP_MACOSX)
+ mac_plugin_interposing::parent::OnPushCursor(aCursorInfo);
+ return true;
+#else
NS_NOTREACHED(
"PluginInstanceParent::RecvPushCursor not implemented!");
return false;
+#endif
}
bool
PluginModuleParent::RecvPopCursor()
{
PLUGIN_LOG_DEBUG(("%s", FULLFUNCTION));
+#if defined(XP_MACOSX)
+ mac_plugin_interposing::parent::OnPopCursor();
+ return true;
+#else
NS_NOTREACHED(
"PluginInstanceParent::RecvPopCursor not implemented!");
return false;
+#endif
}
bool
diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h
index 11c2a47c65..909e8fe351 100644
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -247,7 +247,7 @@ protected:
const mozilla::NativeEventData& aNativeKeyData,
bool aIsConsumed) override;
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) override;
#else
virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override;
@@ -257,7 +257,7 @@ protected:
virtual nsresult NP_GetMIMEDescription(const char** mimeDesc) override;
virtual nsresult NP_GetValue(void *future, NPPVariable aVariable,
void *aValue, NPError* error) override;
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) override;
#endif
virtual nsresult NPP_New(NPMIMEType pluginType, NPP instance,
@@ -278,7 +278,10 @@ private:
public:
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+ virtual nsresult IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) override;
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) override;
#endif
@@ -352,7 +355,7 @@ class PluginModuleContentParent : public PluginModuleParent
virtual ~PluginModuleContentParent();
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override;
#endif
@@ -476,13 +479,13 @@ private:
PluginProcessParent* Process() const { return mSubprocess; }
base::ProcessHandle ChildProcessHandle() { return mSubprocess->GetChildProcessHandle(); }
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) override;
#else
virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override;
#endif
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) override;
#endif
diff --git a/dom/plugins/ipc/PluginProcessChild.cpp b/dom/plugins/ipc/PluginProcessChild.cpp
index 9ba0926636..32bf062150 100644
--- a/dom/plugins/ipc/PluginProcessChild.cpp
+++ b/dom/plugins/ipc/PluginProcessChild.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
* 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/. */
@@ -12,6 +13,13 @@
#include "base/string_util.h"
#include "nsDebugImpl.h"
+#if defined(XP_MACOSX)
+#include "nsCocoaFeatures.h"
+// An undocumented CoreGraphics framework method, present in the same form
+// since at least OS X 10.5.
+extern "C" CGError CGSSetDebugOptions(int options);
+#endif
+
#ifdef XP_WIN
bool ShouldProtectPluginCurrentDirectory(char16ptr_t pluginFilePath);
#endif
@@ -32,6 +40,42 @@ PluginProcessChild::Init()
{
nsDebugImpl::SetMultiprocessMode("NPAPI");
+#if defined(XP_MACOSX)
+ // Remove the trigger for "dyld interposing" that we added in
+ // GeckoChildProcessHost::PerformAsyncLaunchInternal(), in the host
+ // process just before we were launched. Dyld interposing will still
+ // happen in our process (the plugin child process). But we don't want
+ // it to happen in any processes that the plugin might launch from our
+ // process.
+ nsCString interpose(PR_GetEnv("DYLD_INSERT_LIBRARIES"));
+ if (!interpose.IsEmpty()) {
+ // If we added the path to libplugin_child_interpose.dylib to an
+ // existing DYLD_INSERT_LIBRARIES, we appended it to the end, after a
+ // ":" path seperator.
+ int32_t lastSeparatorPos = interpose.RFind(":");
+ int32_t lastTriggerPos = interpose.RFind("libplugin_child_interpose.dylib");
+ bool needsReset = false;
+ if (lastTriggerPos != -1) {
+ if (lastSeparatorPos == -1) {
+ interpose.Truncate();
+ needsReset = true;
+ } else if (lastTriggerPos > lastSeparatorPos) {
+ interpose.SetLength(lastSeparatorPos);
+ needsReset = true;
+ }
+ }
+ if (needsReset) {
+ nsCString setInterpose("DYLD_INSERT_LIBRARIES=");
+ if (!interpose.IsEmpty()) {
+ setInterpose.Append(interpose);
+ }
+ // Values passed to PR_SetEnv() must be seperately allocated.
+ char* setInterposePtr = strdup(setInterpose.get());
+ PR_SetEnv(setInterposePtr);
+ }
+ }
+#endif
+
// Certain plugins, such as flash, steal the unhandled exception filter
// thus we never get crash reports when they fault. This call fixes it.
message_loop()->set_exception_restoration(true);
@@ -71,6 +115,17 @@ PluginProcessChild::Init()
bool retval = mPlugin.InitForChrome(pluginFilename, ParentPid(),
IOThreadChild::message_loop(),
IOThreadChild::channel());
+#if defined(XP_MACOSX)
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ // Explicitly turn off CGEvent logging. This works around bug 1092855.
+ // If there are already CGEvents in the log, turning off logging also
+ // causes those events to be written to disk. But at this point no
+ // CGEvents have yet been processed. CGEvents are events (usually
+ // input events) pulled from the WindowServer. An option of 0x80000008
+ // turns on CGEvent logging.
+ CGSSetDebugOptions(0x80000007);
+ }
+#endif
return retval;
}
diff --git a/dom/plugins/ipc/PluginProcessParent.cpp b/dom/plugins/ipc/PluginProcessParent.cpp
index 4445681359..53cd78ff3b 100644
--- a/dom/plugins/ipc/PluginProcessParent.cpp
+++ b/dom/plugins/ipc/PluginProcessParent.cpp
@@ -47,6 +47,13 @@ PluginProcessParent::Launch(mozilla::UniquePtr<LaunchCompleteTask> aLaunchComple
uint32_t containerArchitectures = GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin);
uint32_t pluginLibArchitectures = currentArchitecture;
+#ifdef XP_MACOSX
+ nsresult rv = GetArchitecturesForBinary(mPluginFilePath.c_str(), &pluginLibArchitectures);
+ if (NS_FAILED(rv)) {
+ // If the call failed just assume that we want the current architecture.
+ pluginLibArchitectures = currentArchitecture;
+ }
+#endif
ProcessArchitecture selectedArchitecture = currentArchitecture;
if (!(pluginLibArchitectures & containerArchitectures & currentArchitecture)) {
diff --git a/dom/plugins/ipc/PluginQuirks.cpp b/dom/plugins/ipc/PluginQuirks.cpp
index a8b0f62056..c60cf88362 100644
--- a/dom/plugins/ipc/PluginQuirks.cpp
+++ b/dom/plugins/ipc/PluginQuirks.cpp
@@ -49,6 +49,16 @@ int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
}
#endif
+#ifdef XP_MACOSX
+ // Whitelist Flash and Quicktime to support offline renderer
+ NS_NAMED_LITERAL_CSTRING(quicktime, "QuickTime Plugin.plugin");
+ if (specialType == nsPluginHost::eSpecialType_Flash) {
+ quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
+ } else if (FindInReadable(quicktime, aPluginFilename)) {
+ quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
+ }
+#endif
+
#ifdef OS_WIN
if (specialType == nsPluginHost::eSpecialType_Unity) {
quirks |= QUIRK_UNITY_FIXUP_MOUSE_CAPTURE;
diff --git a/dom/plugins/ipc/PluginUtilsOSX.h b/dom/plugins/ipc/PluginUtilsOSX.h
new file mode 100644
index 0000000000..c201782c0f
--- /dev/null
+++ b/dom/plugins/ipc/PluginUtilsOSX.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* 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/. */
+
+#ifndef dom_plugins_PluginUtilsOSX_h
+#define dom_plugins_PluginUtilsOSX_h 1
+
+#include "npapi.h"
+#include "mozilla/gfx/QuartzSupport.h"
+#include "nsRect.h"
+
+namespace mozilla {
+namespace plugins {
+namespace PluginUtilsOSX {
+
+// Need to call back into the browser's message loop to process event.
+typedef void (*RemoteProcessEvents) (void*);
+
+NPError ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent);
+
+void InvokeNativeEventLoop();
+
+// Need to call back and send a cocoa draw event to the plugin.
+typedef void (*DrawPluginFunc) (CGContextRef, void*, nsIntRect aUpdateRect);
+
+void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, double aContentsScaleFactor);
+void ReleaseCGLayer(void* cgLayer);
+void Repaint(void* cgLayer, nsIntRect aRect);
+
+bool SetProcessName(const char* aProcessName);
+
+/*
+ * Provides a wrapper around nsCARenderer to manage double buffering
+ * without having to unbind nsCARenderer on every surface swaps.
+ *
+ * The double buffer renderer begins with no initialize surfaces.
+ * The buffers can be initialized and cleared individually.
+ * Swapping still occurs regardless if the buffers are initialized.
+ */
+class nsDoubleBufferCARenderer {
+public:
+ nsDoubleBufferCARenderer() : mCALayer(nullptr), mContentsScaleFactor(1.0) {}
+ // Returns width in "display pixels". A "display pixel" is the smallest
+ // fully addressable part of a display. But in HiDPI modes each "display
+ // pixel" corresponds to more than one device pixel. Multiply display pixels
+ // by mContentsScaleFactor to get device pixels.
+ size_t GetFrontSurfaceWidth();
+ // Returns height in "display pixels". Multiply by
+ // mContentsScaleFactor to get device pixels.
+ size_t GetFrontSurfaceHeight();
+ double GetFrontSurfaceContentsScaleFactor();
+ // Returns width in "display pixels". Multiply by
+ // mContentsScaleFactor to get device pixels.
+ size_t GetBackSurfaceWidth();
+ // Returns height in "display pixels". Multiply by
+ // mContentsScaleFactor to get device pixels.
+ size_t GetBackSurfaceHeight();
+ double GetBackSurfaceContentsScaleFactor();
+ IOSurfaceID GetFrontSurfaceID();
+
+ bool HasBackSurface();
+ bool HasFrontSurface();
+ bool HasCALayer();
+
+ void SetCALayer(void *aCALayer);
+ // aWidth and aHeight are in "display pixels". Multiply by
+ // aContentsScaleFactor to get device pixels.
+ bool InitFrontSurface(size_t aWidth, size_t aHeight,
+ double aContentsScaleFactor,
+ AllowOfflineRendererEnum aAllowOfflineRenderer);
+ void Render();
+ void SwapSurfaces();
+ void ClearFrontSurface();
+ void ClearBackSurface();
+
+ double GetContentsScaleFactor() { return mContentsScaleFactor; }
+
+private:
+ void *mCALayer;
+ RefPtr<nsCARenderer> mCARenderer;
+ RefPtr<MacIOSurface> mFrontSurface;
+ RefPtr<MacIOSurface> mBackSurface;
+ double mContentsScaleFactor;
+};
+
+} // namespace PluginUtilsOSX
+} // namespace plugins
+} // namespace mozilla
+
+#endif //dom_plugins_PluginUtilsOSX_h
diff --git a/dom/plugins/ipc/PluginUtilsOSX.mm b/dom/plugins/ipc/PluginUtilsOSX.mm
new file mode 100644
index 0000000000..2a920f7f61
--- /dev/null
+++ b/dom/plugins/ipc/PluginUtilsOSX.mm
@@ -0,0 +1,462 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* 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 <dlfcn.h>
+#import <AppKit/AppKit.h>
+#import <QuartzCore/QuartzCore.h>
+#include "PluginUtilsOSX.h"
+
+// Remove definitions for try/catch interfering with ObjCException macros.
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+
+#include "nsDebug.h"
+
+#include "mozilla/Sprintf.h"
+
+@interface CALayer (ContentsScale)
+- (double)contentsScale;
+- (void)setContentsScale:(double)scale;
+@end
+
+using namespace mozilla::plugins::PluginUtilsOSX;
+
+@interface CGBridgeLayer : CALayer {
+ DrawPluginFunc mDrawFunc;
+ void* mPluginInstance;
+ nsIntRect mUpdateRect;
+}
+- (void)setDrawFunc:(DrawPluginFunc)aFunc
+ pluginInstance:(void*)aPluginInstance;
+- (void)updateRect:(nsIntRect)aRect;
+
+@end
+
+// CGBitmapContextSetData() is an undocumented function present (with
+// the same signature) since at least OS X 10.5. As the name suggests,
+// it's used to replace the "data" in a bitmap context that was
+// originally specified in a call to CGBitmapContextCreate() or
+// CGBitmapContextCreateWithData().
+typedef void (*CGBitmapContextSetDataFunc) (CGContextRef c,
+ size_t x,
+ size_t y,
+ size_t width,
+ size_t height,
+ void* data,
+ size_t bitsPerComponent,
+ size_t bitsPerPixel,
+ size_t bytesPerRow);
+CGBitmapContextSetDataFunc CGBitmapContextSetDataPtr = NULL;
+
+@implementation CGBridgeLayer
+- (void) updateRect:(nsIntRect)aRect
+{
+ mUpdateRect.UnionRect(mUpdateRect, aRect);
+}
+
+- (void) setDrawFunc:(DrawPluginFunc)aFunc
+ pluginInstance:(void*)aPluginInstance
+{
+ mDrawFunc = aFunc;
+ mPluginInstance = aPluginInstance;
+}
+
+- (void)drawInContext:(CGContextRef)aCGContext
+{
+ ::CGContextSaveGState(aCGContext);
+ ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height);
+ ::CGContextScaleCTM(aCGContext, (CGFloat) 1, (CGFloat) -1);
+
+ mUpdateRect = nsIntRect::Truncate(0, 0, self.bounds.size.width, self.bounds.size.height);
+
+ mDrawFunc(aCGContext, mPluginInstance, mUpdateRect);
+
+ ::CGContextRestoreGState(aCGContext);
+
+ mUpdateRect.SetEmpty();
+}
+
+@end
+
+void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc,
+ void* aPluginInstance,
+ double aContentsScaleFactor) {
+ CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init];
+
+ // We need to make bridgeLayer behave properly when its superlayer changes
+ // size (in nsCARenderer::SetBounds()).
+ bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
+ bridgeLayer.needsDisplayOnBoundsChange = YES;
+ NSNull *nullValue = [NSNull null];
+ NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys:
+ nullValue, @"bounds",
+ nullValue, @"contents",
+ nullValue, @"contentsRect",
+ nullValue, @"position",
+ nil];
+ [bridgeLayer setStyle:[NSDictionary dictionaryWithObject:actions forKey:@"actions"]];
+
+ // For reasons that aren't clear (perhaps one or more OS bugs), we can only
+ // use full HiDPI resolution here if the tree is built with the 10.7 SDK or
+ // up. If we build with the 10.6 SDK, changing the contentsScale property
+ // of bridgeLayer (even to the same value) causes it to stop working (go
+ // blank). This doesn't happen with objects that are members of the CALayer
+ // class (as opposed to one of its subclasses).
+#if defined(MAC_OS_X_VERSION_10_7) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+ if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) {
+ bridgeLayer.contentsScale = aContentsScaleFactor;
+ }
+#endif
+
+ [bridgeLayer setDrawFunc:aFunc
+ pluginInstance:aPluginInstance];
+ return bridgeLayer;
+}
+
+void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) {
+ CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer;
+ [bridgeLayer release];
+}
+
+void mozilla::plugins::PluginUtilsOSX::Repaint(void *caLayer, nsIntRect aRect) {
+ CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)caLayer;
+ [CATransaction begin];
+ [bridgeLayer updateRect:aRect];
+ [bridgeLayer setNeedsDisplay];
+ [bridgeLayer displayIfNeeded];
+ [CATransaction commit];
+}
+
+@interface EventProcessor : NSObject {
+ RemoteProcessEvents aRemoteEvents;
+ void *aPluginModule;
+}
+- (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule;
+- (void)onTick;
+@end
+
+@implementation EventProcessor
+- (void) onTick
+{
+ aRemoteEvents(aPluginModule);
+}
+
+- (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule
+{
+ aRemoteEvents = remoteEvents;
+ aPluginModule = pluginModule;
+}
+@end
+
+#define EVENT_PROCESS_DELAY 0.05 // 50 ms
+
+NPError mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Set the native cursor to the OS default (an arrow) before displaying the
+ // context menu. Otherwise (if the plugin has changed the cursor) it may
+ // stay as the plugin has set it -- which means it may be invisible. We
+ // need to do this because we display the context menu without making the
+ // plugin process the foreground process. If we did, the cursor would
+ // change to an arrow cursor automatically -- as it does in Chrome.
+ [[NSCursor arrowCursor] set];
+
+ EventProcessor* eventProcessor = nullptr;
+ NSTimer *eventTimer = nullptr;
+ if (pluginModule) {
+ // Create a timer to process browser events while waiting
+ // on the menu. This prevents the browser from hanging
+ // during the lifetime of the menu.
+ eventProcessor = [[EventProcessor alloc] init];
+ [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule];
+ eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY
+ target:eventProcessor selector:@selector(onTick)
+ userInfo:nil repeats:TRUE];
+ // Use NSEventTrackingRunLoopMode otherwise the timer will
+ // not fire during the right click menu.
+ [[NSRunLoop currentRunLoop] addTimer:eventTimer
+ forMode:NSEventTrackingRunLoopMode];
+ }
+
+ NSMenu* nsmenu = reinterpret_cast<NSMenu*>(aMenu);
+ NSPoint screen_point = ::NSMakePoint(aX, aY);
+
+ [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil];
+
+ if (pluginModule) {
+ [eventTimer invalidate];
+ [eventProcessor release];
+ }
+
+ return NPERR_NO_ERROR;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR);
+}
+
+void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ ::CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true);
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+
+#define UNDOCUMENTED_SESSION_CONSTANT ((int)-2)
+namespace mozilla {
+namespace plugins {
+namespace PluginUtilsOSX {
+ static void *sApplicationASN = NULL;
+ static void *sApplicationInfoItem = NULL;
+} // namespace PluginUtilsOSX
+} // namespace plugins
+} // namespace mozilla
+
+bool mozilla::plugins::PluginUtilsOSX::SetProcessName(const char* aProcessName) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ nsAutoreleasePool localPool;
+
+ if (!aProcessName || strcmp(aProcessName, "") == 0) {
+ return false;
+ }
+
+ NSString *currentName = [[[NSBundle mainBundle] localizedInfoDictionary]
+ objectForKey:(NSString *)kCFBundleNameKey];
+
+ char formattedName[1024];
+ SprintfLiteral(formattedName, "%s %s", [currentName UTF8String], aProcessName);
+
+ aProcessName = formattedName;
+
+ // This function is based on Chrome/Webkit's and relies on potentially dangerous SPI.
+ typedef CFTypeRef (*LSGetASNType)();
+ typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef,
+ CFStringRef,
+ CFStringRef,
+ CFDictionaryRef*);
+
+ CFBundleRef launchServices = ::CFBundleGetBundleWithIdentifier(
+ CFSTR("com.apple.LaunchServices"));
+ if (!launchServices) {
+ NS_WARNING("Failed to set process name: Could not open LaunchServices bundle");
+ return false;
+ }
+
+ if (!sApplicationASN) {
+ sApplicationASN = ::CFBundleGetFunctionPointerForName(launchServices,
+ CFSTR("_LSGetCurrentApplicationASN"));
+ }
+
+ LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType>
+ (sApplicationASN);
+
+ if (!sApplicationInfoItem) {
+ sApplicationInfoItem = ::CFBundleGetFunctionPointerForName(launchServices,
+ CFSTR("_LSSetApplicationInformationItem"));
+ }
+
+ LSSetInformationItemType setInformationItemFunc
+ = reinterpret_cast<LSSetInformationItemType>
+ (sApplicationInfoItem);
+
+ void * displayNameKeyAddr = ::CFBundleGetDataPointerForName(launchServices,
+ CFSTR("_kLSDisplayNameKey"));
+
+ CFStringRef displayNameKey = nil;
+ if (displayNameKeyAddr) {
+ displayNameKey = reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr);
+ }
+
+ // Rename will fail without this
+ ProcessSerialNumber psn;
+ if (::GetCurrentProcess(&psn) != noErr) {
+ return false;
+ }
+
+ CFTypeRef currentAsn = getASNFunc();
+
+ if (!getASNFunc || !setInformationItemFunc ||
+ !displayNameKey || !currentAsn) {
+ NS_WARNING("Failed to set process name: Accessing launchServices failed");
+ return false;
+ }
+
+ CFStringRef processName = ::CFStringCreateWithCString(nil,
+ aProcessName,
+ kCFStringEncodingASCII);
+ if (!processName) {
+ NS_WARNING("Failed to set process name: Could not create CFStringRef");
+ return false;
+ }
+
+ OSErr err = setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn,
+ displayNameKey, processName,
+ nil); // Optional out param
+ ::CFRelease(processName);
+ if (err != noErr) {
+ NS_WARNING("Failed to set process name: LSSetInformationItemType err");
+ return false;
+ }
+
+ return true;
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+namespace mozilla {
+namespace plugins {
+namespace PluginUtilsOSX {
+
+size_t nsDoubleBufferCARenderer::GetFrontSurfaceWidth() {
+ if (!HasFrontSurface()) {
+ return 0;
+ }
+
+ return mFrontSurface->GetWidth();
+}
+
+size_t nsDoubleBufferCARenderer::GetFrontSurfaceHeight() {
+ if (!HasFrontSurface()) {
+ return 0;
+ }
+
+ return mFrontSurface->GetHeight();
+}
+
+double nsDoubleBufferCARenderer::GetFrontSurfaceContentsScaleFactor() {
+ if (!HasFrontSurface()) {
+ return 1.0;
+ }
+
+ return mFrontSurface->GetContentsScaleFactor();
+}
+
+size_t nsDoubleBufferCARenderer::GetBackSurfaceWidth() {
+ if (!HasBackSurface()) {
+ return 0;
+ }
+
+ return mBackSurface->GetWidth();
+}
+
+size_t nsDoubleBufferCARenderer::GetBackSurfaceHeight() {
+ if (!HasBackSurface()) {
+ return 0;
+ }
+
+ return mBackSurface->GetHeight();
+}
+
+double nsDoubleBufferCARenderer::GetBackSurfaceContentsScaleFactor() {
+ if (!HasBackSurface()) {
+ return 1.0;
+ }
+
+ return mBackSurface->GetContentsScaleFactor();
+}
+
+IOSurfaceID nsDoubleBufferCARenderer::GetFrontSurfaceID() {
+ if (!HasFrontSurface()) {
+ return 0;
+ }
+
+ return mFrontSurface->GetIOSurfaceID();
+}
+
+bool nsDoubleBufferCARenderer::HasBackSurface() {
+ return !!mBackSurface;
+}
+
+bool nsDoubleBufferCARenderer::HasFrontSurface() {
+ return !!mFrontSurface;
+}
+
+bool nsDoubleBufferCARenderer::HasCALayer() {
+ return !!mCALayer;
+}
+
+void nsDoubleBufferCARenderer::SetCALayer(void *aCALayer) {
+ mCALayer = aCALayer;
+}
+
+bool nsDoubleBufferCARenderer::InitFrontSurface(size_t aWidth, size_t aHeight,
+ double aContentsScaleFactor,
+ AllowOfflineRendererEnum aAllowOfflineRenderer) {
+ if (!mCALayer) {
+ return false;
+ }
+
+ mContentsScaleFactor = aContentsScaleFactor;
+ mFrontSurface = MacIOSurface::CreateIOSurface(aWidth, aHeight, mContentsScaleFactor);
+ if (!mFrontSurface) {
+ mCARenderer = nullptr;
+ return false;
+ }
+
+ if (!mCARenderer) {
+ mCARenderer = new nsCARenderer();
+ if (!mCARenderer) {
+ mFrontSurface = nullptr;
+ return false;
+ }
+
+ mCARenderer->AttachIOSurface(mFrontSurface);
+
+ nsresult result = mCARenderer->SetupRenderer(mCALayer,
+ mFrontSurface->GetWidth(),
+ mFrontSurface->GetHeight(),
+ mContentsScaleFactor,
+ aAllowOfflineRenderer);
+
+ if (result != NS_OK) {
+ mCARenderer = nullptr;
+ mFrontSurface = nullptr;
+ return false;
+ }
+ } else {
+ mCARenderer->AttachIOSurface(mFrontSurface);
+ }
+
+ return true;
+}
+
+void nsDoubleBufferCARenderer::Render() {
+ if (!HasFrontSurface() || !mCARenderer) {
+ return;
+ }
+
+ mCARenderer->Render(GetFrontSurfaceWidth(), GetFrontSurfaceHeight(),
+ mContentsScaleFactor, nullptr);
+}
+
+void nsDoubleBufferCARenderer::SwapSurfaces() {
+ RefPtr<MacIOSurface> prevFrontSurface = mFrontSurface;
+ mFrontSurface = mBackSurface;
+ mBackSurface = prevFrontSurface;
+
+ if (mFrontSurface) {
+ mCARenderer->AttachIOSurface(mFrontSurface);
+ }
+}
+
+void nsDoubleBufferCARenderer::ClearFrontSurface() {
+ mFrontSurface = nullptr;
+ if (!mFrontSurface && !mBackSurface) {
+ mCARenderer = nullptr;
+ }
+}
+
+void nsDoubleBufferCARenderer::ClearBackSurface() {
+ mBackSurface = nullptr;
+ if (!mFrontSurface && !mBackSurface) {
+ mCARenderer = nullptr;
+ }
+}
+
+} // namespace PluginUtilsOSX
+} // namespace plugins
+} // namespace mozilla
+
diff --git a/dom/plugins/ipc/interpose/moz.build b/dom/plugins/ipc/interpose/moz.build
new file mode 100644
index 0000000000..319c325a70
--- /dev/null
+++ b/dom/plugins/ipc/interpose/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SharedLibrary('plugin_child_interpose')
+
+SOURCES += [ "%s.mm" % (LIBRARY_NAME) ]
+
+OS_LIBS += ['-framework Carbon']
+
+DIST_INSTALL = True
diff --git a/dom/plugins/ipc/interpose/plugin_child_interpose.mm b/dom/plugins/ipc/interpose/plugin_child_interpose.mm
new file mode 100644
index 0000000000..58c39b0a1c
--- /dev/null
+++ b/dom/plugins/ipc/interpose/plugin_child_interpose.mm
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+// Use "dyld interposing" to hook methods imported from other libraries in the
+// plugin child process. The basic technique is described at
+// http://books.google.com/books?id=K8vUkpOXhN4C&pg=PA73&lpg=PA73&dq=__interpose&source=bl&ots=OJnnXZYpZC&sig=o7I3lXvoduUi13SrPfOON7o3do4&hl=en&ei=AoehS9brCYGQNrvsmeUM&sa=X&oi=book_result&ct=result&resnum=6&ved=0CBsQ6AEwBQ#v=onepage&q=__interpose&f=false.
+// The idea of doing it for the plugin child process comes from Chromium code,
+// particularly from plugin_carbon_interpose_mac.cc
+// (http://codesearch.google.com/codesearch/p?hl=en#OAMlx_jo-ck/src/chrome/browser/plugin_carbon_interpose_mac.cc&q=nscursor&exact_package=chromium&d=1&l=168)
+// and from PluginProcessHost::Init() in plugin_process_host.cc
+// (http://codesearch.google.com/codesearch/p?hl=en#OAMlx_jo-ck/src/content/browser/plugin_process_host.cc&q=nscursor&exact_package=chromium&d=1&l=222).
+
+// These hooks are needed to make certain OS calls work from the child process
+// (a background process) that would normally only work when called in the
+// parent process (the foreground process). They allow us to serialize
+// information from the child process to the parent process, so that the same
+// (or equivalent) calls can be made from the parent process.
+
+// This file lives in a seperate module (libplugin_child_interpose.dylib),
+// which will get loaded by the OS before any other modules when the plugin
+// child process is launched (from GeckoChildProcessHost::
+// PerformAsyncLaunchInternal()). For this reason it shouldn't link in other
+// browser modules when loaded. Instead it should use dlsym() to load
+// pointers to the methods it wants to call in other modules.
+
+#if !defined(__LP64__)
+
+#include <dlfcn.h>
+#import <Carbon/Carbon.h>
+
+// The header file QuickdrawAPI.h is missing on OS X 10.7 and up (though the
+// QuickDraw APIs defined in it are still present) -- so we need to supply the
+// relevant parts of its contents here. It's likely that Apple will eventually
+// remove the APIs themselves (probably in OS X 10.8), so we need to make them
+// weak imports, and test for their presence before using them.
+#if !defined(__QUICKDRAWAPI__)
+
+struct Cursor;
+extern "C" void SetCursor(const Cursor * crsr) __attribute__((weak_import));
+
+#endif /* __QUICKDRAWAPI__ */
+
+BOOL (*OnSetThemeCursorPtr) (ThemeCursor) = NULL;
+BOOL (*OnSetCursorPtr) (const Cursor*) = NULL;
+BOOL (*OnHideCursorPtr) () = NULL;
+BOOL (*OnShowCursorPtr) () = NULL;
+
+static BOOL loadXULPtrs()
+{
+ if (!OnSetThemeCursorPtr) {
+ // mac_plugin_interposing_child_OnSetThemeCursor(ThemeCursor cursor) is in
+ // PluginInterposeOSX.mm
+ OnSetThemeCursorPtr = (BOOL(*)(ThemeCursor))
+ dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetThemeCursor");
+ }
+ if (!OnSetCursorPtr) {
+ // mac_plugin_interposing_child_OnSetCursor(const Cursor* cursor) is in
+ // PluginInterposeOSX.mm
+ OnSetCursorPtr = (BOOL(*)(const Cursor*))
+ dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnSetCursor");
+ }
+ if (!OnHideCursorPtr) {
+ // mac_plugin_interposing_child_OnHideCursor() is in PluginInterposeOSX.mm
+ OnHideCursorPtr = (BOOL(*)())
+ dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnHideCursor");
+ }
+ if (!OnShowCursorPtr) {
+ // mac_plugin_interposing_child_OnShowCursor() is in PluginInterposeOSX.mm
+ OnShowCursorPtr = (BOOL(*)())
+ dlsym(RTLD_DEFAULT, "mac_plugin_interposing_child_OnShowCursor");
+ }
+ return (OnSetCursorPtr && OnSetThemeCursorPtr && OnHideCursorPtr && OnShowCursorPtr);
+}
+
+static OSStatus MacPluginChildSetThemeCursor(ThemeCursor cursor)
+{
+ if (loadXULPtrs()) {
+ OnSetThemeCursorPtr(cursor);
+ }
+ return ::SetThemeCursor(cursor);
+}
+
+static void MacPluginChildSetCursor(const Cursor* cursor)
+{
+ if (::SetCursor) {
+ if (loadXULPtrs()) {
+ OnSetCursorPtr(cursor);
+ }
+ ::SetCursor(cursor);
+ }
+}
+
+static CGError MacPluginChildCGDisplayHideCursor(CGDirectDisplayID display)
+{
+ if (loadXULPtrs()) {
+ OnHideCursorPtr();
+ }
+ return ::CGDisplayHideCursor(display);
+}
+
+static CGError MacPluginChildCGDisplayShowCursor(CGDirectDisplayID display)
+{
+ if (loadXULPtrs()) {
+ OnShowCursorPtr();
+ }
+ return ::CGDisplayShowCursor(display);
+}
+
+#pragma mark -
+
+struct interpose_substitution {
+ const void* replacement;
+ const void* original;
+};
+
+#define INTERPOSE_FUNCTION(function) \
+ { reinterpret_cast<const void*>(MacPluginChild##function), \
+ reinterpret_cast<const void*>(function) }
+
+__attribute__((used)) static const interpose_substitution substitutions[]
+ __attribute__((section("__DATA, __interpose"))) = {
+ INTERPOSE_FUNCTION(SetThemeCursor),
+ INTERPOSE_FUNCTION(CGDisplayHideCursor),
+ INTERPOSE_FUNCTION(CGDisplayShowCursor),
+ // SetCursor() and other QuickDraw APIs will probably be removed in OS X
+ // 10.8. But this will make 'SetCursor' NULL, which will just stop the OS
+ // from interposing it (tested using an INTERPOSE_FUNCTION_BROKEN macro
+ // that just sets the second address of each tuple to NULL).
+ INTERPOSE_FUNCTION(SetCursor),
+};
+
+#endif // !__LP64__
diff --git a/dom/plugins/ipc/moz.build b/dom/plugins/ipc/moz.build
index 00f9a47128..9190d19272 100644
--- a/dom/plugins/ipc/moz.build
+++ b/dom/plugins/ipc/moz.build
@@ -3,6 +3,9 @@
# 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/.
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += ['interpose']
+
EXPORTS.mozilla += [
'PluginLibrary.h',
]
@@ -13,6 +16,7 @@ EXPORTS.mozilla.plugins += [
'BrowserStreamParent.h',
'ChildAsyncCall.h',
'ChildTimer.h',
+ 'NPEventOSX.h',
'NPEventUnix.h',
'NPEventWindows.h',
'PluginAsyncSurrogate.h',
@@ -32,6 +36,7 @@ EXPORTS.mozilla.plugins += [
'PluginScriptableObjectUtils.h',
'PluginStreamChild.h',
'PluginStreamParent.h',
+ 'PluginUtilsOSX.h',
'PluginWidgetChild.h',
'PluginWidgetParent.h',
'StreamNotifyChild.h',
@@ -53,6 +58,11 @@ if CONFIG['OS_ARCH'] == 'WINNT':
'hangui',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ EXPORTS.mozilla.plugins += [
+ 'PluginInterposeOSX.h',
+ ]
+
SOURCES += [
'BrowserStreamChild.cpp',
'BrowserStreamParent.cpp',
@@ -76,6 +86,12 @@ SOURCES += [
'PluginWidgetParent.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'PluginInterposeOSX.mm',
+ 'PluginUtilsOSX.mm',
+ ]
+
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
SOURCES += [
'D3D11SurfaceHolder.cpp',
diff --git a/dom/system/OSFileConstants.cpp b/dom/system/OSFileConstants.cpp
index 2b8a7e5369..551ed5773f 100644
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -24,6 +24,10 @@
#include <linux/fadvise.h>
#endif // defined(XP_LINUX)
+#if defined(XP_MACOSX)
+#include "copyfile.h"
+#endif // defined(XP_MACOSX)
+
#if defined(XP_WIN)
#include <windows.h>
#include <accctrl.h>
@@ -122,6 +126,22 @@ struct Paths {
nsString winStartMenuProgsDir;
#endif // defined(XP_WIN)
+#if defined(XP_MACOSX)
+ /**
+ * The user's Library directory.
+ */
+ nsString macUserLibDir;
+ /**
+ * The Application directory, that stores applications installed in the
+ * system.
+ */
+ nsString macLocalApplicationsDir;
+ /**
+ * The user's trash directory.
+ */
+ nsString macTrashDir;
+#endif // defined(XP_MACOSX)
+
Paths()
{
libDir.SetIsVoid(true);
@@ -136,6 +156,12 @@ struct Paths {
winAppDataDir.SetIsVoid(true);
winStartMenuProgsDir.SetIsVoid(true);
#endif // defined(XP_WIN)
+
+#if defined(XP_MACOSX)
+ macUserLibDir.SetIsVoid(true);
+ macLocalApplicationsDir.SetIsVoid(true);
+ macTrashDir.SetIsVoid(true);
+#endif // defined(XP_MACOSX)
}
};
@@ -280,6 +306,12 @@ nsresult InitOSFileConstants()
GetPathToSpecialDir(NS_WIN_PROGRAMS_DIR, paths->winStartMenuProgsDir);
#endif // defined(XP_WIN)
+#if defined(XP_MACOSX)
+ GetPathToSpecialDir(NS_MAC_USER_LIB_DIR, paths->macUserLibDir);
+ GetPathToSpecialDir(NS_OSX_LOCAL_APPLICATIONS_DIR, paths->macLocalApplicationsDir);
+ GetPathToSpecialDir(NS_MAC_TRASH_DIR, paths->macTrashDir);
+#endif // defined(XP_MACOSX)
+
gPaths = paths.forget();
// Get the umask from the system-info service.
@@ -933,12 +965,20 @@ bool DefineOSFileConstants(JSContext *cx, JS::Handle<JSObject*> global)
// Note that we don't actually provide the full path, only the name of the
// library, which is sufficient to link to the library using js-ctypes.
- // On all supported platforms, libxul is a library "xul" with regular
+#if defined(XP_MACOSX)
+ // Under MacOS X, for some reason, libxul is called simply "XUL",
+ // and we need to provide the full path.
+ nsAutoString libxul;
+ libxul.Append(gPaths->libDir);
+ libxul.AppendLiteral("/XUL");
+#else
+ // On other platforms, libxul is a library "xul" with regular
// library prefix/suffix.
nsAutoString libxul;
libxul.AppendLiteral(DLL_PREFIX);
libxul.AppendLiteral("xul");
libxul.AppendLiteral(DLL_SUFFIX);
+#endif // defined(XP_MACOSX)
if (!SetStringProperty(cx, objPath, "libxul", libxul)) {
return false;
@@ -986,6 +1026,20 @@ bool DefineOSFileConstants(JSContext *cx, JS::Handle<JSObject*> global)
}
#endif // defined(XP_WIN)
+#if defined(XP_MACOSX)
+ if (!SetStringProperty(cx, objPath, "macUserLibDir", gPaths->macUserLibDir)) {
+ return false;
+ }
+
+ if (!SetStringProperty(cx, objPath, "macLocalApplicationsDir", gPaths->macLocalApplicationsDir)) {
+ return false;
+ }
+
+ if (!SetStringProperty(cx, objPath, "macTrashDir", gPaths->macTrashDir)) {
+ return false;
+ }
+#endif // defined(XP_MACOSX)
+
// sqlite3 is always a shared lib
nsAutoString libsqlite3;
libsqlite3.AppendLiteral(DLL_PREFIX);
diff --git a/dom/system/mac/CoreLocationLocationProvider.h b/dom/system/mac/CoreLocationLocationProvider.h
new file mode 100644
index 0000000000..979bc916d8
--- /dev/null
+++ b/dom/system/mac/CoreLocationLocationProvider.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsCOMPtr.h"
+#include "nsIGeolocationProvider.h"
+
+
+/*
+ * The CoreLocationObjects class contains the CoreLocation objects
+ * we'll need.
+ *
+ * Declaring them directly in CoreLocationLocationProvider
+ * would require Objective-C++ syntax, which would contaminate all
+ * files that include this header and require them to be Objective-C++
+ * as well.
+ *
+ * The solution then is to forward-declare CoreLocationObjects here and
+ * hold a pointer to it in CoreLocationLocationProvider, and only actually
+ * define it in CoreLocationLocationProvider.mm, thus making it safe
+ * for nsGeolocation.cpp, which is C++-only, to include this header.
+ */
+class CoreLocationObjects;
+class MLSFallback;
+
+class CoreLocationLocationProvider
+ : public nsIGeolocationProvider
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONPROVIDER
+
+ CoreLocationLocationProvider();
+ void NotifyError(uint16_t aErrorCode);
+ void Update(nsIDOMGeoPosition* aSomewhere);
+ void CreateMLSFallbackProvider();
+ void CancelMLSFallbackProvider();
+
+private:
+ virtual ~CoreLocationLocationProvider();
+
+ CoreLocationObjects* mCLObjects;
+ nsCOMPtr<nsIGeolocationUpdate> mCallback;
+ RefPtr<MLSFallback> mMLSFallbackProvider;
+
+ class MLSUpdate : public nsIGeolocationUpdate
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGEOLOCATIONUPDATE
+
+ explicit MLSUpdate(CoreLocationLocationProvider& parentProvider);
+
+ private:
+ CoreLocationLocationProvider& mParentLocationProvider;
+ virtual ~MLSUpdate();
+ };
+};
diff --git a/dom/system/mac/CoreLocationLocationProvider.mm b/dom/system/mac/CoreLocationLocationProvider.mm
new file mode 100644
index 0000000000..7a3feba97b
--- /dev/null
+++ b/dom/system/mac/CoreLocationLocationProvider.mm
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsGeoPosition.h"
+#include "nsIConsoleService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIDOMGeoPositionError.h"
+#include "CoreLocationLocationProvider.h"
+#include "nsCocoaFeatures.h"
+#include "prtime.h"
+#include "MLSFallback.h"
+
+#include <CoreLocation/CLError.h>
+#include <CoreLocation/CLLocation.h>
+#include <CoreLocation/CLLocationManager.h>
+#include <CoreLocation/CLLocationManagerDelegate.h>
+
+#include <objc/objc.h>
+#include <objc/objc-runtime.h>
+
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+static const CLLocationAccuracy kHIGH_ACCURACY = kCLLocationAccuracyBest;
+static const CLLocationAccuracy kDEFAULT_ACCURACY = kCLLocationAccuracyNearestTenMeters;
+
+@interface LocationDelegate : NSObject <CLLocationManagerDelegate>
+{
+ CoreLocationLocationProvider* mProvider;
+}
+
+- (id)init:(CoreLocationLocationProvider*)aProvider;
+- (void)locationManager:(CLLocationManager*)aManager
+ didFailWithError:(NSError *)aError;
+- (void)locationManager:(CLLocationManager*)aManager didUpdateLocations:(NSArray*)locations;
+
+@end
+
+@implementation LocationDelegate
+- (id) init:(CoreLocationLocationProvider*) aProvider
+{
+ if ((self = [super init])) {
+ mProvider = aProvider;
+ }
+
+ return self;
+}
+
+- (void)locationManager:(CLLocationManager*)aManager
+ didFailWithError:(NSError *)aError
+{
+ nsCOMPtr<nsIConsoleService> console =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+
+ NS_ENSURE_TRUE_VOID(console);
+
+ NSString* message =
+ [@"Failed to acquire position: " stringByAppendingString: [aError localizedDescription]];
+
+ console->LogStringMessage(NS_ConvertUTF8toUTF16([message UTF8String]).get());
+
+ if ([aError code] == kCLErrorDenied) {
+ mProvider->NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED);
+ return;
+ }
+
+ // The CL provider does not fallback to GeoIP, so use NetworkGeolocationProvider for this.
+ // The concept here is: on error, hand off geolocation to MLS, which will then report
+ // back a location or error. We can't call this with no delay however, as this method
+ // is called with an error code of 0 in both failed geolocation cases, and also when
+ // geolocation is not immediately available.
+ // The 2 sec delay is arbitrarily large enough that CL has a reasonable head start and
+ // if it is likely to succeed, it should complete before the MLS provider.
+ // Take note that in locationManager:didUpdateLocations: the handoff to MLS is stopped.
+ mProvider->CreateMLSFallbackProvider();
+}
+
+- (void)locationManager:(CLLocationManager*)aManager didUpdateLocations:(NSArray*)aLocations
+{
+ if (aLocations.count < 1) {
+ return;
+ }
+
+ mProvider->CancelMLSFallbackProvider();
+
+ CLLocation* location = [aLocations objectAtIndex:0];
+
+ nsCOMPtr<nsIDOMGeoPosition> geoPosition =
+ new nsGeoPosition(location.coordinate.latitude,
+ location.coordinate.longitude,
+ location.altitude,
+ location.horizontalAccuracy,
+ location.verticalAccuracy,
+ location.course,
+ location.speed,
+ PR_Now() / PR_USEC_PER_MSEC);
+
+ mProvider->Update(geoPosition);
+}
+@end
+
+NS_IMPL_ISUPPORTS(CoreLocationLocationProvider::MLSUpdate, nsIGeolocationUpdate);
+
+CoreLocationLocationProvider::MLSUpdate::MLSUpdate(CoreLocationLocationProvider& parentProvider)
+ : mParentLocationProvider(parentProvider)
+{
+}
+
+CoreLocationLocationProvider::MLSUpdate::~MLSUpdate()
+{
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::MLSUpdate::Update(nsIDOMGeoPosition *position)
+{
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ position->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return NS_ERROR_FAILURE;
+ }
+ mParentLocationProvider.Update(position);
+ return NS_OK;
+}
+NS_IMETHODIMP
+CoreLocationLocationProvider::MLSUpdate::NotifyError(uint16_t error)
+{
+ mParentLocationProvider.NotifyError(error);
+ return NS_OK;
+}
+class CoreLocationObjects {
+public:
+ nsresult Init(CoreLocationLocationProvider* aProvider) {
+ mLocationManager = [[CLLocationManager alloc] init];
+ NS_ENSURE_TRUE(mLocationManager, NS_ERROR_NOT_AVAILABLE);
+
+ mLocationDelegate = [[LocationDelegate alloc] init:aProvider];
+ NS_ENSURE_TRUE(mLocationDelegate, NS_ERROR_NOT_AVAILABLE);
+
+ mLocationManager.desiredAccuracy = kDEFAULT_ACCURACY;
+ mLocationManager.delegate = mLocationDelegate;
+
+ return NS_OK;
+ }
+
+ ~CoreLocationObjects() {
+ if (mLocationManager) {
+ [mLocationManager release];
+ }
+
+ if (mLocationDelegate) {
+ [mLocationDelegate release];
+ }
+ }
+
+ LocationDelegate* mLocationDelegate;
+ CLLocationManager* mLocationManager;
+};
+
+NS_IMPL_ISUPPORTS(CoreLocationLocationProvider, nsIGeolocationProvider)
+
+CoreLocationLocationProvider::CoreLocationLocationProvider()
+ : mCLObjects(nullptr), mMLSFallbackProvider(nullptr)
+{
+}
+
+CoreLocationLocationProvider::~CoreLocationLocationProvider()
+{
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::Startup()
+{
+ if (!mCLObjects) {
+ nsAutoPtr<CoreLocationObjects> clObjs(new CoreLocationObjects());
+
+ nsresult rv = clObjs->Init(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCLObjects = clObjs.forget();
+ }
+
+ // Must be stopped before starting or response (success or failure) is not guaranteed
+ [mCLObjects->mLocationManager stopUpdatingLocation];
+ [mCLObjects->mLocationManager startUpdatingLocation];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::Watch(nsIGeolocationUpdate* aCallback)
+{
+ if (mCallback) {
+ return NS_OK;
+ }
+
+ mCallback = aCallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::Shutdown()
+{
+ NS_ENSURE_STATE(mCLObjects);
+
+ [mCLObjects->mLocationManager stopUpdatingLocation];
+
+ delete mCLObjects;
+ mCLObjects = nullptr;
+
+ if (mMLSFallbackProvider) {
+ mMLSFallbackProvider->Shutdown();
+ mMLSFallbackProvider = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::SetHighAccuracy(bool aEnable)
+{
+ NS_ENSURE_STATE(mCLObjects);
+
+ mCLObjects->mLocationManager.desiredAccuracy =
+ (aEnable ? kHIGH_ACCURACY : kDEFAULT_ACCURACY);
+
+ return NS_OK;
+}
+
+void
+CoreLocationLocationProvider::Update(nsIDOMGeoPosition* aSomewhere)
+{
+ if (aSomewhere && mCallback) {
+ mCallback->Update(aSomewhere);
+ }
+}
+
+void
+CoreLocationLocationProvider::NotifyError(uint16_t aErrorCode)
+{
+ mCallback->NotifyError(aErrorCode);
+}
+
+void
+CoreLocationLocationProvider::CreateMLSFallbackProvider()
+{
+ if (mMLSFallbackProvider) {
+ return;
+ }
+
+ mMLSFallbackProvider = new MLSFallback();
+ mMLSFallbackProvider->Startup(new MLSUpdate(*this));
+}
+
+void
+CoreLocationLocationProvider::CancelMLSFallbackProvider()
+{
+ if (!mMLSFallbackProvider) {
+ return;
+ }
+
+ mMLSFallbackProvider->Shutdown();
+ mMLSFallbackProvider = nullptr;
+}
diff --git a/dom/system/mac/moz.build b/dom/system/mac/moz.build
new file mode 100644
index 0000000000..08b7c2151e
--- /dev/null
+++ b/dom/system/mac/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += ['CoreLocationLocationProvider.mm']
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/dom/geolocation',
+]
+
diff --git a/dom/system/moz.build b/dom/system/moz.build
index 31097d2481..7e42761e5e 100644
--- a/dom/system/moz.build
+++ b/dom/system/moz.build
@@ -7,6 +7,8 @@ toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
if toolkit == 'windows':
DIRS += ['windows']
+elif toolkit == 'cocoa':
+ DIRS += ['mac']
elif toolkit in ('gtk2', 'gtk3'):
DIRS += ['linux']
diff --git a/dom/xbl/builtin/mac/jar.mn b/dom/xbl/builtin/mac/jar.mn
new file mode 100644
index 0000000000..9f05c2dd6c
--- /dev/null
+++ b/dom/xbl/builtin/mac/jar.mn
@@ -0,0 +1,6 @@
+# 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/.
+
+toolkit.jar:
+* content/global/platformHTMLBindings.xml (platformHTMLBindings.xml)
diff --git a/dom/xbl/builtin/mac/moz.build b/dom/xbl/builtin/mac/moz.build
new file mode 100644
index 0000000000..635fa39c99
--- /dev/null
+++ b/dom/xbl/builtin/mac/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/dom/xbl/builtin/mac/platformHTMLBindings.xml b/dom/xbl/builtin/mac/platformHTMLBindings.xml
new file mode 100644
index 0000000000..b705923999
--- /dev/null
+++ b/dom/xbl/builtin/mac/platformHTMLBindings.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+
+<bindings id="htmlBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="inputFields" bindToUntrustedContent="true">
+ <handlers>
+ <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
+ <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
+ <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="textAreas" bindToUntrustedContent="true">
+ <handlers>
+ <handler event="keypress" key="c" modifiers="accel" command="cmd_copy"/>
+ <handler event="keypress" key="x" modifiers="accel" command="cmd_cut"/>
+ <handler event="keypress" key="v" modifiers="accel" command="cmd_paste"/>
+ <handler event="keypress" key="z" modifiers="accel" command="cmd_undo"/>
+ <handler event="keypress" key="z" modifiers="accel,shift" command="cmd_redo"/>
+ <handler event="keypress" key="a" modifiers="accel" command="cmd_selectAll"/>
+ </handlers>
+ </binding>
+
+ <binding id="browser">
+ <handlers>
+#include ../browser-base.inc
+ <handler event="keypress" keycode="VK_PAGE_UP" command="cmd_scrollPageUp"/>
+ <handler event="keypress" keycode="VK_PAGE_DOWN" command="cmd_scrollPageDown"/>
+ <handler event="keypress" keycode="VK_HOME" command="cmd_scrollTop" />
+ <handler event="keypress" keycode="VK_END" command="cmd_scrollBottom" />
+
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt" command="cmd_moveLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt" command="cmd_moveRight2" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="alt,shift" command="cmd_selectLeft2" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="alt,shift" command="cmd_selectRight2" />
+ <handler event="keypress" keycode="VK_LEFT" modifiers="shift" command="cmd_selectLeft" />
+ <handler event="keypress" keycode="VK_RIGHT" modifiers="shift" command="cmd_selectRight" />
+ <handler event="keypress" keycode="VK_UP" modifiers="alt,shift" command="cmd_selectUp2" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt,shift" command="cmd_selectDown2" />
+ <handler event="keypress" keycode="VK_UP" modifiers="shift" command="cmd_selectUp" />
+ <handler event="keypress" keycode="VK_DOWN" modifiers="shift" command="cmd_selectDown" />
+ <handler event="keypress" keycode="VK_UP" modifiers="accel" command="cmd_moveUp2"/>
+ <handler event="keypress" keycode="VK_DOWN" modifiers="accel" command="cmd_moveDown2"/>
+ </handlers>
+ </binding>
+
+ <binding id="editor">
+ <handlers>
+ <handler event="keypress" key=" " modifiers="shift" command="cmd_scrollPageUp" />
+ <handler event="keypress" key=" " command="cmd_scrollPageDown" />
+
+ <handler event="keypress" key="z" command="cmd_undo" modifiers="accel"/>
+ <handler event="keypress" key="z" command="cmd_redo" modifiers="accel,shift" />
+ <handler event="keypress" key="x" command="cmd_cut" modifiers="accel"/>
+ <handler event="keypress" key="c" command="cmd_copy" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_paste" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,shift"/>
+ <handler event="keypress" key="a" command="cmd_selectAll" modifiers="accel"/>
+ <handler event="keypress" key="v" command="cmd_pasteNoFormatting" modifiers="accel,alt,shift"/>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/dom/xbl/builtin/moz.build b/dom/xbl/builtin/moz.build
index b6c41678c4..27d3e6c146 100644
--- a/dom/xbl/builtin/moz.build
+++ b/dom/xbl/builtin/moz.build
@@ -5,6 +5,8 @@
if CONFIG['OS_ARCH'] == 'WINNT':
DIRS += ['win']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += ['mac']
elif CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
DIRS += ['unix']
else:
diff --git a/dom/xbl/nsXBLPrototypeHandler.cpp b/dom/xbl/nsXBLPrototypeHandler.cpp
index 4872a850d7..963e6835c6 100644
--- a/dom/xbl/nsXBLPrototypeHandler.cpp
+++ b/dom/xbl/nsXBLPrototypeHandler.cpp
@@ -167,8 +167,13 @@ nsXBLPrototypeHandler::InitAccessKeys()
return;
}
- // Compiled-in defaults, in case we can't get the pref
+ // Compiled-in defaults, in case we can't get the pref --
+ // mac doesn't have menu shortcuts, other platforms use alt.
+#ifdef XP_MACOSX
+ kMenuAccessKey = 0;
+#else
kMenuAccessKey = nsIDOMKeyEvent::DOM_VK_ALT;
+#endif
// Get the menu access key value from prefs, overriding the default:
kMenuAccessKey =
diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp
index 93d422d1e9..2ae03e0b17 100644
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -523,6 +523,18 @@ nsXULElement::IsFocusableInternal(int32_t *aTabIndex, bool aWithMouse)
// elements are not focusable by default
bool shouldFocus = false;
+#ifdef XP_MACOSX
+ // on Mac, mouse interactions only focus the element if it's a list,
+ // or if it's a remote target, since the remote target must handle
+ // the focus.
+ if (aWithMouse &&
+ IsNonList(mNodeInfo) &&
+ !EventStateManager::IsRemoteTarget(this))
+ {
+ return false;
+ }
+#endif
+
nsCOMPtr<nsIDOMXULControlElement> xulControl = do_QueryObject(this);
if (xulControl) {
// a disabled element cannot be focused and is not part of the tab order
diff --git a/embedding/components/build/moz.build b/embedding/components/build/moz.build
index c66f0c79ee..361fd8768a 100644
--- a/embedding/components/build/moz.build
+++ b/embedding/components/build/moz.build
@@ -22,6 +22,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
LOCAL_INCLUDES += [
'../printingui/win',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DEFINES['PROXY_PRINTING'] = 1
+ LOCAL_INCLUDES += [
+ '../printingui/mac',
+ ]
if CONFIG['MOZ_PDF_PRINTING']:
DEFINES['PROXY_PRINTING'] = 1
diff --git a/embedding/components/printingui/mac/moz.build b/embedding/components/printingui/mac/moz.build
new file mode 100644
index 0000000000..c2fe2f4731
--- /dev/null
+++ b/embedding/components/printingui/mac/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+UNIFIED_SOURCES += [
+ 'nsPrintProgress.cpp',
+ 'nsPrintProgressParams.cpp',
+]
+
+SOURCES += [
+ 'nsPrintingPromptServiceX.mm',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/embedding/components/printingui/mac/nsPrintProgress.cpp b/embedding/components/printingui/mac/nsPrintProgress.cpp
new file mode 100644
index 0000000000..0ae0a63d9a
--- /dev/null
+++ b/embedding/components/printingui/mac/nsPrintProgress.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsPrintProgress.h"
+
+#include "nsIBaseWindow.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIComponentManager.h"
+#include "nsPIDOMWindow.h"
+
+NS_IMPL_ADDREF(nsPrintProgress)
+NS_IMPL_RELEASE(nsPrintProgress)
+
+NS_INTERFACE_MAP_BEGIN(nsPrintProgress)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback)
+ NS_INTERFACE_MAP_ENTRY(nsIPrintProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+
+nsPrintProgress::nsPrintProgress()
+{
+ m_closeProgress = false;
+ m_processCanceled = false;
+ m_pendingStateFlags = -1;
+ m_pendingStateValue = NS_OK;
+}
+
+nsPrintProgress::~nsPrintProgress()
+{
+ (void)ReleaseListeners();
+}
+
+NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent,
+ const char *dialogURL,
+ nsISupports *parameters,
+ nsIObserver *openDialogObserver,
+ bool *notifyOnOpen)
+{
+ MOZ_ASSERT_UNREACHABLE("The nsPrintingPromptService::ShowProgress "
+ "implementation for OS X returns "
+ "NS_ERROR_NOT_IMPLEMENTED, so we should never get "
+ "here.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose)
+{
+ MOZ_ASSERT_UNREACHABLE("The nsPrintingPromptService::ShowProgress "
+ "implementation for OS X returns "
+ "NS_ERROR_NOT_IMPLEMENTED, so we should never get "
+ "here.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ if (! m_closeProgress && m_dialog) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog);
+ MOZ_ASSERT(window);
+ return window->GetPrompter(_retval);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser)
+{
+ NS_ENSURE_ARG_POINTER(aProcessCanceledByUser);
+ *aProcessCanceledByUser = m_processCanceled;
+ return NS_OK;
+}
+NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser)
+{
+ m_processCanceled = aProcessCanceledByUser;
+ OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener)
+{
+ if (!listener) //Nothing to do with a null listener!
+ return NS_OK;
+
+ m_listenerList.AppendObject(listener);
+ if (m_closeProgress || m_processCanceled)
+ listener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP, NS_OK);
+ else
+ {
+ listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get());
+ if (m_pendingStateFlags != -1)
+ listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener)
+{
+ if (listener)
+ m_listenerList.RemoveObject(listener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::DoneIniting()
+{
+ if (m_observer) {
+ m_observer->Observe(nullptr, nullptr, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ m_pendingStateFlags = aStateFlags;
+ m_pendingStateValue = aStatus;
+
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ if (aMessage && *aMessage)
+ m_pendingStatus = aMessage;
+
+ uint32_t count = m_listenerList.Count();
+ for (uint32_t i = count - 1; i < count; i --)
+ {
+ nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i);
+ if (progressListener)
+ progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ return NS_OK;
+}
+
+nsresult nsPrintProgress::ReleaseListeners()
+{
+ m_listenerList.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status)
+{
+ return OnStatusChange(nullptr, nullptr, NS_OK, status);
+}
+
+NS_IMETHODIMP nsPrintProgress::StartMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::StopMeteors()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell,
+ mozIDOMWindowProxy *window)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsPrintProgress::CloseWindow()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
diff --git a/embedding/components/printingui/mac/nsPrintProgress.h b/embedding/components/printingui/mac/nsPrintProgress.h
new file mode 100644
index 0000000000..938558c3e1
--- /dev/null
+++ b/embedding/components/printingui/mac/nsPrintProgress.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef __nsPrintProgress_h
+#define __nsPrintProgress_h
+
+#include "nsIPrintProgress.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsIPrintStatusFeedback.h"
+#include "nsIObserver.h"
+#include "nsString.h"
+
+class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPRINTPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIPRINTSTATUSFEEDBACK
+
+ nsPrintProgress();
+
+protected:
+ virtual ~nsPrintProgress();
+
+private:
+ nsresult ReleaseListeners();
+
+ bool m_closeProgress;
+ bool m_processCanceled;
+ nsString m_pendingStatus;
+ int32_t m_pendingStateFlags;
+ nsresult m_pendingStateValue;
+ // XXX This member is read-only.
+ nsCOMPtr<mozIDOMWindowProxy> m_dialog;
+ nsCOMArray<nsIWebProgressListener> m_listenerList;
+ nsCOMPtr<nsIObserver> m_observer;
+};
+
+#endif
diff --git a/embedding/components/printingui/mac/nsPrintProgressParams.cpp b/embedding/components/printingui/mac/nsPrintProgressParams.cpp
new file mode 100644
index 0000000000..eba86b2987
--- /dev/null
+++ b/embedding/components/printingui/mac/nsPrintProgressParams.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsPrintProgressParams.h"
+#include "nsReadableUtils.h"
+
+
+NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams)
+
+nsPrintProgressParams::nsPrintProgressParams()
+{
+}
+
+nsPrintProgressParams::~nsPrintProgressParams()
+{
+}
+
+NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle)
+{
+ NS_ENSURE_ARG(aDocTitle);
+
+ *aDocTitle = ToNewUnicode(mDocTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle)
+{
+ mDocTitle = aDocTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL)
+{
+ NS_ENSURE_ARG(aDocURL);
+
+ *aDocURL = ToNewUnicode(mDocURL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL)
+{
+ mDocURL = aDocURL;
+ return NS_OK;
+}
+
diff --git a/embedding/components/printingui/mac/nsPrintProgressParams.h b/embedding/components/printingui/mac/nsPrintProgressParams.h
new file mode 100644
index 0000000000..65c00d98f0
--- /dev/null
+++ b/embedding/components/printingui/mac/nsPrintProgressParams.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef __nsPrintProgressParams_h
+#define __nsPrintProgressParams_h
+
+#include "nsIPrintProgressParams.h"
+#include "nsString.h"
+
+class nsPrintProgressParams : public nsIPrintProgressParams
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTPROGRESSPARAMS
+
+ nsPrintProgressParams();
+
+protected:
+ virtual ~nsPrintProgressParams();
+
+private:
+ nsString mDocTitle;
+ nsString mDocURL;
+};
+
+#endif
diff --git a/embedding/components/printingui/mac/nsPrintingPromptService.h b/embedding/components/printingui/mac/nsPrintingPromptService.h
new file mode 100644
index 0000000000..0334db2230
--- /dev/null
+++ b/embedding/components/printingui/mac/nsPrintingPromptService.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef __nsPrintingPromptService_h
+#define __nsPrintingPromptService_h
+
+// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF}
+#define NS_PRINTINGPROMPTSERVICE_CID \
+ {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}}
+#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \
+ "@mozilla.org/embedcomp/printingprompt-service;1"
+
+#include "nsCOMPtr.h"
+#include "nsIPrintingPromptService.h"
+#include "nsPIPromptService.h"
+#include "nsIWindowWatcher.h"
+
+// Printing Progress Includes
+#include "nsPrintProgress.h"
+#include "nsIWebProgressListener.h"
+
+class nsPrintingPromptService: public nsIPrintingPromptService,
+ public nsIWebProgressListener
+{
+public:
+ nsPrintingPromptService();
+
+ nsresult Init();
+
+ NS_DECL_NSIPRINTINGPROMPTSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_ISUPPORTS
+
+protected:
+ virtual ~nsPrintingPromptService();
+
+private:
+ nsCOMPtr<nsIPrintProgress> mPrintProgress;
+};
+
+#endif
diff --git a/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm b/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm
new file mode 100644
index 0000000000..3b956100be
--- /dev/null
+++ b/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsPrintingPromptService.h"
+
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsObjCExceptions.h"
+
+#include "nsIPrintingPromptService.h"
+#include "nsIFactory.h"
+#include "nsIPrintDialogService.h"
+#include "nsPIDOMWindow.h"
+
+//*****************************************************************************
+// nsPrintingPromptService
+//*****************************************************************************
+
+NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener)
+
+nsPrintingPromptService::nsPrintingPromptService()
+{
+}
+
+nsPrintingPromptService::~nsPrintingPromptService()
+{
+}
+
+nsresult nsPrintingPromptService::Init()
+{
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsPrintingPromptService::nsIPrintingPromptService
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, nsIWebBrowserPrint *webBrowserPrint, nsIPrintSettings *printSettings)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService(
+ NS_PRINTDIALOGSERVICE_CONTRACTID));
+ if (dlgPrint) {
+ return dlgPrint->Show(nsPIDOMWindowOuter::From(parent), printSettings,
+ webBrowserPrint);
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent,
+ nsIWebBrowserPrint* webBrowserPrint, // ok to be null
+ nsIPrintSettings* printSettings, // ok to be null
+ nsIObserver* openDialogObserver, // ok to be null
+ bool isForPrinting,
+ nsIWebProgressListener** webProgressListener,
+ nsIPrintProgressParams** printProgressParams,
+ bool* notifyOnOpen)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, nsIPrintSettings *printSettings, nsIObserver *aObs)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+ nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService(
+ NS_PRINTDIALOGSERVICE_CONTRACTID));
+ if (dlgPrint) {
+ return dlgPrint->ShowPageSetup(nsPIDOMWindowOuter::From(parent), printSettings);
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, const char16_t *printerName, nsIPrintSettings *printSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+//*****************************************************************************
+// nsPrintingPromptService::nsIWebProgressListener
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus)
+{
+ return NS_OK;
+}
+
+/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */
+NS_IMETHODIMP
+nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress)
+{
+ return NS_OK;
+}
+
+/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */
+NS_IMETHODIMP
+nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */
+NS_IMETHODIMP
+nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage)
+{
+ return NS_OK;
+}
+
+/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */
+NS_IMETHODIMP
+nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state)
+{
+ return NS_OK;
+}
diff --git a/embedding/components/printingui/moz.build b/embedding/components/printingui/moz.build
index e0d6316931..c75471555e 100644
--- a/embedding/components/printingui/moz.build
+++ b/embedding/components/printingui/moz.build
@@ -10,5 +10,7 @@ DIRS += ['ipc']
if CONFIG['NS_PRINTING']:
if toolkit == 'windows':
DIRS += ['win']
+ elif toolkit == 'cocoa':
+ DIRS += ['mac']
elif CONFIG['MOZ_PDF_PRINTING']:
DIRS += ['unixshared']
diff --git a/extensions/auth/gssapi.h b/extensions/auth/gssapi.h
index a5331d741b..a3ce3d8c58 100644
--- a/extensions/auth/gssapi.h
+++ b/extensions/auth/gssapi.h
@@ -1,3 +1,4 @@
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
/* ***** BEGIN LICENSE BLOCK *****
* Copyright 1993 by OpenVision Technologies, Inc.
*
@@ -93,6 +94,10 @@
EXTERN_C_BEGIN
+#if defined(XP_MACOSX)
+# pragma pack(push,2)
+#endif
+
/*
* If the platform supports the xom.h header file, it should be
* included here.
@@ -833,6 +838,10 @@ GSS_CALLCONV GSS_FUNC(gss_duplicate_name)
);
+#if defined(XP_MACOSX)
+# pragma pack(pop)
+#endif
+
EXTERN_C_END
#endif /* GSSAPI_H_ */
diff --git a/extensions/auth/nsAuthGSSAPI.cpp b/extensions/auth/nsAuthGSSAPI.cpp
index bc99d519e3..0e273a3005 100644
--- a/extensions/auth/nsAuthGSSAPI.cpp
+++ b/extensions/auth/nsAuthGSSAPI.cpp
@@ -1,3 +1,4 @@
+/* vim:set ts=4 sw=4 sts=4 et cindent: */
/* 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/. */
@@ -24,6 +25,19 @@
#include "nsAuthGSSAPI.h"
+#ifdef XP_MACOSX
+#include <Kerberos/Kerberos.h>
+#endif
+
+#ifdef XP_MACOSX
+typedef KLStatus (*KLCacheHasValidTickets_type)(
+ KLPrincipal,
+ KLKerberosVersion,
+ KLBoolean *,
+ KLPrincipal *,
+ char **);
+#endif
+
#if defined(HAVE_RES_NINIT)
#include <sys/types.h>
#include <netinet/in.h>
@@ -77,6 +91,12 @@ static PRLibrary* gssLibrary = nullptr;
#define gss_wrap_ptr ((gss_wrap_type)*gssFuncs[8].func)
#define gss_unwrap_ptr ((gss_unwrap_type)*gssFuncs[9].func)
+#ifdef XP_MACOSX
+static PRFuncPtr KLCacheHasValidTicketsPtr;
+#define KLCacheHasValidTickets_ptr \
+ ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
+#endif
+
static nsresult
gssInit()
{
@@ -192,6 +212,15 @@ gssInit()
return NS_ERROR_FAILURE;
}
}
+#ifdef XP_MACOSX
+ if (gssNativeImp &&
+ !(KLCacheHasValidTicketsPtr =
+ PR_FindFunctionSymbol(lib, "KLCacheHasValidTickets"))) {
+ LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n"));
+ PR_UnloadLibrary(lib);
+ return NS_ERROR_FAILURE;
+ }
+#endif
gssLibrary = lib;
return NS_OK;
@@ -412,6 +441,24 @@ nsAuthGSSAPI::GetNextToken(const void *inToken,
return NS_ERROR_UNEXPECTED;
}
+#if defined(XP_MACOSX)
+ // Suppress Kerberos prompts to get credentials. See bug 240643.
+ // We can only use Mac OS X specific kerb functions if we are using
+ // the native lib
+ KLBoolean found;
+ bool doingMailTask = mServiceName.Find("imap@") ||
+ mServiceName.Find("pop@") ||
+ mServiceName.Find("smtp@") ||
+ mServiceName.Find("ldap@");
+
+ if (!doingMailTask && (gssNativeImp &&
+ (KLCacheHasValidTickets_ptr(nullptr, kerberosVersion_V5, &found, nullptr, nullptr) != klNoErr || !found)))
+ {
+ major_status = GSS_S_FAILURE;
+ minor_status = 0;
+ }
+ else
+#endif /* XP_MACOSX */
major_status = gss_init_sec_context_ptr(&minor_status,
GSS_C_NO_CREDENTIAL,
&mCtx,
diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h
index 89de5630c6..e2020dc9e6 100644
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1481,6 +1481,11 @@ public:
static already_AddRefed<DrawTarget> CreateDrawTargetWithSkCanvas(SkCanvas* aCanvas);
#endif
+#ifdef XP_DARWIN
+ static already_AddRefed<GlyphRenderingOptions>
+ CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor);
+#endif
+
#ifdef WIN32
static already_AddRefed<DrawTarget> CreateDrawTargetForD3D11Texture(ID3D11Texture2D *aTexture, SurfaceFormat aFormat);
diff --git a/gfx/2d/Factory.cpp b/gfx/2d/Factory.cpp
index 5bec8e9ad8..5cd5d14eab 100644
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -25,6 +25,11 @@
#include "NativeFontResourceGDI.h"
#endif
+#ifdef XP_DARWIN
+#include "ScaledFontMac.h"
+#include "NativeFontResourceMac.h"
+#endif
+
#ifdef MOZ_WIDGET_GTK
#include "ScaledFontFontconfig.h"
#endif
@@ -260,6 +265,15 @@ Factory::CheckSurfaceSize(const IntSize &sz,
return false;
}
+#if defined(XP_MACOSX)
+ // CoreGraphics is limited to images < 32K in *height*,
+ // so clamp all surfaces on the Mac to that height
+ if (sz.height > SHRT_MAX) {
+ gfxDebug() << "Surface size too large (exceeds CoreGraphics limit)!";
+ return false;
+ }
+#endif
+
// assuming 4 bytes per pixel, make sure the allocation size
// doesn't overflow a int32_t either
CheckedInt<int32_t> stride = GetAlignedStride<16>(sz.width, 4);
@@ -467,6 +481,12 @@ Factory::CreateScaledFontForNativeFont(const NativeFont &aNativeFont, Float aSiz
}
#endif
#endif
+#ifdef XP_DARWIN
+ case NativeFontType::MAC_FONT_FACE:
+ {
+ return MakeAndAddRef<ScaledFontMac>(static_cast<CGFontRef>(aNativeFont.mFont), aSize);
+ }
+#endif
#if defined(USE_CAIRO) || defined(USE_SKIA_FREETYPE)
case NativeFontType::CAIRO_FONT_FACE:
{
@@ -504,6 +524,8 @@ Factory::CreateNativeFontResource(uint8_t *aData, uint32_t aSize,
return NativeFontResourceGDI::Create(aData, aSize,
/* aNeedsCairo = */ true);
}
+#elif XP_DARWIN
+ return NativeFontResourceMac::Create(aData, aSize);
#else
gfxWarning() << "Unable to create cairo scaled font from truetype data";
return nullptr;
@@ -780,6 +802,14 @@ Factory::CreateWrappingDataSourceSurface(uint8_t *aData,
return newSurf.forget();
}
+#ifdef XP_DARWIN
+already_AddRefed<GlyphRenderingOptions>
+Factory::CreateCGGlyphRenderingOptions(const Color &aFontSmoothingBackgroundColor)
+{
+ return MakeAndAddRef<GlyphRenderingOptionsCG>(aFontSmoothingBackgroundColor);
+}
+#endif
+
already_AddRefed<DataSourceSurface>
Factory::CreateDataSourceSurface(const IntSize &aSize,
SurfaceFormat aFormat,
diff --git a/gfx/2d/JobScheduler_posix.cpp b/gfx/2d/JobScheduler_posix.cpp
index 5446df6118..e41388f21c 100644
--- a/gfx/2d/JobScheduler_posix.cpp
+++ b/gfx/2d/JobScheduler_posix.cpp
@@ -31,7 +31,9 @@ public:
// XXX - temporarily disabled, see bug 1209039
//
// // Call this from the thread itself because of Mac.
-//#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+//#ifdef XP_MACOSX
+// pthread_setname_np(aName);
+//#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
// pthread_set_name_np(mThread, aName);
//#elif defined(__NetBSD__)
// pthread_setname_np(mThread, "%s", (void*)aName);
diff --git a/gfx/2d/MacIOSurface.cpp b/gfx/2d/MacIOSurface.cpp
new file mode 100644
index 0000000000..759de8575b
--- /dev/null
+++ b/gfx/2d/MacIOSurface.cpp
@@ -0,0 +1,615 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* 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 "MacIOSurface.h"
+#include <OpenGL/gl.h>
+#include <QuartzCore/QuartzCore.h>
+#include <dlfcn.h>
+#include "mozilla/RefPtr.h"
+#include "mozilla/Assertions.h"
+#include "GLConsts.h"
+
+using namespace mozilla;
+// IOSurface signatures
+#define IOSURFACE_FRAMEWORK_PATH \
+ "/System/Library/Frameworks/IOSurface.framework/IOSurface"
+#define OPENGL_FRAMEWORK_PATH \
+ "/System/Library/Frameworks/OpenGL.framework/OpenGL"
+#define COREGRAPHICS_FRAMEWORK_PATH \
+ "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" \
+ "CoreGraphics.framework/CoreGraphics"
+#define COREVIDEO_FRAMEWORK_PATH \
+ "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/" \
+ "CoreVideo.framework/CoreVideo"
+
+#define GET_CONST(const_name) \
+ ((CFStringRef*) dlsym(sIOSurfaceFramework, const_name))
+#define GET_IOSYM(dest,sym_name) \
+ (typeof(dest)) dlsym(sIOSurfaceFramework, sym_name)
+#define GET_CGLSYM(dest,sym_name) \
+ (typeof(dest)) dlsym(sOpenGLFramework, sym_name)
+#define GET_CGSYM(dest,sym_name) \
+ (typeof(dest)) dlsym(sCoreGraphicsFramework, sym_name)
+#define GET_CVSYM(dest, sym_name) \
+ (typeof(dest)) dlsym(sCoreVideoFramework, sym_name)
+
+MacIOSurfaceLib::LibraryUnloader MacIOSurfaceLib::sLibraryUnloader;
+bool MacIOSurfaceLib::isLoaded = false;
+void* MacIOSurfaceLib::sIOSurfaceFramework;
+void* MacIOSurfaceLib::sOpenGLFramework;
+void* MacIOSurfaceLib::sCoreGraphicsFramework;
+void* MacIOSurfaceLib::sCoreVideoFramework;
+IOSurfaceCreateFunc MacIOSurfaceLib::sCreate;
+IOSurfaceGetIDFunc MacIOSurfaceLib::sGetID;
+IOSurfaceLookupFunc MacIOSurfaceLib::sLookup;
+IOSurfaceGetBaseAddressFunc MacIOSurfaceLib::sGetBaseAddress;
+IOSurfaceGetBaseAddressOfPlaneFunc MacIOSurfaceLib::sGetBaseAddressOfPlane;
+IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sWidth;
+IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sHeight;
+IOSurfaceSizeTFunc MacIOSurfaceLib::sPlaneCount;
+IOSurfaceSizePlaneTFunc MacIOSurfaceLib::sBytesPerRow;
+IOSurfaceGetPropertyMaximumFunc MacIOSurfaceLib::sGetPropertyMaximum;
+IOSurfaceVoidFunc MacIOSurfaceLib::sIncrementUseCount;
+IOSurfaceVoidFunc MacIOSurfaceLib::sDecrementUseCount;
+IOSurfaceLockFunc MacIOSurfaceLib::sLock;
+IOSurfaceUnlockFunc MacIOSurfaceLib::sUnlock;
+CGLTexImageIOSurface2DFunc MacIOSurfaceLib::sTexImage;
+IOSurfaceContextCreateFunc MacIOSurfaceLib::sIOSurfaceContextCreate;
+IOSurfaceContextCreateImageFunc MacIOSurfaceLib::sIOSurfaceContextCreateImage;
+IOSurfaceContextGetSurfaceFunc MacIOSurfaceLib::sIOSurfaceContextGetSurface;
+CVPixelBufferGetIOSurfaceFunc MacIOSurfaceLib::sCVPixelBufferGetIOSurface;
+unsigned int (*MacIOSurfaceLib::sCGContextGetTypePtr) (CGContextRef) = nullptr;
+IOSurfacePixelFormatFunc MacIOSurfaceLib::sPixelFormat;
+
+CFStringRef MacIOSurfaceLib::kPropWidth;
+CFStringRef MacIOSurfaceLib::kPropHeight;
+CFStringRef MacIOSurfaceLib::kPropBytesPerElem;
+CFStringRef MacIOSurfaceLib::kPropBytesPerRow;
+CFStringRef MacIOSurfaceLib::kPropIsGlobal;
+
+bool MacIOSurfaceLib::isInit() {
+ // Guard against trying to reload the library
+ // if it is not available.
+ if (!isLoaded)
+ LoadLibrary();
+ MOZ_ASSERT(sIOSurfaceFramework);
+ return sIOSurfaceFramework;
+}
+
+IOSurfacePtr MacIOSurfaceLib::IOSurfaceCreate(CFDictionaryRef properties) {
+ return sCreate(properties);
+}
+
+IOSurfacePtr MacIOSurfaceLib::IOSurfaceLookup(IOSurfaceID aIOSurfaceID) {
+ return sLookup(aIOSurfaceID);
+}
+
+IOSurfaceID MacIOSurfaceLib::IOSurfaceGetID(IOSurfacePtr aIOSurfacePtr) {
+ return sGetID(aIOSurfacePtr);
+}
+
+void* MacIOSurfaceLib::IOSurfaceGetBaseAddress(IOSurfacePtr aIOSurfacePtr) {
+ return sGetBaseAddress(aIOSurfacePtr);
+}
+
+void* MacIOSurfaceLib::IOSurfaceGetBaseAddressOfPlane(IOSurfacePtr aIOSurfacePtr,
+ size_t planeIndex) {
+ return sGetBaseAddressOfPlane(aIOSurfacePtr, planeIndex);
+}
+
+size_t MacIOSurfaceLib::IOSurfaceGetPlaneCount(IOSurfacePtr aIOSurfacePtr) {
+ return sPlaneCount(aIOSurfacePtr);
+}
+
+size_t MacIOSurfaceLib::IOSurfaceGetWidth(IOSurfacePtr aIOSurfacePtr, size_t plane) {
+ return sWidth(aIOSurfacePtr, plane);
+}
+
+size_t MacIOSurfaceLib::IOSurfaceGetHeight(IOSurfacePtr aIOSurfacePtr, size_t plane) {
+ return sHeight(aIOSurfacePtr, plane);
+}
+
+size_t MacIOSurfaceLib::IOSurfaceGetBytesPerRow(IOSurfacePtr aIOSurfacePtr, size_t plane) {
+ return sBytesPerRow(aIOSurfacePtr, plane);
+}
+
+size_t MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(CFStringRef property) {
+ return sGetPropertyMaximum(property);
+}
+
+OSType MacIOSurfaceLib::IOSurfaceGetPixelFormat(IOSurfacePtr aIOSurfacePtr) {
+ return sPixelFormat(aIOSurfacePtr);
+}
+
+IOReturn MacIOSurfaceLib::IOSurfaceLock(IOSurfacePtr aIOSurfacePtr,
+ uint32_t options, uint32_t* seed) {
+ return sLock(aIOSurfacePtr, options, seed);
+}
+
+IOReturn MacIOSurfaceLib::IOSurfaceUnlock(IOSurfacePtr aIOSurfacePtr,
+ uint32_t options, uint32_t *seed) {
+ return sUnlock(aIOSurfacePtr, options, seed);
+}
+
+void MacIOSurfaceLib::IOSurfaceIncrementUseCount(IOSurfacePtr aIOSurfacePtr) {
+ sIncrementUseCount(aIOSurfacePtr);
+}
+
+void MacIOSurfaceLib::IOSurfaceDecrementUseCount(IOSurfacePtr aIOSurfacePtr) {
+ sDecrementUseCount(aIOSurfacePtr);
+}
+
+CGLError MacIOSurfaceLib::CGLTexImageIOSurface2D(CGLContextObj ctxt,
+ GLenum target, GLenum internalFormat,
+ GLsizei width, GLsizei height,
+ GLenum format, GLenum type,
+ IOSurfacePtr ioSurface, GLuint plane) {
+ return sTexImage(ctxt, target, internalFormat, width, height,
+ format, type, ioSurface, plane);
+}
+
+IOSurfacePtr MacIOSurfaceLib::CVPixelBufferGetIOSurface(CVPixelBufferRef aPixelBuffer) {
+ return sCVPixelBufferGetIOSurface(aPixelBuffer);
+}
+
+CGContextRef MacIOSurfaceLib::IOSurfaceContextCreate(IOSurfacePtr aIOSurfacePtr,
+ unsigned aWidth, unsigned aHeight,
+ unsigned aBitsPerComponent, unsigned aBytes,
+ CGColorSpaceRef aColorSpace, CGBitmapInfo bitmapInfo) {
+ if (!sIOSurfaceContextCreate)
+ return nullptr;
+ return sIOSurfaceContextCreate(aIOSurfacePtr, aWidth, aHeight, aBitsPerComponent, aBytes, aColorSpace, bitmapInfo);
+}
+
+CGImageRef MacIOSurfaceLib::IOSurfaceContextCreateImage(CGContextRef aContext) {
+ if (!sIOSurfaceContextCreateImage)
+ return nullptr;
+ return sIOSurfaceContextCreateImage(aContext);
+}
+
+IOSurfacePtr MacIOSurfaceLib::IOSurfaceContextGetSurface(CGContextRef aContext) {
+ if (!sIOSurfaceContextGetSurface)
+ return nullptr;
+ return sIOSurfaceContextGetSurface(aContext);
+}
+
+CFStringRef MacIOSurfaceLib::GetIOConst(const char* symbole) {
+ CFStringRef *address = (CFStringRef*)dlsym(sIOSurfaceFramework, symbole);
+ if (!address)
+ return nullptr;
+
+ return *address;
+}
+
+void MacIOSurfaceLib::LoadLibrary() {
+ if (isLoaded) {
+ return;
+ }
+ isLoaded = true;
+ sIOSurfaceFramework = dlopen(IOSURFACE_FRAMEWORK_PATH,
+ RTLD_LAZY | RTLD_LOCAL);
+ sOpenGLFramework = dlopen(OPENGL_FRAMEWORK_PATH,
+ RTLD_LAZY | RTLD_LOCAL);
+
+ sCoreGraphicsFramework = dlopen(COREGRAPHICS_FRAMEWORK_PATH,
+ RTLD_LAZY | RTLD_LOCAL);
+
+ sCoreVideoFramework = dlopen(COREVIDEO_FRAMEWORK_PATH,
+ RTLD_LAZY | RTLD_LOCAL);
+
+ if (!sIOSurfaceFramework || !sOpenGLFramework || !sCoreGraphicsFramework ||
+ !sCoreVideoFramework) {
+ if (sIOSurfaceFramework)
+ dlclose(sIOSurfaceFramework);
+ if (sOpenGLFramework)
+ dlclose(sOpenGLFramework);
+ if (sCoreGraphicsFramework)
+ dlclose(sCoreGraphicsFramework);
+ if (sCoreVideoFramework)
+ dlclose(sCoreVideoFramework);
+ sIOSurfaceFramework = nullptr;
+ sOpenGLFramework = nullptr;
+ sCoreGraphicsFramework = nullptr;
+ sCoreVideoFramework = nullptr;
+ return;
+ }
+
+ kPropWidth = GetIOConst("kIOSurfaceWidth");
+ kPropHeight = GetIOConst("kIOSurfaceHeight");
+ kPropBytesPerElem = GetIOConst("kIOSurfaceBytesPerElement");
+ kPropBytesPerRow = GetIOConst("kIOSurfaceBytesPerRow");
+ kPropIsGlobal = GetIOConst("kIOSurfaceIsGlobal");
+ sCreate = GET_IOSYM(sCreate, "IOSurfaceCreate");
+ sGetID = GET_IOSYM(sGetID, "IOSurfaceGetID");
+ sWidth = GET_IOSYM(sWidth, "IOSurfaceGetWidthOfPlane");
+ sHeight = GET_IOSYM(sHeight, "IOSurfaceGetHeightOfPlane");
+ sBytesPerRow = GET_IOSYM(sBytesPerRow, "IOSurfaceGetBytesPerRowOfPlane");
+ sGetPropertyMaximum = GET_IOSYM(sGetPropertyMaximum, "IOSurfaceGetPropertyMaximum");
+ sLookup = GET_IOSYM(sLookup, "IOSurfaceLookup");
+ sLock = GET_IOSYM(sLock, "IOSurfaceLock");
+ sUnlock = GET_IOSYM(sUnlock, "IOSurfaceUnlock");
+ sIncrementUseCount =
+ GET_IOSYM(sIncrementUseCount, "IOSurfaceIncrementUseCount");
+ sDecrementUseCount =
+ GET_IOSYM(sDecrementUseCount, "IOSurfaceDecrementUseCount");
+ sGetBaseAddress = GET_IOSYM(sGetBaseAddress, "IOSurfaceGetBaseAddress");
+ sGetBaseAddressOfPlane =
+ GET_IOSYM(sGetBaseAddressOfPlane, "IOSurfaceGetBaseAddressOfPlane");
+ sPlaneCount = GET_IOSYM(sPlaneCount, "IOSurfaceGetPlaneCount");
+ sPixelFormat = GET_IOSYM(sPixelFormat, "IOSurfaceGetPixelFormat");
+
+ sTexImage = GET_CGLSYM(sTexImage, "CGLTexImageIOSurface2D");
+ sCGContextGetTypePtr = (unsigned int (*)(CGContext*))dlsym(RTLD_DEFAULT, "CGContextGetType");
+
+ sCVPixelBufferGetIOSurface =
+ GET_CVSYM(sCVPixelBufferGetIOSurface, "CVPixelBufferGetIOSurface");
+
+ // Optional symbols
+ sIOSurfaceContextCreate = GET_CGSYM(sIOSurfaceContextCreate, "CGIOSurfaceContextCreate");
+ sIOSurfaceContextCreateImage = GET_CGSYM(sIOSurfaceContextCreateImage, "CGIOSurfaceContextCreateImage");
+ sIOSurfaceContextGetSurface = GET_CGSYM(sIOSurfaceContextGetSurface, "CGIOSurfaceContextGetSurface");
+
+ if (!sCreate || !sGetID || !sLookup || !sTexImage || !sGetBaseAddress ||
+ !sGetBaseAddressOfPlane || !sPlaneCount ||
+ !kPropWidth || !kPropHeight || !kPropBytesPerElem || !kPropIsGlobal ||
+ !sLock || !sUnlock || !sIncrementUseCount || !sDecrementUseCount ||
+ !sWidth || !sHeight || !kPropBytesPerRow ||
+ !sBytesPerRow || !sGetPropertyMaximum || !sCVPixelBufferGetIOSurface) {
+ CloseLibrary();
+ }
+}
+
+void MacIOSurfaceLib::CloseLibrary() {
+ if (sIOSurfaceFramework) {
+ dlclose(sIOSurfaceFramework);
+ }
+ if (sOpenGLFramework) {
+ dlclose(sOpenGLFramework);
+ }
+ if (sCoreVideoFramework) {
+ dlclose(sCoreVideoFramework);
+ }
+ sIOSurfaceFramework = nullptr;
+ sOpenGLFramework = nullptr;
+ sCoreVideoFramework = nullptr;
+}
+
+MacIOSurface::MacIOSurface(const void* aIOSurfacePtr,
+ double aContentsScaleFactor, bool aHasAlpha)
+ : mIOSurfacePtr(aIOSurfacePtr)
+ , mContentsScaleFactor(aContentsScaleFactor)
+ , mHasAlpha(aHasAlpha)
+{
+ CFRetain(mIOSurfacePtr);
+ IncrementUseCount();
+}
+
+MacIOSurface::~MacIOSurface() {
+ DecrementUseCount();
+ CFRelease(mIOSurfacePtr);
+}
+
+already_AddRefed<MacIOSurface> MacIOSurface::CreateIOSurface(int aWidth, int aHeight,
+ double aContentsScaleFactor,
+ bool aHasAlpha) {
+ if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0)
+ return nullptr;
+
+ CFMutableDictionaryRef props = ::CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 4,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ if (!props)
+ return nullptr;
+
+ MOZ_ASSERT((size_t)aWidth <= GetMaxWidth());
+ MOZ_ASSERT((size_t)aHeight <= GetMaxHeight());
+
+ int32_t bytesPerElem = 4;
+ size_t intScaleFactor = ceil(aContentsScaleFactor);
+ aWidth *= intScaleFactor;
+ aHeight *= intScaleFactor;
+ CFNumberRef cfWidth = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aWidth);
+ CFNumberRef cfHeight = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &aHeight);
+ CFNumberRef cfBytesPerElem = ::CFNumberCreate(nullptr, kCFNumberSInt32Type, &bytesPerElem);
+ ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropWidth,
+ cfWidth);
+ ::CFRelease(cfWidth);
+ ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropHeight,
+ cfHeight);
+ ::CFRelease(cfHeight);
+ ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropBytesPerElem,
+ cfBytesPerElem);
+ ::CFRelease(cfBytesPerElem);
+ ::CFDictionaryAddValue(props, MacIOSurfaceLib::kPropIsGlobal,
+ kCFBooleanTrue);
+
+ IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceCreate(props);
+ ::CFRelease(props);
+
+ if (!surfaceRef)
+ return nullptr;
+
+ RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
+ if (!ioSurface) {
+ ::CFRelease(surfaceRef);
+ return nullptr;
+ }
+
+ // Release the IOSurface because MacIOSurface retained it
+ CFRelease(surfaceRef);
+
+ return ioSurface.forget();
+}
+
+already_AddRefed<MacIOSurface> MacIOSurface::LookupSurface(IOSurfaceID aIOSurfaceID,
+ double aContentsScaleFactor,
+ bool aHasAlpha) {
+ if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0)
+ return nullptr;
+
+ IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceLookup(aIOSurfaceID);
+ if (!surfaceRef)
+ return nullptr;
+
+ RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
+ if (!ioSurface) {
+ ::CFRelease(surfaceRef);
+ return nullptr;
+ }
+
+ // Release the IOSurface because MacIOSurface retained it
+ CFRelease(surfaceRef);
+
+ return ioSurface.forget();
+}
+
+IOSurfaceID MacIOSurface::GetIOSurfaceID() {
+ return MacIOSurfaceLib::IOSurfaceGetID(mIOSurfacePtr);
+}
+
+void* MacIOSurface::GetBaseAddress() {
+ return MacIOSurfaceLib::IOSurfaceGetBaseAddress(mIOSurfacePtr);
+}
+
+void* MacIOSurface::GetBaseAddressOfPlane(size_t aPlaneIndex)
+{
+ return MacIOSurfaceLib::IOSurfaceGetBaseAddressOfPlane(mIOSurfacePtr,
+ aPlaneIndex);
+}
+
+size_t MacIOSurface::GetWidth(size_t plane) {
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ return GetDevicePixelWidth(plane) / intScaleFactor;
+}
+
+size_t MacIOSurface::GetHeight(size_t plane) {
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ return GetDevicePixelHeight(plane) / intScaleFactor;
+}
+
+size_t MacIOSurface::GetPlaneCount() {
+ return MacIOSurfaceLib::IOSurfaceGetPlaneCount(mIOSurfacePtr);
+}
+
+/*static*/ size_t MacIOSurface::GetMaxWidth() {
+ if (!MacIOSurfaceLib::isInit())
+ return -1;
+ return MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(MacIOSurfaceLib::kPropWidth);
+}
+
+/*static*/ size_t MacIOSurface::GetMaxHeight() {
+ if (!MacIOSurfaceLib::isInit())
+ return -1;
+ return MacIOSurfaceLib::IOSurfaceGetPropertyMaximum(MacIOSurfaceLib::kPropHeight);
+}
+
+size_t MacIOSurface::GetDevicePixelWidth(size_t plane) {
+ return MacIOSurfaceLib::IOSurfaceGetWidth(mIOSurfacePtr, plane);
+}
+
+size_t MacIOSurface::GetDevicePixelHeight(size_t plane) {
+ return MacIOSurfaceLib::IOSurfaceGetHeight(mIOSurfacePtr, plane);
+}
+
+size_t MacIOSurface::GetBytesPerRow(size_t plane) {
+ return MacIOSurfaceLib::IOSurfaceGetBytesPerRow(mIOSurfacePtr, plane);
+}
+
+OSType MacIOSurface::GetPixelFormat() {
+ return MacIOSurfaceLib::IOSurfaceGetPixelFormat(mIOSurfacePtr);
+}
+
+void MacIOSurface::IncrementUseCount() {
+ MacIOSurfaceLib::IOSurfaceIncrementUseCount(mIOSurfacePtr);
+}
+
+void MacIOSurface::DecrementUseCount() {
+ MacIOSurfaceLib::IOSurfaceDecrementUseCount(mIOSurfacePtr);
+}
+
+#define READ_ONLY 0x1
+void MacIOSurface::Lock(bool aReadOnly) {
+ MacIOSurfaceLib::IOSurfaceLock(mIOSurfacePtr, aReadOnly ? READ_ONLY : 0, nullptr);
+}
+
+void MacIOSurface::Unlock(bool aReadOnly) {
+ MacIOSurfaceLib::IOSurfaceUnlock(mIOSurfacePtr, aReadOnly ? READ_ONLY : 0, nullptr);
+}
+
+using mozilla::gfx::SourceSurface;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SurfaceFormat;
+
+void
+MacIOSurfaceBufferDeallocator(void* aClosure)
+{
+ MOZ_ASSERT(aClosure);
+
+ delete [] static_cast<unsigned char*>(aClosure);
+}
+
+already_AddRefed<SourceSurface>
+MacIOSurface::GetAsSurface() {
+ Lock();
+ size_t bytesPerRow = GetBytesPerRow();
+ size_t ioWidth = GetDevicePixelWidth();
+ size_t ioHeight = GetDevicePixelHeight();
+
+ unsigned char* ioData = (unsigned char*)GetBaseAddress();
+ unsigned char* dataCpy =
+ new unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)];
+ for (size_t i = 0; i < ioHeight; i++) {
+ memcpy(dataCpy + i * bytesPerRow,
+ ioData + i * bytesPerRow, ioWidth * 4);
+ }
+
+ Unlock();
+
+ SurfaceFormat format = HasAlpha() ? mozilla::gfx::SurfaceFormat::B8G8R8A8 :
+ mozilla::gfx::SurfaceFormat::B8G8R8X8;
+
+ RefPtr<mozilla::gfx::DataSourceSurface> surf =
+ mozilla::gfx::Factory::CreateWrappingDataSourceSurface(dataCpy,
+ bytesPerRow,
+ IntSize(ioWidth, ioHeight),
+ format,
+ &MacIOSurfaceBufferDeallocator,
+ static_cast<void*>(dataCpy));
+
+ return surf.forget();
+}
+
+SurfaceFormat
+MacIOSurface::GetFormat()
+{
+ OSType pixelFormat = GetPixelFormat();
+ if (pixelFormat == '420v') {
+ return SurfaceFormat::NV12;
+ } else if (pixelFormat == '2vuy') {
+ return SurfaceFormat::YUV422;
+ } else {
+ return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8;
+ }
+}
+
+SurfaceFormat
+MacIOSurface::GetReadFormat()
+{
+ OSType pixelFormat = GetPixelFormat();
+ if (pixelFormat == '420v') {
+ return SurfaceFormat::NV12;
+ } else if (pixelFormat == '2vuy') {
+ return SurfaceFormat::R8G8B8X8;
+ } else {
+ return HasAlpha() ? SurfaceFormat::R8G8B8A8 : SurfaceFormat::R8G8B8X8;
+ }
+}
+
+CGLError
+MacIOSurface::CGLTexImageIOSurface2D(CGLContextObj ctx, size_t plane)
+{
+ MOZ_ASSERT(plane >= 0);
+ OSType pixelFormat = GetPixelFormat();
+
+ GLenum internalFormat;
+ GLenum format;
+ GLenum type;
+ if (pixelFormat == '420v') {
+ MOZ_ASSERT(GetPlaneCount() == 2);
+ MOZ_ASSERT(plane < 2);
+
+ if (plane == 0) {
+ internalFormat = format = GL_LUMINANCE;
+ } else {
+ internalFormat = format = GL_LUMINANCE_ALPHA;
+ }
+ type = GL_UNSIGNED_BYTE;
+ } else if (pixelFormat == '2vuy') {
+ MOZ_ASSERT(plane == 0);
+
+ internalFormat = GL_RGB;
+ format = LOCAL_GL_YCBCR_422_APPLE;
+ type = GL_UNSIGNED_SHORT_8_8_APPLE;
+ } else {
+ MOZ_ASSERT(plane == 0);
+
+ internalFormat = HasAlpha() ? GL_RGBA : GL_RGB;
+ format = GL_BGRA;
+ type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ }
+ CGLError temp = MacIOSurfaceLib::CGLTexImageIOSurface2D(ctx,
+ GL_TEXTURE_RECTANGLE_ARB,
+ internalFormat,
+ GetDevicePixelWidth(plane),
+ GetDevicePixelHeight(plane),
+ format,
+ type,
+ mIOSurfacePtr, plane);
+ return temp;
+}
+
+static
+CGColorSpaceRef CreateSystemColorSpace() {
+ CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
+ if (!cspace) {
+ cspace = ::CGColorSpaceCreateDeviceRGB();
+ }
+ return cspace;
+}
+
+CGContextRef MacIOSurface::CreateIOSurfaceContext() {
+ CGColorSpaceRef cspace = CreateSystemColorSpace();
+ CGContextRef ref = MacIOSurfaceLib::IOSurfaceContextCreate(mIOSurfacePtr,
+ GetDevicePixelWidth(),
+ GetDevicePixelHeight(),
+ 8, 32, cspace, 0x2002);
+ ::CGColorSpaceRelease(cspace);
+ return ref;
+}
+
+CGImageRef MacIOSurface::CreateImageFromIOSurfaceContext(CGContextRef aContext) {
+ if (!MacIOSurfaceLib::isInit())
+ return nullptr;
+
+ return MacIOSurfaceLib::IOSurfaceContextCreateImage(aContext);
+}
+
+already_AddRefed<MacIOSurface> MacIOSurface::IOSurfaceContextGetSurface(CGContextRef aContext,
+ double aContentsScaleFactor,
+ bool aHasAlpha) {
+ if (!MacIOSurfaceLib::isInit() || aContentsScaleFactor <= 0)
+ return nullptr;
+
+ IOSurfacePtr surfaceRef = MacIOSurfaceLib::IOSurfaceContextGetSurface(aContext);
+ if (!surfaceRef)
+ return nullptr;
+
+ RefPtr<MacIOSurface> ioSurface = new MacIOSurface(surfaceRef, aContentsScaleFactor, aHasAlpha);
+ if (!ioSurface) {
+ ::CFRelease(surfaceRef);
+ return nullptr;
+ }
+ return ioSurface.forget();
+}
+
+
+CGContextType GetContextType(CGContextRef ref)
+{
+ if (!MacIOSurfaceLib::isInit() || !MacIOSurfaceLib::sCGContextGetTypePtr)
+ return CG_CONTEXT_TYPE_UNKNOWN;
+
+ unsigned int type = MacIOSurfaceLib::sCGContextGetTypePtr(ref);
+ if (type == CG_CONTEXT_TYPE_BITMAP) {
+ return CG_CONTEXT_TYPE_BITMAP;
+ } else if (type == CG_CONTEXT_TYPE_IOSURFACE) {
+ return CG_CONTEXT_TYPE_IOSURFACE;
+ } else {
+ return CG_CONTEXT_TYPE_UNKNOWN;
+ }
+}
+
+
diff --git a/gfx/2d/MacIOSurface.h b/gfx/2d/MacIOSurface.h
new file mode 100644
index 0000000000..e9c76e5dd8
--- /dev/null
+++ b/gfx/2d/MacIOSurface.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef MacIOSurface_h__
+#define MacIOSurface_h__
+#ifdef XP_DARWIN
+#include <QuartzCore/QuartzCore.h>
+#include <CoreVideo/CoreVideo.h>
+#include <dlfcn.h>
+
+struct _CGLContextObject;
+
+typedef _CGLContextObject* CGLContextObj;
+typedef struct CGContext* CGContextRef;
+typedef struct CGImage* CGImageRef;
+typedef uint32_t IOSurfaceID;
+
+typedef CFTypeRef IOSurfacePtr;
+typedef IOSurfacePtr (*IOSurfaceCreateFunc) (CFDictionaryRef properties);
+typedef IOSurfacePtr (*IOSurfaceLookupFunc) (uint32_t io_surface_id);
+typedef IOSurfaceID (*IOSurfaceGetIDFunc)(IOSurfacePtr io_surface);
+typedef void (*IOSurfaceVoidFunc)(IOSurfacePtr io_surface);
+typedef IOReturn (*IOSurfaceLockFunc)(IOSurfacePtr io_surface, uint32_t options,
+ uint32_t *seed);
+typedef IOReturn (*IOSurfaceUnlockFunc)(IOSurfacePtr io_surface,
+ uint32_t options, uint32_t *seed);
+typedef void* (*IOSurfaceGetBaseAddressFunc)(IOSurfacePtr io_surface);
+typedef void* (*IOSurfaceGetBaseAddressOfPlaneFunc)(IOSurfacePtr io_surface,
+ size_t planeIndex);
+typedef size_t (*IOSurfaceSizeTFunc)(IOSurfacePtr io_surface);
+typedef size_t (*IOSurfaceSizePlaneTFunc)(IOSurfacePtr io_surface, size_t plane);
+typedef size_t (*IOSurfaceGetPropertyMaximumFunc) (CFStringRef property);
+typedef CGLError (*CGLTexImageIOSurface2DFunc) (CGLContextObj ctxt,
+ GLenum target, GLenum internalFormat,
+ GLsizei width, GLsizei height,
+ GLenum format, GLenum type,
+ IOSurfacePtr ioSurface, GLuint plane);
+typedef CGContextRef (*IOSurfaceContextCreateFunc)(CFTypeRef io_surface,
+ unsigned width, unsigned height,
+ unsigned bitsPerComponent, unsigned bytes,
+ CGColorSpaceRef colorSpace, CGBitmapInfo bitmapInfo);
+typedef CGImageRef (*IOSurfaceContextCreateImageFunc)(CGContextRef ref);
+typedef IOSurfacePtr (*IOSurfaceContextGetSurfaceFunc)(CGContextRef ref);
+
+typedef IOSurfacePtr (*CVPixelBufferGetIOSurfaceFunc)(
+ CVPixelBufferRef pixelBuffer);
+
+typedef OSType (*IOSurfacePixelFormatFunc)(IOSurfacePtr io_surface);
+
+#ifdef XP_MACOSX
+#import <OpenGL/OpenGL.h>
+#else
+#import <OpenGLES/ES2/gl.h>
+#endif
+
+#include "2D.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/RefCounted.h"
+
+enum CGContextType {
+ CG_CONTEXT_TYPE_UNKNOWN = 0,
+ // These are found by inspection, it's possible they could be changed
+ CG_CONTEXT_TYPE_BITMAP = 4,
+ CG_CONTEXT_TYPE_IOSURFACE = 8
+};
+
+CGContextType GetContextType(CGContextRef ref);
+
+class MacIOSurface final : public mozilla::external::AtomicRefCounted<MacIOSurface> {
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(MacIOSurface)
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ // The usage count of the IOSurface is increased by 1 during the lifetime
+ // of the MacIOSurface instance.
+ // MacIOSurface holds a reference to the corresponding IOSurface.
+
+ static already_AddRefed<MacIOSurface> CreateIOSurface(int aWidth, int aHeight,
+ double aContentsScaleFactor = 1.0,
+ bool aHasAlpha = true);
+ static void ReleaseIOSurface(MacIOSurface *aIOSurface);
+ static already_AddRefed<MacIOSurface> LookupSurface(IOSurfaceID aSurfaceID,
+ double aContentsScaleFactor = 1.0,
+ bool aHasAlpha = true);
+
+ explicit MacIOSurface(const void *aIOSurfacePtr,
+ double aContentsScaleFactor = 1.0,
+ bool aHasAlpha = true);
+ ~MacIOSurface();
+ IOSurfaceID GetIOSurfaceID();
+ void *GetBaseAddress();
+ void *GetBaseAddressOfPlane(size_t planeIndex);
+ size_t GetPlaneCount();
+ OSType GetPixelFormat();
+ // GetWidth() and GetHeight() return values in "display pixels". A
+ // "display pixel" is the smallest fully addressable part of a display.
+ // But in HiDPI modes each "display pixel" corresponds to more than one
+ // device pixel. Use GetDevicePixel**() to get device pixels.
+ size_t GetWidth(size_t plane = 0);
+ size_t GetHeight(size_t plane = 0);
+ double GetContentsScaleFactor() { return mContentsScaleFactor; }
+ size_t GetDevicePixelWidth(size_t plane = 0);
+ size_t GetDevicePixelHeight(size_t plane = 0);
+ size_t GetBytesPerRow(size_t plane = 0);
+ void Lock(bool aReadOnly = true);
+ void Unlock(bool aReadOnly = true);
+ void IncrementUseCount();
+ void DecrementUseCount();
+ bool HasAlpha() { return mHasAlpha; }
+ mozilla::gfx::SurfaceFormat GetFormat();
+ mozilla::gfx::SurfaceFormat GetReadFormat();
+
+ // We would like to forward declare NSOpenGLContext, but it is an @interface
+ // and this file is also used from c++, so we use a void *.
+ CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt, size_t plane = 0);
+ already_AddRefed<SourceSurface> GetAsSurface();
+ CGContextRef CreateIOSurfaceContext();
+
+ // FIXME This doesn't really belong here
+ static CGImageRef CreateImageFromIOSurfaceContext(CGContextRef aContext);
+ static already_AddRefed<MacIOSurface> IOSurfaceContextGetSurface(CGContextRef aContext,
+ double aContentsScaleFactor = 1.0,
+ bool aHasAlpha = true);
+ static size_t GetMaxWidth();
+ static size_t GetMaxHeight();
+
+private:
+ friend class nsCARenderer;
+ const void* mIOSurfacePtr;
+ double mContentsScaleFactor;
+ bool mHasAlpha;
+};
+
+class MacIOSurfaceLib {
+public:
+ MacIOSurfaceLib() = delete;
+ static void *sIOSurfaceFramework;
+ static void *sOpenGLFramework;
+ static void *sCoreGraphicsFramework;
+ static void *sCoreVideoFramework;
+ static bool isLoaded;
+ static IOSurfaceCreateFunc sCreate;
+ static IOSurfaceGetIDFunc sGetID;
+ static IOSurfaceLookupFunc sLookup;
+ static IOSurfaceGetBaseAddressFunc sGetBaseAddress;
+ static IOSurfaceGetBaseAddressOfPlaneFunc sGetBaseAddressOfPlane;
+ static IOSurfaceSizeTFunc sPlaneCount;
+ static IOSurfaceLockFunc sLock;
+ static IOSurfaceUnlockFunc sUnlock;
+ static IOSurfaceVoidFunc sIncrementUseCount;
+ static IOSurfaceVoidFunc sDecrementUseCount;
+ static IOSurfaceSizePlaneTFunc sWidth;
+ static IOSurfaceSizePlaneTFunc sHeight;
+ static IOSurfaceSizePlaneTFunc sBytesPerRow;
+ static IOSurfaceGetPropertyMaximumFunc sGetPropertyMaximum;
+ static CGLTexImageIOSurface2DFunc sTexImage;
+ static IOSurfaceContextCreateFunc sIOSurfaceContextCreate;
+ static IOSurfaceContextCreateImageFunc sIOSurfaceContextCreateImage;
+ static IOSurfaceContextGetSurfaceFunc sIOSurfaceContextGetSurface;
+ static CVPixelBufferGetIOSurfaceFunc sCVPixelBufferGetIOSurface;
+ static IOSurfacePixelFormatFunc sPixelFormat;
+ static CFStringRef kPropWidth;
+ static CFStringRef kPropHeight;
+ static CFStringRef kPropBytesPerElem;
+ static CFStringRef kPropBytesPerRow;
+ static CFStringRef kPropIsGlobal;
+
+ static bool isInit();
+ static CFStringRef GetIOConst(const char* symbole);
+ static IOSurfacePtr IOSurfaceCreate(CFDictionaryRef properties);
+ static IOSurfacePtr IOSurfaceLookup(IOSurfaceID aIOSurfaceID);
+ static IOSurfaceID IOSurfaceGetID(IOSurfacePtr aIOSurfacePtr);
+ static void* IOSurfaceGetBaseAddress(IOSurfacePtr aIOSurfacePtr);
+ static void* IOSurfaceGetBaseAddressOfPlane(IOSurfacePtr aIOSurfacePtr,
+ size_t aPlaneIndex);
+ static size_t IOSurfaceGetPlaneCount(IOSurfacePtr aIOSurfacePtr);
+ static size_t IOSurfaceGetWidth(IOSurfacePtr aIOSurfacePtr, size_t plane);
+ static size_t IOSurfaceGetHeight(IOSurfacePtr aIOSurfacePtr, size_t plane);
+ static size_t IOSurfaceGetBytesPerRow(IOSurfacePtr aIOSurfacePtr, size_t plane);
+ static size_t IOSurfaceGetPropertyMaximum(CFStringRef property);
+ static IOReturn IOSurfaceLock(IOSurfacePtr aIOSurfacePtr,
+ uint32_t options, uint32_t *seed);
+ static IOReturn IOSurfaceUnlock(IOSurfacePtr aIOSurfacePtr,
+ uint32_t options, uint32_t *seed);
+ static void IOSurfaceIncrementUseCount(IOSurfacePtr aIOSurfacePtr);
+ static void IOSurfaceDecrementUseCount(IOSurfacePtr aIOSurfacePtr);
+ static CGLError CGLTexImageIOSurface2D(CGLContextObj ctxt,
+ GLenum target, GLenum internalFormat,
+ GLsizei width, GLsizei height,
+ GLenum format, GLenum type,
+ IOSurfacePtr ioSurface, GLuint plane);
+ static CGContextRef IOSurfaceContextCreate(IOSurfacePtr aIOSurfacePtr,
+ unsigned aWidth, unsigned aHeight,
+ unsigned aBitsPerCompoent, unsigned aBytes,
+ CGColorSpaceRef aColorSpace, CGBitmapInfo bitmapInfo);
+ static CGImageRef IOSurfaceContextCreateImage(CGContextRef ref);
+ static IOSurfacePtr IOSurfaceContextGetSurface(CGContextRef ref);
+ static IOSurfacePtr CVPixelBufferGetIOSurface(CVPixelBufferRef apixelBuffer);
+ static OSType IOSurfaceGetPixelFormat(IOSurfacePtr aIOSurfacePtr);
+ static unsigned int (*sCGContextGetTypePtr) (CGContextRef);
+ static void LoadLibrary();
+ static void CloseLibrary();
+
+ // Static deconstructor
+ static class LibraryUnloader {
+ public:
+ ~LibraryUnloader() {
+ CloseLibrary();
+ }
+ } sLibraryUnloader;
+};
+
+#endif
+#endif
diff --git a/gfx/2d/NativeFontResourceMac.cpp b/gfx/2d/NativeFontResourceMac.cpp
new file mode 100644
index 0000000000..5747d737ef
--- /dev/null
+++ b/gfx/2d/NativeFontResourceMac.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; 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 "NativeFontResourceMac.h"
+#include "Types.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/gfx/Logging.h"
+
+#ifdef MOZ_WIDGET_UIKIT
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+/* static */
+already_AddRefed<NativeFontResourceMac>
+NativeFontResourceMac::Create(uint8_t *aFontData, uint32_t aDataLength)
+{
+ // copy font data
+ CFDataRef data = CFDataCreate(kCFAllocatorDefault, aFontData, aDataLength);
+ if (!data) {
+ return nullptr;
+ }
+
+ // create a provider
+ CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
+
+ // release our reference to the CFData, provider keeps it alive
+ CFRelease(data);
+
+ // create the font object
+ CGFontRef fontRef = CGFontCreateWithDataProvider(provider);
+
+ // release our reference, font will keep it alive as long as needed
+ CGDataProviderRelease(provider);
+
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ // passes ownership of fontRef to the NativeFontResourceMac instance
+ RefPtr<NativeFontResourceMac> fontResource =
+ new NativeFontResourceMac(fontRef);
+
+ return fontResource.forget();
+}
+
+already_AddRefed<ScaledFont>
+NativeFontResourceMac::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
+ const uint8_t* aInstanceData, uint32_t aInstanceDataLength)
+{
+ RefPtr<ScaledFontBase> scaledFont = new ScaledFontMac(mFontRef, aGlyphSize);
+
+ if (!scaledFont->PopulateCairoScaledFont()) {
+ gfxWarning() << "Unable to create cairo scaled Mac font.";
+ return nullptr;
+ }
+
+ return scaledFont.forget();
+}
+
+} // gfx
+} // mozilla
diff --git a/gfx/2d/NativeFontResourceMac.h b/gfx/2d/NativeFontResourceMac.h
new file mode 100644
index 0000000000..bba935bc54
--- /dev/null
+++ b/gfx/2d/NativeFontResourceMac.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_gfx_NativeFontResourceMac_h
+#define mozilla_gfx_NativeFontResourceMac_h
+
+#include "2D.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "ScaledFontMac.h"
+
+namespace mozilla {
+namespace gfx {
+
+class NativeFontResourceMac final : public NativeFontResource
+{
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(NativeFontResourceMac)
+
+ static already_AddRefed<NativeFontResourceMac>
+ Create(uint8_t *aFontData, uint32_t aDataLength);
+
+ already_AddRefed<ScaledFont>
+ CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
+ const uint8_t* aInstanceData, uint32_t aInstanceDataLength) final;
+
+ ~NativeFontResourceMac()
+ {
+ CFRelease(mFontRef);
+ }
+
+private:
+ explicit NativeFontResourceMac(CGFontRef aFontRef) : mFontRef(aFontRef) {}
+
+ CGFontRef mFontRef;
+};
+
+} // gfx
+} // mozilla
+
+#endif // mozilla_gfx_NativeFontResourceMac_h
diff --git a/gfx/2d/PathCG.cpp b/gfx/2d/PathCG.cpp
new file mode 100644
index 0000000000..4d70b2607f
--- /dev/null
+++ b/gfx/2d/PathCG.cpp
@@ -0,0 +1,435 @@
+/* -*- 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 "PathCG.h"
+#include <math.h>
+#include "Logging.h"
+#include "PathHelpers.h"
+
+namespace mozilla {
+namespace gfx {
+
+static inline Rect
+CGRectToRect(CGRect rect)
+{
+ return Rect(rect.origin.x,
+ rect.origin.y,
+ rect.size.width,
+ rect.size.height);
+}
+
+static inline Point
+CGPointToPoint(CGPoint point)
+{
+ return Point(point.x, point.y);
+}
+
+static inline void
+SetStrokeOptions(CGContextRef cg, const StrokeOptions &aStrokeOptions)
+{
+ switch (aStrokeOptions.mLineCap)
+ {
+ case CapStyle::BUTT:
+ CGContextSetLineCap(cg, kCGLineCapButt);
+ break;
+ case CapStyle::ROUND:
+ CGContextSetLineCap(cg, kCGLineCapRound);
+ break;
+ case CapStyle::SQUARE:
+ CGContextSetLineCap(cg, kCGLineCapSquare);
+ break;
+ }
+
+ switch (aStrokeOptions.mLineJoin)
+ {
+ case JoinStyle::BEVEL:
+ CGContextSetLineJoin(cg, kCGLineJoinBevel);
+ break;
+ case JoinStyle::ROUND:
+ CGContextSetLineJoin(cg, kCGLineJoinRound);
+ break;
+ case JoinStyle::MITER:
+ case JoinStyle::MITER_OR_BEVEL:
+ CGContextSetLineJoin(cg, kCGLineJoinMiter);
+ break;
+ }
+
+ CGContextSetLineWidth(cg, aStrokeOptions.mLineWidth);
+ CGContextSetMiterLimit(cg, aStrokeOptions.mMiterLimit);
+
+ // XXX: rename mDashLength to dashLength
+ if (aStrokeOptions.mDashLength > 0) {
+ // we use a regular array instead of a std::vector here because we don't want to leak the <vector> include
+ CGFloat *dashes = new CGFloat[aStrokeOptions.mDashLength];
+ for (size_t i=0; i<aStrokeOptions.mDashLength; i++) {
+ dashes[i] = aStrokeOptions.mDashPattern[i];
+ }
+ CGContextSetLineDash(cg, aStrokeOptions.mDashOffset, dashes, aStrokeOptions.mDashLength);
+ delete[] dashes;
+ }
+}
+
+static inline CGAffineTransform
+GfxMatrixToCGAffineTransform(const Matrix &m)
+{
+ CGAffineTransform t;
+ t.a = m._11;
+ t.b = m._12;
+ t.c = m._21;
+ t.d = m._22;
+ t.tx = m._31;
+ t.ty = m._32;
+ return t;
+}
+
+PathBuilderCG::~PathBuilderCG()
+{
+ CGPathRelease(mCGPath);
+}
+
+void
+PathBuilderCG::MoveTo(const Point &aPoint)
+{
+ if (!aPoint.IsFinite()) {
+ return;
+ }
+ CGPathMoveToPoint(mCGPath, nullptr, aPoint.x, aPoint.y);
+}
+
+void
+PathBuilderCG::LineTo(const Point &aPoint)
+{
+ if (!aPoint.IsFinite()) {
+ return;
+ }
+
+ if (CGPathIsEmpty(mCGPath))
+ MoveTo(aPoint);
+ else
+ CGPathAddLineToPoint(mCGPath, nullptr, aPoint.x, aPoint.y);
+}
+
+void
+PathBuilderCG::BezierTo(const Point &aCP1,
+ const Point &aCP2,
+ const Point &aCP3)
+{
+ if (!aCP1.IsFinite() || !aCP2.IsFinite() || !aCP3.IsFinite()) {
+ return;
+ }
+
+ if (CGPathIsEmpty(mCGPath))
+ MoveTo(aCP1);
+ CGPathAddCurveToPoint(mCGPath, nullptr,
+ aCP1.x, aCP1.y,
+ aCP2.x, aCP2.y,
+ aCP3.x, aCP3.y);
+
+}
+
+void
+PathBuilderCG::QuadraticBezierTo(const Point &aCP1,
+ const Point &aCP2)
+{
+ if (!aCP1.IsFinite() || !aCP2.IsFinite()) {
+ return;
+ }
+
+ if (CGPathIsEmpty(mCGPath))
+ MoveTo(aCP1);
+ CGPathAddQuadCurveToPoint(mCGPath, nullptr,
+ aCP1.x, aCP1.y,
+ aCP2.x, aCP2.y);
+}
+
+void
+PathBuilderCG::Close()
+{
+ if (!CGPathIsEmpty(mCGPath))
+ CGPathCloseSubpath(mCGPath);
+}
+
+void
+PathBuilderCG::Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
+ Float aEndAngle, bool aAntiClockwise)
+{
+ if (!aOrigin.IsFinite() || !IsFinite(aRadius) ||
+ !IsFinite(aStartAngle) || !IsFinite(aEndAngle)) {
+ return;
+ }
+
+ // Disabled for now due to a CG bug when using CGPathAddArc with stroke
+ // dashing and rotation transforms that are multiples of 90 degrees. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=949661#c8
+#if 0
+ // Core Graphic's initial coordinate system is y-axis up, whereas Moz2D's is
+ // y-axis down. Core Graphics therefore considers "clockwise" to mean "sweep
+ // in the direction of decreasing angle" whereas Moz2D considers it to mean
+ // "sweep in the direction of increasing angle". In other words if this
+ // Moz2D method is instructed to sweep anti-clockwise we need to tell
+ // CGPathAddArc to sweep clockwise, and vice versa. Hence why we pass the
+ // value of aAntiClockwise directly to CGPathAddArc's "clockwise" bool
+ // parameter.
+ CGPathAddArc(mCGPath, nullptr,
+ aOrigin.x, aOrigin.y,
+ aRadius,
+ aStartAngle,
+ aEndAngle,
+ aAntiClockwise);
+#endif
+ ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
+ aAntiClockwise);
+}
+
+Point
+PathBuilderCG::CurrentPoint() const
+{
+ Point ret;
+ if (!CGPathIsEmpty(mCGPath)) {
+ CGPoint pt = CGPathGetCurrentPoint(mCGPath);
+ ret.MoveTo(pt.x, pt.y);
+ }
+ return ret;
+}
+
+void
+PathBuilderCG::EnsureActive(const Point &aPoint)
+{
+}
+
+already_AddRefed<Path>
+PathBuilderCG::Finish()
+{
+ return MakeAndAddRef<PathCG>(mCGPath, mFillRule);
+}
+
+already_AddRefed<PathBuilder>
+PathCG::CopyToBuilder(FillRule aFillRule) const
+{
+ CGMutablePathRef path = CGPathCreateMutableCopy(mPath);
+ return MakeAndAddRef<PathBuilderCG>(path, aFillRule);
+}
+
+
+
+already_AddRefed<PathBuilder>
+PathCG::TransformedCopyToBuilder(const Matrix &aTransform, FillRule aFillRule) const
+{
+ // 10.7 adds CGPathCreateMutableCopyByTransformingPath it might be faster than doing
+ // this by hand
+
+ struct TransformApplier {
+ CGMutablePathRef path;
+ CGAffineTransform transform;
+ static void
+ TranformCGPathApplierFunc(void *vinfo, const CGPathElement *element)
+ {
+ TransformApplier *info = reinterpret_cast<TransformApplier*>(vinfo);
+ switch (element->type) {
+ case kCGPathElementMoveToPoint:
+ {
+ CGPoint pt = element->points[0];
+ CGPathMoveToPoint(info->path, &info->transform, pt.x, pt.y);
+ break;
+ }
+ case kCGPathElementAddLineToPoint:
+ {
+ CGPoint pt = element->points[0];
+ CGPathAddLineToPoint(info->path, &info->transform, pt.x, pt.y);
+ break;
+ }
+ case kCGPathElementAddQuadCurveToPoint:
+ {
+ CGPoint cpt = element->points[0];
+ CGPoint pt = element->points[1];
+ CGPathAddQuadCurveToPoint(info->path, &info->transform, cpt.x, cpt.y, pt.x, pt.y);
+ break;
+ }
+ case kCGPathElementAddCurveToPoint:
+ {
+ CGPoint cpt1 = element->points[0];
+ CGPoint cpt2 = element->points[1];
+ CGPoint pt = element->points[2];
+ CGPathAddCurveToPoint(info->path, &info->transform, cpt1.x, cpt1.y, cpt2.x, cpt2.y, pt.x, pt.y);
+ break;
+ }
+ case kCGPathElementCloseSubpath:
+ {
+ CGPathCloseSubpath(info->path);
+ break;
+ }
+ }
+ }
+ };
+
+ TransformApplier ta;
+ ta.path = CGPathCreateMutable();
+ ta.transform = GfxMatrixToCGAffineTransform(aTransform);
+
+ CGPathApply(mPath, &ta, TransformApplier::TranformCGPathApplierFunc);
+ return MakeAndAddRef<PathBuilderCG>(ta.path, aFillRule);
+}
+
+static void
+StreamPathToSinkApplierFunc(void *vinfo, const CGPathElement *element)
+{
+ PathSink *sink = reinterpret_cast<PathSink*>(vinfo);
+ switch (element->type) {
+ case kCGPathElementMoveToPoint:
+ {
+ CGPoint pt = element->points[0];
+ sink->MoveTo(CGPointToPoint(pt));
+ break;
+ }
+ case kCGPathElementAddLineToPoint:
+ {
+ CGPoint pt = element->points[0];
+ sink->LineTo(CGPointToPoint(pt));
+ break;
+ }
+ case kCGPathElementAddQuadCurveToPoint:
+ {
+ CGPoint cpt = element->points[0];
+ CGPoint pt = element->points[1];
+ sink->QuadraticBezierTo(CGPointToPoint(cpt),
+ CGPointToPoint(pt));
+ break;
+ }
+ case kCGPathElementAddCurveToPoint:
+ {
+ CGPoint cpt1 = element->points[0];
+ CGPoint cpt2 = element->points[1];
+ CGPoint pt = element->points[2];
+ sink->BezierTo(CGPointToPoint(cpt1),
+ CGPointToPoint(cpt2),
+ CGPointToPoint(pt));
+ break;
+ }
+ case kCGPathElementCloseSubpath:
+ {
+ sink->Close();
+ break;
+ }
+ }
+}
+
+void
+PathCG::StreamToSink(PathSink *aSink) const
+{
+ CGPathApply(mPath, aSink, StreamPathToSinkApplierFunc);
+}
+
+bool
+PathCG::ContainsPoint(const Point &aPoint, const Matrix &aTransform) const
+{
+ Matrix inverse = aTransform;
+ inverse.Invert();
+ Point transformedPoint = inverse.TransformPoint(aPoint);
+ // We could probably drop the input transform and just transform the point at the caller?
+ CGPoint point = {transformedPoint.x, transformedPoint.y};
+
+ // The transform parameter of CGPathContainsPoint doesn't seem to work properly on OS X 10.5
+ // so we transform aPoint ourselves.
+ return CGPathContainsPoint(mPath, nullptr, point, mFillRule == FillRule::FILL_EVEN_ODD);
+}
+
+static size_t
+PutBytesNull(void *info, const void *buffer, size_t count)
+{
+ return count;
+}
+
+/* The idea of a scratch context comes from WebKit */
+static CGContextRef
+CreateScratchContext()
+{
+ CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr};
+ CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks);
+ CGContextRef cg = CGPDFContextCreate(consumer, nullptr, nullptr);
+ CGDataConsumerRelease(consumer);
+ return cg;
+}
+
+static CGContextRef
+ScratchContext()
+{
+ static CGContextRef cg = CreateScratchContext();
+ return cg;
+}
+
+bool
+PathCG::StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
+ const Point &aPoint,
+ const Matrix &aTransform) const
+{
+ Matrix inverse = aTransform;
+ inverse.Invert();
+ Point transformedPoint = inverse.TransformPoint(aPoint);
+ // We could probably drop the input transform and just transform the point at the caller?
+ CGPoint point = {transformedPoint.x, transformedPoint.y};
+
+ CGContextRef cg = ScratchContext();
+
+ CGContextSaveGState(cg);
+
+ CGContextBeginPath(cg);
+ CGContextAddPath(cg, mPath);
+
+ SetStrokeOptions(cg, aStrokeOptions);
+
+ CGContextReplacePathWithStrokedPath(cg);
+ CGContextRestoreGState(cg);
+
+ CGPathRef sPath = CGContextCopyPath(cg);
+ bool inStroke = CGPathContainsPoint(sPath, nullptr, point, false);
+ CGPathRelease(sPath);
+
+ return inStroke;
+}
+
+//XXX: what should these functions return for an empty path?
+// currently they return CGRectNull {inf,inf, 0, 0}
+Rect
+PathCG::GetBounds(const Matrix &aTransform) const
+{
+ //XXX: are these bounds tight enough
+ Rect bounds = CGRectToRect(CGPathGetBoundingBox(mPath));
+
+ //XXX: currently this returns the bounds of the transformed bounds
+ // this is strictly looser than the bounds of the transformed path
+ return aTransform.TransformBounds(bounds);
+}
+
+Rect
+PathCG::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
+ const Matrix &aTransform) const
+{
+ // 10.7 has CGPathCreateCopyByStrokingPath which we could use
+ // instead of this scratch context business
+ CGContextRef cg = ScratchContext();
+
+ CGContextSaveGState(cg);
+
+ CGContextBeginPath(cg);
+ CGContextAddPath(cg, mPath);
+
+ SetStrokeOptions(cg, aStrokeOptions);
+
+ CGContextReplacePathWithStrokedPath(cg);
+ Rect bounds = CGRectToRect(CGContextGetPathBoundingBox(cg));
+
+ CGContextRestoreGState(cg);
+
+ if (!bounds.IsFinite()) {
+ return Rect();
+ }
+
+ return aTransform.TransformBounds(bounds);
+}
+
+
+} // namespace gfx
+
+} // namespace mozilla
diff --git a/gfx/2d/PathCG.h b/gfx/2d/PathCG.h
new file mode 100644
index 0000000000..db609cb577
--- /dev/null
+++ b/gfx/2d/PathCG.h
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_PATHCG_H_
+#define MOZILLA_GFX_PATHCG_H_
+
+#ifdef MOZ_WIDGET_COCOA
+#include <ApplicationServices/ApplicationServices.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#endif
+
+#include "2D.h"
+
+namespace mozilla {
+namespace gfx {
+
+class PathCG;
+
+class PathBuilderCG : public PathBuilder
+{
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilderCG)
+ // absorbs a reference of aPath
+ PathBuilderCG(CGMutablePathRef aPath, FillRule aFillRule)
+ : mFillRule(aFillRule)
+ {
+ mCGPath = aPath;
+ }
+
+ explicit PathBuilderCG(FillRule aFillRule)
+ : mFillRule(aFillRule)
+ {
+ mCGPath = CGPathCreateMutable();
+ }
+
+ virtual ~PathBuilderCG();
+
+ virtual void MoveTo(const Point &aPoint);
+ virtual void LineTo(const Point &aPoint);
+ virtual void BezierTo(const Point &aCP1,
+ const Point &aCP2,
+ const Point &aCP3);
+ virtual void QuadraticBezierTo(const Point &aCP1,
+ const Point &aCP2);
+ virtual void Close();
+ virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
+ Float aEndAngle, bool aAntiClockwise = false);
+ virtual Point CurrentPoint() const;
+
+ virtual already_AddRefed<Path> Finish();
+
+ virtual BackendType GetBackendType() const { return BackendType::SKIA; }
+
+private:
+ friend class PathCG;
+ friend class ScaledFontMac;
+
+ void EnsureActive(const Point &aPoint);
+
+ CGMutablePathRef mCGPath;
+ Point mCurrentPoint;
+ Point mBeginPoint;
+ FillRule mFillRule;
+};
+
+class PathCG : public Path
+{
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathCG)
+ PathCG(CGMutablePathRef aPath, FillRule aFillRule)
+ : mPath(aPath)
+ , mFillRule(aFillRule)
+ {
+ CGPathRetain(mPath);
+ }
+ virtual ~PathCG() { CGPathRelease(mPath); }
+
+ // Paths will always return BackendType::COREGRAPHICS, but note that they
+ // are compatible with BackendType::COREGRAPHICS_ACCELERATED backend.
+ virtual BackendType GetBackendType() const { return BackendType::SKIA; }
+
+ virtual already_AddRefed<PathBuilder> CopyToBuilder(FillRule aFillRule) const;
+ virtual already_AddRefed<PathBuilder> TransformedCopyToBuilder(const Matrix &aTransform,
+ FillRule aFillRule) const;
+
+ virtual bool ContainsPoint(const Point &aPoint, const Matrix &aTransform) const;
+ virtual bool StrokeContainsPoint(const StrokeOptions &aStrokeOptions,
+ const Point &aPoint,
+ const Matrix &aTransform) const;
+ virtual Rect GetBounds(const Matrix &aTransform = Matrix()) const;
+ virtual Rect GetStrokedBounds(const StrokeOptions &aStrokeOptions,
+ const Matrix &aTransform = Matrix()) const;
+
+ virtual void StreamToSink(PathSink *aSink) const;
+
+ virtual FillRule GetFillRule() const { return mFillRule; }
+
+ CGMutablePathRef GetPath() const { return mPath; }
+
+private:
+ friend class DrawTargetCG;
+
+ CGMutablePathRef mPath;
+ Point mEndPoint;
+ FillRule mFillRule;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif
diff --git a/gfx/2d/QuartzSupport.h b/gfx/2d/QuartzSupport.h
new file mode 100644
index 0000000000..f56fcb77ca
--- /dev/null
+++ b/gfx/2d/QuartzSupport.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* 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/. */
+
+#ifndef nsCoreAnimationSupport_h__
+#define nsCoreAnimationSupport_h__
+#ifdef XP_MACOSX
+
+#import <OpenGL/OpenGL.h>
+#import <OpenGL/gl.h>
+#import "ApplicationServices/ApplicationServices.h"
+#include "gfxTypes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "nsError.h"
+
+// Get the system color space.
+CGColorSpaceRef CreateSystemColorSpace();
+
+// Manages a CARenderer
+struct _CGLContextObject;
+
+enum AllowOfflineRendererEnum { ALLOW_OFFLINE_RENDERER, DISALLOW_OFFLINE_RENDERER };
+
+class nsCARenderer : public mozilla::RefCounted<nsCARenderer> {
+public:
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(nsCARenderer)
+ nsCARenderer() : mCARenderer(nullptr), mWrapperCALayer(nullptr), mFBOTexture(0),
+ mOpenGLContext(nullptr), mCGImage(nullptr), mCGData(nullptr),
+ mIOSurface(nullptr), mFBO(0), mIOTexture(0),
+ mUnsupportedWidth(UINT32_MAX), mUnsupportedHeight(UINT32_MAX),
+ mAllowOfflineRenderer(DISALLOW_OFFLINE_RENDERER),
+ mContentsScaleFactor(1.0) {}
+ ~nsCARenderer();
+ // aWidth and aHeight are in "display pixels". A "display pixel" is the
+ // smallest fully addressable part of a display. But in HiDPI modes each
+ // "display pixel" corresponds to more than one device pixel. Multiply
+ // display pixels by aContentsScaleFactor to get device pixels.
+ nsresult SetupRenderer(void* aCALayer, int aWidth, int aHeight,
+ double aContentsScaleFactor,
+ AllowOfflineRendererEnum aAllowOfflineRenderer);
+ // aWidth and aHeight are in "display pixels". Multiply by
+ // aContentsScaleFactor to get device pixels.
+ nsresult Render(int aWidth, int aHeight,
+ double aContentsScaleFactor,
+ CGImageRef *aOutCAImage);
+ bool isInit() { return mCARenderer != nullptr; }
+ /*
+ * Render the CALayer to an IOSurface. If no IOSurface
+ * is attached then an internal pixel buffer will be
+ * used.
+ */
+ void AttachIOSurface(MacIOSurface *aSurface);
+ IOSurfaceID GetIOSurfaceID();
+ // aX, aY, aWidth and aHeight are in "display pixels". Multiply by
+ // surf->GetContentsScaleFactor() to get device pixels.
+ static nsresult DrawSurfaceToCGContext(CGContextRef aContext,
+ MacIOSurface *surf,
+ CGColorSpaceRef aColorSpace,
+ int aX, int aY,
+ size_t aWidth, size_t aHeight);
+
+ // Remove & Add the layer without destroying
+ // the renderer for fast back buffer swapping.
+ void DetachCALayer();
+ void AttachCALayer(void *aCALayer);
+#ifdef DEBUG
+ static void SaveToDisk(MacIOSurface *surf);
+#endif
+private:
+ // aWidth and aHeight are in "display pixels". Multiply by
+ // mContentsScaleFactor to get device pixels.
+ void SetBounds(int aWidth, int aHeight);
+ // aWidth and aHeight are in "display pixels". Multiply by
+ // mContentsScaleFactor to get device pixels.
+ void SetViewport(int aWidth, int aHeight);
+ void Destroy();
+
+ void *mCARenderer;
+ void *mWrapperCALayer;
+ GLuint mFBOTexture;
+ _CGLContextObject *mOpenGLContext;
+ CGImageRef mCGImage;
+ void *mCGData;
+ RefPtr<MacIOSurface> mIOSurface;
+ uint32_t mFBO;
+ uint32_t mIOTexture;
+ int mUnsupportedWidth;
+ int mUnsupportedHeight;
+ AllowOfflineRendererEnum mAllowOfflineRenderer;
+ double mContentsScaleFactor;
+};
+
+#endif // XP_MACOSX
+#endif // nsCoreAnimationSupport_h__
+
diff --git a/gfx/2d/QuartzSupport.mm b/gfx/2d/QuartzSupport.mm
new file mode 100644
index 0000000000..464db5ecee
--- /dev/null
+++ b/gfx/2d/QuartzSupport.mm
@@ -0,0 +1,625 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* 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 "QuartzSupport.h"
+#include "nsDebug.h"
+#include "MacIOSurface.h"
+#include "mozilla/Sprintf.h"
+
+#import <QuartzCore/QuartzCore.h>
+#import <AppKit/NSOpenGL.h>
+#include <dlfcn.h>
+#include "GLDefs.h"
+
+#define IOSURFACE_FRAMEWORK_PATH \
+ "/System/Library/Frameworks/IOSurface.framework/IOSurface"
+#define OPENGL_FRAMEWORK_PATH \
+ "/System/Library/Frameworks/OpenGL.framework/OpenGL"
+#define COREGRAPHICS_FRAMEWORK_PATH \
+ "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/CoreGraphics.framework/CoreGraphics"
+
+@interface CALayer (ContentsScale)
+- (double)contentsScale;
+- (void)setContentsScale:(double)scale;
+@end
+
+
+CGColorSpaceRef CreateSystemColorSpace() {
+ CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
+ if (!cspace) {
+ cspace = ::CGColorSpaceCreateDeviceRGB();
+ }
+ return cspace;
+}
+
+nsCARenderer::~nsCARenderer() {
+ Destroy();
+}
+
+void cgdata_release_callback(void *aCGData, const void *data, size_t size) {
+ if (aCGData) {
+ free(aCGData);
+ }
+}
+
+void nsCARenderer::Destroy() {
+ if (mCARenderer) {
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ // Bug 556453:
+ // Explicitly remove the layer from the renderer
+ // otherwise it does not always happen right away.
+ caRenderer.layer = nullptr;
+ [caRenderer release];
+ }
+ if (mWrapperCALayer) {
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ [wrapperLayer release];
+ }
+ if (mOpenGLContext) {
+ if (mFBO || mIOTexture || mFBOTexture) {
+ // Release these resources with the context that allocated them
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+
+ if (mFBOTexture) {
+ ::glDeleteTextures(1, &mFBOTexture);
+ }
+ if (mIOTexture) {
+ ::glDeleteTextures(1, &mIOTexture);
+ }
+ if (mFBO) {
+ ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ ::glDeleteFramebuffersEXT(1, &mFBO);
+ }
+
+ if (oldContext)
+ ::CGLSetCurrentContext(oldContext);
+ }
+ ::CGLDestroyContext((CGLContextObj)mOpenGLContext);
+ }
+ if (mCGImage) {
+ ::CGImageRelease(mCGImage);
+ }
+ // mCGData is deallocated by cgdata_release_callback
+
+ mCARenderer = nil;
+ mWrapperCALayer = nil;
+ mFBOTexture = 0;
+ mOpenGLContext = nullptr;
+ mCGImage = nullptr;
+ mIOSurface = nullptr;
+ mFBO = 0;
+ mIOTexture = 0;
+}
+
+nsresult nsCARenderer::SetupRenderer(void *aCALayer, int aWidth, int aHeight,
+ double aContentsScaleFactor,
+ AllowOfflineRendererEnum aAllowOfflineRenderer) {
+ mAllowOfflineRenderer = aAllowOfflineRenderer;
+
+ if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0)
+ return NS_ERROR_FAILURE;
+
+ if (aWidth == mUnsupportedWidth &&
+ aHeight == mUnsupportedHeight) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CGLPixelFormatAttribute attributes[] = {
+ kCGLPFAAccelerated,
+ kCGLPFADepthSize, (CGLPixelFormatAttribute)24,
+ kCGLPFAAllowOfflineRenderers,
+ (CGLPixelFormatAttribute)0
+ };
+
+ if (mAllowOfflineRenderer == DISALLOW_OFFLINE_RENDERER) {
+ attributes[3] = (CGLPixelFormatAttribute)0;
+ }
+
+ GLint screen;
+ CGLPixelFormatObj format;
+ if (::CGLChoosePixelFormat(attributes, &format, &screen) != kCGLNoError) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ if (::CGLCreateContext(format, nullptr, &mOpenGLContext) != kCGLNoError) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ ::CGLDestroyPixelFormat(format);
+
+ CARenderer* caRenderer = [[CARenderer rendererWithCGLContext:mOpenGLContext
+ options:nil] retain];
+ if (caRenderer == nil) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ CALayer* wrapperCALayer = [[CALayer layer] retain];
+ if (wrapperCALayer == nil) {
+ [caRenderer release];
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ mCARenderer = caRenderer;
+ mWrapperCALayer = wrapperCALayer;
+ caRenderer.layer = wrapperCALayer;
+ [wrapperCALayer addSublayer:(CALayer*)aCALayer];
+ mContentsScaleFactor = aContentsScaleFactor;
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ SetBounds(aWidth, aHeight);
+
+ // We target rendering to a CGImage if no shared IOSurface are given.
+ if (!mIOSurface) {
+ mCGData = malloc(aWidth*intScaleFactor*aHeight*4*intScaleFactor);
+ if (!mCGData) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ memset(mCGData, 0, aWidth*intScaleFactor*aHeight*4*intScaleFactor);
+
+ CGDataProviderRef dataProvider = nullptr;
+ dataProvider = ::CGDataProviderCreateWithData(mCGData,
+ mCGData, aHeight*intScaleFactor*aWidth*4*intScaleFactor,
+ cgdata_release_callback);
+ if (!dataProvider) {
+ cgdata_release_callback(mCGData, mCGData,
+ aHeight*intScaleFactor*aWidth*4*intScaleFactor);
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ CGColorSpaceRef colorSpace = CreateSystemColorSpace();
+
+ mCGImage = ::CGImageCreate(aWidth * intScaleFactor, aHeight * intScaleFactor,
+ 8, 32, aWidth * intScaleFactor * 4, colorSpace,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ dataProvider, nullptr, true, kCGRenderingIntentDefault);
+
+ ::CGDataProviderRelease(dataProvider);
+ ::CGColorSpaceRelease(colorSpace);
+ if (!mCGImage) {
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+
+ if (mIOSurface) {
+ // Create the IOSurface mapped texture.
+ ::glGenTextures(1, &mIOTexture);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mIOTexture);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ MacIOSurfaceLib::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB,
+ GL_RGBA, aWidth * intScaleFactor,
+ aHeight * intScaleFactor,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ mIOSurface->mIOSurfacePtr, 0);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
+ } else {
+ ::glGenTextures(1, &mFBOTexture);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mFBOTexture);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ ::glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
+ }
+
+ // Create the fbo
+ ::glGenFramebuffersEXT(1, &mFBO);
+ ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO);
+ if (mIOSurface) {
+ ::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, mIOTexture, 0);
+ } else {
+ ::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_RECTANGLE_ARB, mFBOTexture, 0);
+ }
+
+
+ // Make sure that the Framebuffer configuration is supported on the client machine
+ GLenum fboStatus;
+ fboStatus = ::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+ if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT) {
+ NS_ERROR("FBO not supported");
+ if (oldContext)
+ ::CGLSetCurrentContext(oldContext);
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ return NS_ERROR_FAILURE;
+ }
+
+ SetViewport(aWidth, aHeight);
+
+ GLenum result = ::glGetError();
+ if (result != GL_NO_ERROR) {
+ NS_ERROR("Unexpected OpenGL Error");
+ mUnsupportedWidth = aWidth;
+ mUnsupportedHeight = aHeight;
+ Destroy();
+ if (oldContext)
+ ::CGLSetCurrentContext(oldContext);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (oldContext)
+ ::CGLSetCurrentContext(oldContext);
+
+ return NS_OK;
+}
+
+void nsCARenderer::SetBounds(int aWidth, int aHeight) {
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* pluginLayer = (CALayer*) [sublayers objectAtIndex:0];
+
+ // Create a transaction and disable animations
+ // to make the position update instant.
+ [CATransaction begin];
+ NSMutableDictionary *newActions =
+ [[NSMutableDictionary alloc] initWithObjectsAndKeys:
+ [NSNull null], @"onOrderIn",
+ [NSNull null], @"onOrderOut",
+ [NSNull null], @"sublayers",
+ [NSNull null], @"contents",
+ [NSNull null], @"position",
+ [NSNull null], @"bounds",
+ nil];
+ wrapperLayer.actions = newActions;
+ [newActions release];
+
+ // If we're in HiDPI mode, mContentsScaleFactor will (presumably) be 2.0.
+ // For some reason, to make things work properly in HiDPI mode we need to
+ // make caRenderer's 'bounds' and 'layer' different sizes -- to set 'bounds'
+ // to the size of 'layer's backing store. And to avoid this possibly
+ // confusing the plugin, we need to hide it's effects from the plugin by
+ // making pluginLayer (usually the CALayer* provided by the plugin) a
+ // sublayer of our own wrapperLayer (see bug 829284).
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ [CATransaction setValue: [NSNumber numberWithFloat:0.0f] forKey: kCATransactionAnimationDuration];
+ [CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
+ [wrapperLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)];
+ [wrapperLayer setPosition:CGPointMake(aWidth/2.0, aHeight/2.0)];
+ [pluginLayer setBounds:CGRectMake(0, 0, aWidth, aHeight)];
+ [pluginLayer setFrame:CGRectMake(0, 0, aWidth, aHeight)];
+ caRenderer.bounds = CGRectMake(0, 0, aWidth * intScaleFactor, aHeight * intScaleFactor);
+ if (mContentsScaleFactor != 1.0) {
+ CGAffineTransform affineTransform = [wrapperLayer affineTransform];
+ affineTransform.a = mContentsScaleFactor;
+ affineTransform.d = mContentsScaleFactor;
+ affineTransform.tx = ((double)aWidth)/mContentsScaleFactor;
+ affineTransform.ty = ((double)aHeight)/mContentsScaleFactor;
+ [wrapperLayer setAffineTransform:affineTransform];
+ } else {
+ // These settings are the default values. But they might have been
+ // changed as above if we were previously running in a HiDPI mode
+ // (i.e. if we just switched from that to a non-HiDPI mode).
+ [wrapperLayer setAffineTransform:CGAffineTransformIdentity];
+ }
+ [CATransaction commit];
+}
+
+void nsCARenderer::SetViewport(int aWidth, int aHeight) {
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ aWidth *= intScaleFactor;
+ aHeight *= intScaleFactor;
+
+ ::glViewport(0.0, 0.0, aWidth, aHeight);
+ ::glMatrixMode(GL_PROJECTION);
+ ::glLoadIdentity();
+ ::glOrtho (0.0, aWidth, 0.0, aHeight, -1, 1);
+
+ // Render upside down to speed up CGContextDrawImage
+ ::glTranslatef(0.0f, aHeight, 0.0);
+ ::glScalef(1.0, -1.0, 1.0);
+}
+
+void nsCARenderer::AttachIOSurface(MacIOSurface *aSurface) {
+ if (mIOSurface &&
+ aSurface->GetIOSurfaceID() == mIOSurface->GetIOSurfaceID()) {
+ return;
+ }
+
+ mIOSurface = aSurface;
+
+ // Update the framebuffer and viewport
+ if (mCARenderer) {
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ size_t intScaleFactor = ceil(mContentsScaleFactor);
+ int width = caRenderer.bounds.size.width / intScaleFactor;
+ int height = caRenderer.bounds.size.height / intScaleFactor;
+
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, mIOTexture);
+ MacIOSurfaceLib::CGLTexImageIOSurface2D(mOpenGLContext, GL_TEXTURE_RECTANGLE_ARB,
+ GL_RGBA, mIOSurface->GetDevicePixelWidth(),
+ mIOSurface->GetDevicePixelHeight(),
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ mIOSurface->mIOSurfacePtr, 0);
+ ::glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);
+
+ // Rebind the FBO to make it live
+ ::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO);
+
+ if (static_cast<int>(mIOSurface->GetWidth()) != width ||
+ static_cast<int>(mIOSurface->GetHeight()) != height) {
+ width = mIOSurface->GetWidth();
+ height = mIOSurface->GetHeight();
+ SetBounds(width, height);
+ SetViewport(width, height);
+ }
+
+ if (oldContext) {
+ ::CGLSetCurrentContext(oldContext);
+ }
+ }
+}
+
+IOSurfaceID nsCARenderer::GetIOSurfaceID() {
+ if (!mIOSurface) {
+ return 0;
+ }
+
+ return mIOSurface->GetIOSurfaceID();
+}
+
+nsresult nsCARenderer::Render(int aWidth, int aHeight,
+ double aContentsScaleFactor,
+ CGImageRef *aOutCGImage) {
+ if (!aOutCGImage && !mIOSurface) {
+ NS_ERROR("No target destination for rendering");
+ } else if (aOutCGImage) {
+ // We are expected to return a CGImageRef, we will set
+ // it to nullptr in case we fail before the image is ready.
+ *aOutCGImage = nullptr;
+ }
+
+ if (aWidth == 0 || aHeight == 0 || aContentsScaleFactor <= 0)
+ return NS_OK;
+
+ if (!mCARenderer || !mWrapperCALayer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CARenderer* caRenderer = (CARenderer*)mCARenderer;
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ size_t intScaleFactor = ceil(aContentsScaleFactor);
+ int renderer_width = caRenderer.bounds.size.width / intScaleFactor;
+ int renderer_height = caRenderer.bounds.size.height / intScaleFactor;
+
+ if (renderer_width != aWidth || renderer_height != aHeight ||
+ mContentsScaleFactor != aContentsScaleFactor) {
+ // XXX: This should be optimized to not rescale the buffer
+ // if we are resizing down.
+ // caLayer may be the CALayer* provided by the plugin, so we need to
+ // preserve it across the call to Destroy().
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* caLayer = (CALayer*) [sublayers objectAtIndex:0];
+ // mIOSurface is set by AttachIOSurface(), not by SetupRenderer(). So
+ // since it may have been set by a prior call to AttachIOSurface(), we
+ // need to preserve it across the call to Destroy().
+ RefPtr<MacIOSurface> ioSurface = mIOSurface;
+ Destroy();
+ mIOSurface = ioSurface;
+ if (SetupRenderer(caLayer, aWidth, aHeight, aContentsScaleFactor,
+ mAllowOfflineRenderer) != NS_OK) {
+ return NS_ERROR_FAILURE;
+ }
+
+ caRenderer = (CARenderer*)mCARenderer;
+ }
+
+ CGLContextObj oldContext = ::CGLGetCurrentContext();
+ ::CGLSetCurrentContext(mOpenGLContext);
+ if (!mIOSurface) {
+ // If no shared IOSurface is given render to our own
+ // texture for readback.
+ ::glGenTextures(1, &mFBOTexture);
+ }
+
+ GLenum result = ::glGetError();
+ if (result != GL_NO_ERROR) {
+ NS_ERROR("Unexpected OpenGL Error");
+ Destroy();
+ if (oldContext)
+ ::CGLSetCurrentContext(oldContext);
+ return NS_ERROR_FAILURE;
+ }
+
+ ::glClearColor(0.0, 0.0, 0.0, 0.0);
+ ::glClear(GL_COLOR_BUFFER_BIT);
+
+ [CATransaction commit];
+ double caTime = ::CACurrentMediaTime();
+ [caRenderer beginFrameAtTime:caTime timeStamp:nullptr];
+ [caRenderer addUpdateRect:CGRectMake(0,0, aWidth * intScaleFactor,
+ aHeight * intScaleFactor)];
+ [caRenderer render];
+ [caRenderer endFrame];
+
+ // Read the data back either to the IOSurface or mCGImage.
+ if (mIOSurface) {
+ ::glFlush();
+ } else {
+ ::glPixelStorei(GL_PACK_ALIGNMENT, 4);
+ ::glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ ::glPixelStorei(GL_PACK_SKIP_ROWS, 0);
+ ::glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
+
+ ::glReadPixels(0.0f, 0.0f, aWidth * intScaleFactor,
+ aHeight * intScaleFactor,
+ GL_BGRA, GL_UNSIGNED_BYTE,
+ mCGData);
+
+ *aOutCGImage = mCGImage;
+ }
+
+ if (oldContext) {
+ ::CGLSetCurrentContext(oldContext);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsCARenderer::DrawSurfaceToCGContext(CGContextRef aContext,
+ MacIOSurface *surf,
+ CGColorSpaceRef aColorSpace,
+ int aX, int aY,
+ size_t aWidth, size_t aHeight) {
+ surf->Lock();
+ size_t bytesPerRow = surf->GetBytesPerRow();
+ size_t ioWidth = surf->GetWidth();
+ size_t ioHeight = surf->GetHeight();
+
+ // We get rendering glitches if we use a width/height that falls
+ // outside of the IOSurface.
+ if (aWidth + aX > ioWidth)
+ aWidth = ioWidth - aX;
+ if (aHeight + aY > ioHeight)
+ aHeight = ioHeight - aY;
+
+ if (aX < 0 || static_cast<size_t>(aX) >= ioWidth ||
+ aY < 0 || static_cast<size_t>(aY) >= ioHeight) {
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+
+ void* ioData = surf->GetBaseAddress();
+ double scaleFactor = surf->GetContentsScaleFactor();
+ size_t intScaleFactor = ceil(surf->GetContentsScaleFactor());
+ CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(ioData,
+ ioData, ioHeight*intScaleFactor*(bytesPerRow)*4,
+ nullptr); //No release callback
+ if (!dataProvider) {
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+
+ CGImageRef cgImage = ::CGImageCreate(ioWidth * intScaleFactor,
+ ioHeight * intScaleFactor, 8, 32, bytesPerRow, aColorSpace,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ dataProvider, nullptr, true, kCGRenderingIntentDefault);
+ ::CGDataProviderRelease(dataProvider);
+ if (!cgImage) {
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+ CGImageRef subImage = ::CGImageCreateWithImageInRect(cgImage,
+ ::CGRectMake(aX * scaleFactor,
+ aY * scaleFactor,
+ aWidth * scaleFactor,
+ aHeight * scaleFactor));
+ if (!subImage) {
+ ::CGImageRelease(cgImage);
+ surf->Unlock();
+ return NS_ERROR_FAILURE;
+ }
+
+ ::CGContextScaleCTM(aContext, 1.0f, -1.0f);
+ ::CGContextDrawImage(aContext,
+ CGRectMake(aX * scaleFactor,
+ (-(CGFloat)aY - (CGFloat)aHeight) * scaleFactor,
+ aWidth * scaleFactor,
+ aHeight * scaleFactor),
+ subImage);
+
+ ::CGImageRelease(subImage);
+ ::CGImageRelease(cgImage);
+ surf->Unlock();
+ return NS_OK;
+}
+
+void nsCARenderer::DetachCALayer() {
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* oldLayer = (CALayer*) [sublayers objectAtIndex:0];
+ [oldLayer removeFromSuperlayer];
+}
+
+void nsCARenderer::AttachCALayer(void *aCALayer) {
+ CALayer* wrapperLayer = (CALayer*)mWrapperCALayer;
+ NSArray* sublayers = [wrapperLayer sublayers];
+ CALayer* oldLayer = (CALayer*) [sublayers objectAtIndex:0];
+ [oldLayer removeFromSuperlayer];
+ [wrapperLayer addSublayer:(CALayer*)aCALayer];
+}
+
+#ifdef DEBUG
+
+int sSaveToDiskSequence = 0;
+void nsCARenderer::SaveToDisk(MacIOSurface *surf) {
+ surf->Lock();
+ size_t bytesPerRow = surf->GetBytesPerRow();
+ size_t ioWidth = surf->GetWidth();
+ size_t ioHeight = surf->GetHeight();
+ void* ioData = surf->GetBaseAddress();
+ CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(ioData,
+ ioData, ioHeight*(bytesPerRow)*4,
+ nullptr); //No release callback
+ if (!dataProvider) {
+ surf->Unlock();
+ return;
+ }
+
+ CGColorSpaceRef colorSpace = CreateSystemColorSpace();
+ CGImageRef cgImage = ::CGImageCreate(ioWidth, ioHeight, 8, 32, bytesPerRow,
+ colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
+ dataProvider, nullptr, true, kCGRenderingIntentDefault);
+ ::CGDataProviderRelease(dataProvider);
+ ::CGColorSpaceRelease(colorSpace);
+ if (!cgImage) {
+ surf->Unlock();
+ return;
+ }
+
+ char cstr[1000];
+ SprintfLiteral(cstr, "file:///Users/benoitgirard/debug/iosurface_%i.png", ++sSaveToDiskSequence);
+
+ CFStringRef cfStr = ::CFStringCreateWithCString(kCFAllocatorDefault, cstr, kCFStringEncodingMacRoman);
+
+ printf("Exporting: %s\n", cstr);
+ CFURLRef url = ::CFURLCreateWithString( nullptr, cfStr, nullptr);
+ ::CFRelease(cfStr);
+
+ CFStringRef type = kUTTypePNG;
+ size_t count = 1;
+ CFDictionaryRef options = nullptr;
+ CGImageDestinationRef dest = ::CGImageDestinationCreateWithURL(url, type, count, options);
+ ::CFRelease(url);
+
+ ::CGImageDestinationAddImage(dest, cgImage, nullptr);
+
+ ::CGImageDestinationFinalize(dest);
+ ::CFRelease(dest);
+ ::CGImageRelease(cgImage);
+
+ surf->Unlock();
+
+ return;
+
+}
+
+#endif
diff --git a/gfx/2d/SFNTNameTable.cpp b/gfx/2d/SFNTNameTable.cpp
index e7da2305c9..e187b7f047 100644
--- a/gfx/2d/SFNTNameTable.cpp
+++ b/gfx/2d/SFNTNameTable.cpp
@@ -9,6 +9,10 @@
#include "Logging.h"
#include "mozilla/Move.h"
+#if defined(XP_MACOSX)
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
namespace mozilla {
namespace gfx {
@@ -57,6 +61,9 @@ struct NameRecord
enum ENameDecoder : int
{
eNameDecoderUTF16,
+#if defined(XP_MACOSX)
+ eNameDecoderMacRoman,
+#endif
eNameDecoderNone
};
@@ -121,6 +128,19 @@ IsUTF16Encoding(const NameRecord *aNameRecord)
return false;
}
+#if defined(XP_MACOSX)
+static bool
+IsMacRomanEncoding(const NameRecord *aNameRecord)
+{
+ if (aNameRecord->platformID == PLATFORM_ID_MAC &&
+ aNameRecord->encodingID == ENCODING_ID_MAC_ROMAN) {
+ return true;
+ }
+
+ return false;
+}
+#endif
+
static NameRecordMatchers*
CreateCanonicalMatchers(const BigEndianUint16& aNameID)
{
@@ -129,6 +149,37 @@ CreateCanonicalMatchers(const BigEndianUint16& aNameID)
// records and Mac platform records.
NameRecordMatchers *matchers = new NameRecordMatchers();
+#if defined(XP_MACOSX)
+ // First, look for the English name.
+ if (!matchers->append(
+ [=](const NameRecord *aNameRecord) {
+ if (aNameRecord->nameID == aNameID &&
+ aNameRecord->languageID == LANG_ID_MAC_ENGLISH &&
+ aNameRecord->platformID == PLATFORM_ID_MAC &&
+ IsMacRomanEncoding(aNameRecord)) {
+ return eNameDecoderMacRoman;
+ } else {
+ return eNameDecoderNone;
+ }
+ })) {
+ MOZ_CRASH();
+ }
+
+ // Second, look for all languages.
+ if (!matchers->append(
+ [=](const NameRecord *aNameRecord) {
+ if (aNameRecord->nameID == aNameID &&
+ aNameRecord->platformID == PLATFORM_ID_MAC &&
+ IsMacRomanEncoding(aNameRecord)) {
+ return eNameDecoderMacRoman;
+ } else {
+ return eNameDecoderNone;
+ }
+ })) {
+ MOZ_CRASH();
+ }
+#endif /* defined(XP_MACOSX) */
+
// First, look for the English name (this will normally succeed).
if (!matchers->append(
[=](const NameRecord *aNameRecord) {
@@ -222,6 +273,10 @@ SFNTNameTable::ReadU16Name(const NameRecordMatchers& aMatchers,
switch (aMatchers[i](record)) {
case eNameDecoderUTF16:
return ReadU16NameFromU16Record(record, aU16Name);
+#if defined(XP_MACOSX)
+ case eNameDecoderMacRoman:
+ return ReadU16NameFromMacRomanRecord(record, aU16Name);
+#endif
case eNameDecoderNone:
break;
default:
@@ -256,5 +311,46 @@ SFNTNameTable::ReadU16NameFromU16Record(const NameRecord *aNameRecord,
return true;
}
+#if defined(XP_MACOSX)
+bool
+SFNTNameTable::ReadU16NameFromMacRomanRecord(const NameRecord *aNameRecord,
+ mozilla::u16string& aU16Name)
+{
+ uint32_t offset = aNameRecord->offset;
+ uint32_t length = aNameRecord->length;
+ if (mStringDataLength < offset + length) {
+ gfxWarning() << "Name data too short to contain name string.";
+ return false;
+ }
+ if (length > INT_MAX) {
+ gfxWarning() << "Name record too long to decode.";
+ return false;
+ }
+
+ // pointer to the Mac Roman encoded string in the name record
+ const uint8_t *encodedStr = mStringData + offset;
+
+ CFStringRef cfString;
+ cfString = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, encodedStr,
+ length, kCFStringEncodingMacRoman,
+ false, kCFAllocatorNull);
+
+ // length (in UTF-16 code pairs) of the decoded string
+ CFIndex decodedLength = CFStringGetLength(cfString);
+
+ // temporary buffer
+ UniquePtr<UniChar[]> u16Buffer = MakeUnique<UniChar[]>(decodedLength);
+
+ CFStringGetCharacters(cfString, CFRangeMake(0, decodedLength),
+ u16Buffer.get());
+
+ CFRelease(cfString);
+
+ aU16Name.assign(reinterpret_cast<char16_t*>(u16Buffer.get()), decodedLength);
+
+ return true;
+}
+#endif
+
} // gfx
} // mozilla
diff --git a/gfx/2d/SFNTNameTable.h b/gfx/2d/SFNTNameTable.h
index 3fb1ee0bc8..a38ef0186e 100644
--- a/gfx/2d/SFNTNameTable.h
+++ b/gfx/2d/SFNTNameTable.h
@@ -55,6 +55,11 @@ private:
bool ReadU16NameFromU16Record(const NameRecord *aNameRecord,
mozilla::u16string& aU16Name);
+#if defined(XP_MACOSX)
+ bool ReadU16NameFromMacRomanRecord(const NameRecord *aNameRecord,
+ mozilla::u16string& aU16Name);
+#endif
+
const NameRecord *mFirstRecord;
const NameRecord *mEndOfRecords;
const uint8_t *mStringData;
diff --git a/gfx/2d/ScaledFontMac.cpp b/gfx/2d/ScaledFontMac.cpp
new file mode 100644
index 0000000000..6baf257826
--- /dev/null
+++ b/gfx/2d/ScaledFontMac.cpp
@@ -0,0 +1,247 @@
+/* -*- 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 "ScaledFontMac.h"
+#ifdef USE_SKIA
+#include "PathSkia.h"
+#include "skia/include/core/SkPaint.h"
+#include "skia/include/core/SkPath.h"
+#include "skia/include/ports/SkTypeface_mac.h"
+#endif
+#include <vector>
+#include <dlfcn.h>
+#ifdef MOZ_WIDGET_UIKIT
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+// prototype for private API
+extern "C" {
+CGPathRef CGFontGetGlyphPath(CGFontRef fontRef, CGAffineTransform *textTransform, int unknown, CGGlyph glyph);
+};
+#endif
+
+#ifdef USE_CAIRO_SCALED_FONT
+#include "cairo-quartz.h"
+#endif
+
+namespace mozilla {
+namespace gfx {
+
+ScaledFontMac::CTFontDrawGlyphsFuncT* ScaledFontMac::CTFontDrawGlyphsPtr = nullptr;
+bool ScaledFontMac::sSymbolLookupDone = false;
+
+ScaledFontMac::ScaledFontMac(CGFontRef aFont, Float aSize)
+ : ScaledFontBase(aSize)
+{
+ if (!sSymbolLookupDone) {
+ CTFontDrawGlyphsPtr =
+ (CTFontDrawGlyphsFuncT*)dlsym(RTLD_DEFAULT, "CTFontDrawGlyphs");
+ sSymbolLookupDone = true;
+ }
+
+ // XXX: should we be taking a reference
+ mFont = CGFontRetain(aFont);
+ if (CTFontDrawGlyphsPtr != nullptr) {
+ // only create mCTFont if we're going to be using the CTFontDrawGlyphs API
+ mCTFont = CTFontCreateWithGraphicsFont(aFont, aSize, nullptr, nullptr);
+ } else {
+ mCTFont = nullptr;
+ }
+}
+
+ScaledFontMac::~ScaledFontMac()
+{
+ if (mCTFont) {
+ CFRelease(mCTFont);
+ }
+ CGFontRelease(mFont);
+}
+
+#ifdef USE_SKIA
+SkTypeface* ScaledFontMac::GetSkTypeface()
+{
+ if (!mTypeface) {
+ if (mCTFont) {
+ mTypeface = SkCreateTypefaceFromCTFont(mCTFont);
+ } else {
+ CTFontRef fontFace = CTFontCreateWithGraphicsFont(mFont, mSize, nullptr, nullptr);
+ mTypeface = SkCreateTypefaceFromCTFont(fontFace);
+ CFRelease(fontFace);
+ }
+ }
+ return mTypeface;
+}
+#endif
+
+// private API here are the public options on OS X
+// CTFontCreatePathForGlyph
+// ATSUGlyphGetCubicPaths
+// we've used this in cairo sucessfully for some time.
+// Note: cairo dlsyms it. We could do that but maybe it's
+// safe just to use?
+
+already_AddRefed<Path>
+ScaledFontMac::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget)
+{
+ return ScaledFontBase::GetPathForGlyphs(aBuffer, aTarget);
+}
+
+uint32_t
+CalcTableChecksum(const uint32_t *tableStart, uint32_t length, bool skipChecksumAdjust = false)
+{
+ uint32_t sum = 0L;
+ const uint32_t *table = tableStart;
+ const uint32_t *end = table+((length+3) & ~3) / sizeof(uint32_t);
+ while (table < end) {
+ if (skipChecksumAdjust && (table - tableStart) == 2) {
+ table++;
+ } else {
+ sum += CFSwapInt32BigToHost(*table++);
+ }
+ }
+ return sum;
+}
+
+struct TableRecord {
+ uint32_t tag;
+ uint32_t checkSum;
+ uint32_t offset;
+ uint32_t length;
+ CFDataRef data;
+};
+
+int maxPow2LessThan(int a)
+{
+ int x = 1;
+ int shift = 0;
+ while ((x<<(shift+1)) < a) {
+ shift++;
+ }
+ return shift;
+}
+
+struct writeBuf
+{
+ explicit writeBuf(int size)
+ {
+ this->data = new unsigned char [size];
+ this->offset = 0;
+ }
+ ~writeBuf() {
+ delete this->data;
+ }
+
+ template <class T>
+ void writeElement(T a)
+ {
+ *reinterpret_cast<T*>(&this->data[this->offset]) = a;
+ this->offset += sizeof(T);
+ }
+
+ void writeMem(const void *data, unsigned long length)
+ {
+ memcpy(&this->data[this->offset], data, length);
+ this->offset += length;
+ }
+
+ void align()
+ {
+ while (this->offset & 3) {
+ this->data[this->offset] = 0;
+ this->offset++;
+ }
+ }
+
+ unsigned char *data;
+ int offset;
+};
+
+bool
+ScaledFontMac::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton)
+{
+ // We'll reconstruct a TTF font from the tables we can get from the CGFont
+ CFArrayRef tags = CGFontCopyTableTags(mFont);
+ CFIndex count = CFArrayGetCount(tags);
+
+ TableRecord *records = new TableRecord[count];
+ uint32_t offset = 0;
+ offset += sizeof(uint32_t)*3;
+ offset += sizeof(uint32_t)*4*count;
+ bool CFF = false;
+ for (CFIndex i = 0; i<count; i++) {
+ uint32_t tag = (uint32_t)(uintptr_t)CFArrayGetValueAtIndex(tags, i);
+ if (tag == 0x43464620) // 'CFF '
+ CFF = true;
+ CFDataRef data = CGFontCopyTableForTag(mFont, tag);
+ records[i].tag = tag;
+ records[i].offset = offset;
+ records[i].data = data;
+ records[i].length = CFDataGetLength(data);
+ bool skipChecksumAdjust = (tag == 0x68656164); // 'head'
+ records[i].checkSum = CalcTableChecksum(reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(data)),
+ records[i].length, skipChecksumAdjust);
+ offset += records[i].length;
+ // 32 bit align the tables
+ offset = (offset + 3) & ~3;
+ }
+ CFRelease(tags);
+
+ struct writeBuf buf(offset);
+ // write header/offset table
+ if (CFF) {
+ buf.writeElement(CFSwapInt32HostToBig(0x4f54544f));
+ } else {
+ buf.writeElement(CFSwapInt32HostToBig(0x00010000));
+ }
+ buf.writeElement(CFSwapInt16HostToBig(count));
+ buf.writeElement(CFSwapInt16HostToBig((1<<maxPow2LessThan(count))*16));
+ buf.writeElement(CFSwapInt16HostToBig(maxPow2LessThan(count)));
+ buf.writeElement(CFSwapInt16HostToBig(count*16-((1<<maxPow2LessThan(count))*16)));
+
+ // write table record entries
+ for (CFIndex i = 0; i<count; i++) {
+ buf.writeElement(CFSwapInt32HostToBig(records[i].tag));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].checkSum));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].offset));
+ buf.writeElement(CFSwapInt32HostToBig(records[i].length));
+ }
+
+ // write tables
+ int checkSumAdjustmentOffset = 0;
+ for (CFIndex i = 0; i<count; i++) {
+ if (records[i].tag == 0x68656164) {
+ checkSumAdjustmentOffset = buf.offset + 2*4;
+ }
+ buf.writeMem(CFDataGetBytePtr(records[i].data), CFDataGetLength(records[i].data));
+ buf.align();
+ CFRelease(records[i].data);
+ }
+ delete[] records;
+
+ // clear the checksumAdjust field before checksumming the whole font
+ memset(&buf.data[checkSumAdjustmentOffset], 0, sizeof(uint32_t));
+ uint32_t fontChecksum = CFSwapInt32HostToBig(0xb1b0afba - CalcTableChecksum(reinterpret_cast<const uint32_t*>(buf.data), offset));
+ // set checkSumAdjust to the computed checksum
+ memcpy(&buf.data[checkSumAdjustmentOffset], &fontChecksum, sizeof(fontChecksum));
+
+ // we always use an index of 0
+ aDataCallback(buf.data, buf.offset, 0, mSize, aBaton);
+
+ return true;
+
+}
+
+#ifdef USE_CAIRO_SCALED_FONT
+cairo_font_face_t*
+ScaledFontMac::GetCairoFontFace()
+{
+ MOZ_ASSERT(mFont);
+ return cairo_quartz_font_face_create_for_cgfont(mFont);
+}
+#endif
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/2d/ScaledFontMac.h b/gfx/2d/ScaledFontMac.h
new file mode 100644
index 0000000000..c141f96b26
--- /dev/null
+++ b/gfx/2d/ScaledFontMac.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_SCALEDFONTMAC_H_
+#define MOZILLA_GFX_SCALEDFONTMAC_H_
+
+#ifdef MOZ_WIDGET_COCOA
+#include <ApplicationServices/ApplicationServices.h>
+#else
+#include <CoreGraphics/CoreGraphics.h>
+#include <CoreText/CoreText.h>
+#endif
+
+#include "2D.h"
+
+#include "ScaledFontBase.h"
+
+namespace mozilla {
+namespace gfx {
+
+class GlyphRenderingOptionsCG : public GlyphRenderingOptions
+{
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GlyphRenderingOptionsCG, override)
+
+ explicit GlyphRenderingOptionsCG(const Color &aFontSmoothingBackgroundColor)
+ : mFontSmoothingBackgroundColor(aFontSmoothingBackgroundColor)
+ {}
+
+ const Color &FontSmoothingBackgroundColor() const { return mFontSmoothingBackgroundColor; }
+
+ virtual FontType GetType() const override { return FontType::MAC; }
+
+private:
+ Color mFontSmoothingBackgroundColor;
+};
+
+class ScaledFontMac : public ScaledFontBase
+{
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFontMac)
+ ScaledFontMac(CGFontRef aFont, Float aSize);
+ virtual ~ScaledFontMac();
+
+ virtual FontType GetType() const { return FontType::MAC; }
+#ifdef USE_SKIA
+ virtual SkTypeface* GetSkTypeface();
+#endif
+ virtual already_AddRefed<Path> GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget *aTarget);
+ virtual bool GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton);
+
+#ifdef USE_CAIRO_SCALED_FONT
+ cairo_font_face_t* GetCairoFontFace();
+#endif
+
+private:
+ friend class DrawTargetSkia;
+ CGFontRef mFont;
+ CTFontRef mCTFont; // only created if CTFontDrawGlyphs is available, otherwise null
+
+ typedef void (CTFontDrawGlyphsFuncT)(CTFontRef,
+ const CGGlyph[], const CGPoint[],
+ size_t, CGContextRef);
+
+ static bool sSymbolLookupDone;
+
+public:
+ // function pointer for CTFontDrawGlyphs, if available;
+ // initialized the first time a ScaledFontMac is created,
+ // so it will be valid by the time DrawTargetCG wants to use it
+ static CTFontDrawGlyphsFuncT* CTFontDrawGlyphsPtr;
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_SCALEDFONTMAC_H_ */
diff --git a/gfx/2d/moz.build b/gfx/2d/moz.build
index 7a80b1de2e..4f437d575a 100644
--- a/gfx/2d/moz.build
+++ b/gfx/2d/moz.build
@@ -58,7 +58,16 @@ EXPORTS.mozilla.gfx += [
EXPORTS.mozilla.gfx += ['ssse3-scaler.h']
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
+ EXPORTS.mozilla.gfx += [
+ 'MacIOSurface.h',
+ ]
+ SOURCES += [
+ 'NativeFontResourceMac.cpp',
+ 'PathCG.cpp',
+ 'ScaledFontMac.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
SOURCES += [
'DrawTargetD2D1.cpp',
'ExtendInputEffectD2D1.cpp',
@@ -172,6 +181,15 @@ if CONFIG['CLANG_CXX']:
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ EXPORTS.mozilla.gfx += [
+ 'QuartzSupport.h',
+ ]
+ SOURCES += [
+ 'MacIOSurface.cpp',
+ 'QuartzSupport.mm',
+ ]
+
if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['BUILD_ARM_NEON']:
SOURCES += ['BlurNEON.cpp']
SOURCES['BlurNEON.cpp'].flags += CONFIG['NEON_FLAGS']
diff --git a/gfx/cairo/cairo/src/moz.build b/gfx/cairo/cairo/src/moz.build
index 75201123ce..e097c45ca6 100644
--- a/gfx/cairo/cairo/src/moz.build
+++ b/gfx/cairo/cairo/src/moz.build
@@ -16,7 +16,7 @@ EXPORTS.cairo += [
'pixman-rename.h',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] not in ('uikit'):
+if CONFIG['MOZ_WIDGET_TOOLKIT'] not in ('cocoa', 'uikit'):
EXPORTS.cairo += [
'cairo-pdf.h',
]
@@ -53,7 +53,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
]
else:
DEFINES['CAIRO_OMIT_WIN32_PRINTING'] = True
-elif CONFIG['MOZ_WIDGET_TOOLKIT'] in {'uikit'}:
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] in {'cocoa', 'uikit'}:
EXPORTS.cairo += [
'cairo-quartz-image.h',
'cairo-quartz.h',
diff --git a/gfx/cairo/libpixman/src/moz.build b/gfx/cairo/libpixman/src/moz.build
index 02e2582452..9b463afd64 100644
--- a/gfx/cairo/libpixman/src/moz.build
+++ b/gfx/cairo/libpixman/src/moz.build
@@ -8,7 +8,11 @@ EXPORTS += [
'pixman.h',
]
-if CONFIG['GNU_CC']:
+# Apple's arm assembler doesn't support the same syntax as
+# the standard GNU assembler, so use the C fallback paths for now.
+# This may be fixable if clang's ARM/iOS assembler improves into a
+# viable solution in the future.
+if CONFIG['OS_ARCH'] != 'Darwin' and CONFIG['GNU_CC']:
if CONFIG['HAVE_ARM_NEON']:
SOURCES += [
'pixman-arm-neon-asm-bilinear.S',
@@ -86,11 +90,16 @@ if '86' in CONFIG['OS_TEST']:
elif 'ppc' in CONFIG['OS_TEST']:
if CONFIG['GNU_CC']:
use_vmx = True
+# Apple's arm assembler doesn't support the same syntax as
+# the standard GNU assembler, so use the C fallback paths for now.
+# This may be fixable if clang's ARM/iOS assembler improves into a
+# viable solution in the future.
elif 'arm' in CONFIG['OS_TEST']:
- if CONFIG['HAVE_ARM_SIMD']:
- use_arm_simd_gcc = True
- if CONFIG['HAVE_ARM_NEON']:
- use_arm_neon_gcc = True
+ if CONFIG['OS_ARCH'] != 'Darwin':
+ if CONFIG['HAVE_ARM_SIMD']:
+ use_arm_simd_gcc = True
+ if CONFIG['HAVE_ARM_NEON']:
+ use_arm_neon_gcc = True
if use_mmx:
DEFINES['USE_MMX'] = True
diff --git a/gfx/gl/ForceDiscreteGPUHelperCGL.h b/gfx/gl/ForceDiscreteGPUHelperCGL.h
new file mode 100644
index 0000000000..3b4cb07efa
--- /dev/null
+++ b/gfx/gl/ForceDiscreteGPUHelperCGL.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef ForceDiscreteGPUHelperCGL_h_
+#define ForceDiscreteGPUHelperCGL_h_
+
+#include <OpenGL/OpenGL.h>
+
+/** This RAII helper guarantees that we're on the discrete GPU during its lifetime.
+ *
+ * As long as any ForceDiscreteGPUHelperCGL object is alive, we're on the discrete GPU.
+ */
+class ForceDiscreteGPUHelperCGL
+{
+ CGLPixelFormatObj mPixelFormatObj;
+
+public:
+ ForceDiscreteGPUHelperCGL()
+ {
+ // the code in this function is taken from Chromium, src/ui/gfx/gl/gl_context_cgl.cc, r122013
+ // BSD-style license, (c) The Chromium Authors
+ CGLPixelFormatAttribute attribs[1];
+ attribs[0] = static_cast<CGLPixelFormatAttribute>(0);
+ GLint num_pixel_formats = 0;
+ CGLChoosePixelFormat(attribs, &mPixelFormatObj, &num_pixel_formats);
+ }
+
+ ~ForceDiscreteGPUHelperCGL()
+ {
+ CGLReleasePixelFormat(mPixelFormatObj);
+ }
+};
+
+#endif // ForceDiscreteGPUHelperCGL_h_
diff --git a/gfx/gl/GLBlitHelper.cpp b/gfx/gl/GLBlitHelper.cpp
index 12e30c8804..da7f5b462d 100644
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -14,6 +14,11 @@
#include "mozilla/gfx/Matrix.h"
#include "mozilla/UniquePtr.h"
+#ifdef XP_MACOSX
+#include "MacIOSurfaceImage.h"
+#include "GLContextCGL.h"
+#endif
+
using mozilla::layers::PlanarYCbCrImage;
using mozilla::layers::PlanarYCbCrData;
@@ -181,6 +186,34 @@ GLBlitHelper::InitTexQuadProgram(BlitType target)
} \n\
";
+#ifdef XP_MACOSX
+ const char kTexNV12PlanarBlit_FragShaderSource[] = "\
+ #version 100 \n\
+ #extension GL_ARB_texture_rectangle : require \n\
+ #ifdef GL_ES \n\
+ precision mediump float \n\
+ #endif \n\
+ varying vec2 vTexCoord; \n\
+ uniform sampler2DRect uYTexture; \n\
+ uniform sampler2DRect uCbCrTexture; \n\
+ uniform vec2 uYTexScale; \n\
+ uniform vec2 uCbCrTexScale; \n\
+ void main() \n\
+ { \n\
+ float y = texture2DRect(uYTexture, vTexCoord * uYTexScale).r; \n\
+ float cb = texture2DRect(uCbCrTexture, vTexCoord * uCbCrTexScale).r; \n\
+ float cr = texture2DRect(uCbCrTexture, vTexCoord * uCbCrTexScale).a; \n\
+ y = (y - 0.06275) * 1.16438; \n\
+ cb = cb - 0.50196; \n\
+ cr = cr - 0.50196; \n\
+ gl_FragColor.r = y + cr * 1.59603; \n\
+ gl_FragColor.g = y - 0.81297 * cr - 0.39176 * cb; \n\
+ gl_FragColor.b = y + cb * 2.01723; \n\
+ gl_FragColor.a = 1.0; \n\
+ } \n\
+ ";
+#endif
+
bool success = false;
GLuint* programPtr;
@@ -203,6 +236,13 @@ GLBlitHelper::InitTexQuadProgram(BlitType target)
fragShaderPtr = &mTexYUVPlanarBlit_FragShader;
fragShaderSource = kTexYUVPlanarBlit_FragShaderSource;
break;
+#ifdef XP_MACOSX
+ case ConvertMacIOSurfaceImage:
+ programPtr = &mTexNV12PlanarBlit_Program;
+ fragShaderPtr = &mTexNV12PlanarBlit_FragShader;
+ fragShaderSource = kTexNV12PlanarBlit_FragShaderSource;
+ break;
+#endif
default:
return false;
}
@@ -353,6 +393,24 @@ GLBlitHelper::InitTexQuadProgram(BlitType target)
mGL->fUniform1i(texCr, Channel_Cr);
break;
}
+ case ConvertMacIOSurfaceImage: {
+#ifdef XP_MACOSX
+ GLint texY = mGL->fGetUniformLocation(program, "uYTexture");
+ GLint texCbCr = mGL->fGetUniformLocation(program, "uCbCrTexture");
+ mYTexScaleLoc = mGL->fGetUniformLocation(program, "uYTexScale");
+ mCbCrTexScaleLoc= mGL->fGetUniformLocation(program, "uCbCrTexScale");
+
+ DebugOnly<bool> hasUniformLocations = texY != -1 &&
+ texCbCr != -1 &&
+ mYTexScaleLoc != -1 &&
+ mCbCrTexScaleLoc != -1;
+ MOZ_ASSERT(hasUniformLocations, "uniforms not found");
+
+ mGL->fUniform1i(texY, Channel_Y);
+ mGL->fUniform1i(texCbCr, Channel_Cb);
+#endif
+ break;
+ }
default:
return false;
}
@@ -620,6 +678,47 @@ GLBlitHelper::BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage)
return true;
}
+#ifdef XP_MACOSX
+bool
+GLBlitHelper::BlitMacIOSurfaceImage(layers::MacIOSurfaceImage* ioImage)
+{
+ ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
+ MacIOSurface* surf = ioImage->GetSurface();
+
+ GLint oldTex[2];
+ for (int i = 0; i < 2; i++) {
+ mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+ mGL->fGetIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &oldTex[i]);
+ }
+
+ GLuint textures[2];
+ mGL->fGenTextures(2, textures);
+
+ mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
+ mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[0]);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
+ surf->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(mGL)->GetCGLContext(), 0);
+ mGL->fUniform2f(mYTexScaleLoc, surf->GetWidth(0), surf->GetHeight(0));
+
+ mGL->fActiveTexture(LOCAL_GL_TEXTURE1);
+ mGL->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textures[1]);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
+ surf->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(mGL)->GetCGLContext(), 1);
+ mGL->fUniform2f(mCbCrTexScaleLoc, surf->GetWidth(1), surf->GetHeight(1));
+
+ mGL->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4);
+ for (int i = 0; i < 2; i++) {
+ mGL->fActiveTexture(LOCAL_GL_TEXTURE0 + i);
+ mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, oldTex[i]);
+ }
+
+ mGL->fDeleteTextures(2, textures);
+ return true;
+}
+#endif
+
bool
GLBlitHelper::BlitImageToFramebuffer(layers::Image* srcImage,
const gfx::IntSize& destSize,
@@ -637,6 +736,13 @@ GLBlitHelper::BlitImageToFramebuffer(layers::Image* srcImage,
srcOrigin = OriginPos::BottomLeft;
break;
+#ifdef XP_MACOSX
+ case ImageFormat::MAC_IOSURFACE:
+ type = ConvertMacIOSurfaceImage;
+ srcOrigin = OriginPos::TopLeft;
+ break;
+#endif
+
default:
return false;
}
@@ -662,6 +768,11 @@ GLBlitHelper::BlitImageToFramebuffer(layers::Image* srcImage,
return ret;
}
+#ifdef XP_MACOSX
+ case ConvertMacIOSurfaceImage:
+ return BlitMacIOSurfaceImage(srcImage->AsMacIOSurfaceImage());
+#endif
+
default:
return false;
}
diff --git a/gfx/gl/GLBlitHelper.h b/gfx/gl/GLBlitHelper.h
index 9dd8cd8870..c2858f5913 100644
--- a/gfx/gl/GLBlitHelper.h
+++ b/gfx/gl/GLBlitHelper.h
@@ -107,6 +107,9 @@ class GLBlitHelper final
void BindAndUploadEGLImage(EGLImage image, GLuint target);
bool BlitPlanarYCbCrImage(layers::PlanarYCbCrImage* yuvImage);
+#ifdef XP_MACOSX
+ bool BlitMacIOSurfaceImage(layers::MacIOSurfaceImage* ioImage);
+#endif
explicit GLBlitHelper(GLContext* gl);
diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp
index 0106f4fd09..72a7bea58b 100644
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -36,6 +36,10 @@
#include "mozilla/DebugOnly.h"
+#ifdef XP_MACOSX
+#include <CoreServices/CoreServices.h>
+#endif
+
#if defined(MOZ_WIDGET_COCOA)
#include "nsCocoaFeatures.h"
#endif
@@ -873,6 +877,18 @@ GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
// multisampling hardcodes blending with the default blendfunc, which breaks WebGL.
MarkUnsupported(GLFeature::framebuffer_multisample);
}
+
+#ifdef XP_MACOSX
+ // The Mac Nvidia driver, for versions up to and including 10.8,
+ // don't seem to properly support this. See 814839
+ // this has been fixed in Mac OS X 10.9. See 907946
+ // and it also works in 10.8.3 and higher. See 1094338.
+ if (Vendor() == gl::GLVendor::NVIDIA &&
+ !nsCocoaFeatures::IsAtLeastVersion(10,8,3))
+ {
+ MarkUnsupported(GLFeature::depth_texture);
+ }
+#endif
}
if (IsExtensionSupported(GLContext::ARB_pixel_buffer_object)) {
@@ -993,6 +1009,25 @@ GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
raw_fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mMaxRenderbufferSize);
raw_fGetIntegerv(LOCAL_GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
+#ifdef XP_MACOSX
+ if (mWorkAroundDriverBugs) {
+ if (mVendor == GLVendor::Intel) {
+ // see bug 737182 for 2D textures, bug 684882 for cube map textures.
+ mMaxTextureSize = std::min(mMaxTextureSize, 4096);
+ mMaxCubeMapTextureSize = std::min(mMaxCubeMapTextureSize, 512);
+ // for good measure, we align renderbuffers on what we do for 2D textures
+ mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 4096);
+ mNeedsTextureSizeChecks = true;
+ } else if (mVendor == GLVendor::NVIDIA) {
+ // See bug 879656. 8192 fails, 8191 works.
+ mMaxTextureSize = std::min(mMaxTextureSize, 8191);
+ mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 8191);
+
+ // Part of the bug 879656, but it also doesn't hurt the 877949
+ mNeedsTextureSizeChecks = true;
+ }
+ }
+#endif
#ifdef MOZ_X11
if (mWorkAroundDriverBugs) {
if (mVendor == GLVendor::Nouveau) {
@@ -1765,6 +1800,20 @@ GLContext::InitExtensions()
MarkExtensionUnsupported(ANGLE_texture_compression_dxt3);
MarkExtensionUnsupported(ANGLE_texture_compression_dxt5);
}
+
+#ifdef XP_MACOSX
+ // Bug 1009642: On OSX Mavericks (10.9), the driver for Intel HD
+ // 3000 appears to be buggy WRT updating sub-images of S3TC
+ // textures with glCompressedTexSubImage2D. Works on Intel HD 4000
+ // and Intel HD 5000/Iris that I tested.
+ // Bug 1124996: Appears to be the same on OSX Yosemite (10.10)
+ if (nsCocoaFeatures::macOSVersionMajor() == 10 &&
+ nsCocoaFeatures::macOSVersionMinor() >= 9 &&
+ Renderer() == GLRenderer::IntelHD3000)
+ {
+ MarkExtensionUnsupported(EXT_texture_compression_s3tc);
+ }
+#endif
}
if (shouldDumpExts) {
@@ -2779,6 +2828,35 @@ GLContext::fReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum f
}
AfterGLReadCall();
+
+ // Check if GL is giving back 1.0 alpha for
+ // RGBA reads to RGBA images from no-alpha buffers.
+#ifdef XP_MACOSX
+ if (WorkAroundDriverBugs() &&
+ Vendor() == gl::GLVendor::NVIDIA &&
+ format == LOCAL_GL_RGBA &&
+ type == LOCAL_GL_UNSIGNED_BYTE &&
+ !IsCoreProfile() &&
+ width && height)
+ {
+ GLint alphaBits = 0;
+ fGetIntegerv(LOCAL_GL_ALPHA_BITS, &alphaBits);
+ if (!alphaBits) {
+ const uint32_t alphaMask = 0xff000000;
+
+ uint32_t* itr = (uint32_t*)pixels;
+ uint32_t testPixel = *itr;
+ if ((testPixel & alphaMask) != alphaMask) {
+ // We need to set the alpha channel to 1.0 manually.
+ uint32_t* itrEnd = itr + width*height; // Stride is guaranteed to be width*4.
+
+ for (; itr != itrEnd; itr++) {
+ *itr |= alphaMask;
+ }
+ }
+ }
+ }
+#endif
}
void
diff --git a/gfx/gl/GLContextCGL.h b/gfx/gl/GLContextCGL.h
new file mode 100644
index 0000000000..29d5d982d7
--- /dev/null
+++ b/gfx/gl/GLContextCGL.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef GLCONTEXTCGL_H_
+#define GLCONTEXTCGL_H_
+
+#include "GLContext.h"
+
+#include "OpenGL/OpenGL.h"
+
+#ifdef __OBJC__
+#include <AppKit/NSOpenGL.h>
+#else
+typedef void NSOpenGLContext;
+#endif
+
+namespace mozilla {
+namespace gl {
+
+class GLContextCGL : public GLContext
+{
+ friend class GLContextProviderCGL;
+
+ NSOpenGLContext* const mContext;
+
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GLContextCGL, override)
+ GLContextCGL(CreateContextFlags flags, const SurfaceCaps& caps,
+ NSOpenGLContext* context, bool isOffscreen, ContextProfile profile);
+
+ ~GLContextCGL();
+
+ virtual GLContextType GetContextType() const override { return GLContextType::CGL; }
+
+ static GLContextCGL* Cast(GLContext* gl) {
+ MOZ_ASSERT(gl->GetContextType() == GLContextType::CGL);
+ return static_cast<GLContextCGL*>(gl);
+ }
+
+ bool Init() override;
+
+ NSOpenGLContext* GetNSOpenGLContext() const { return mContext; }
+ CGLContextObj GetCGLContext() const;
+
+ virtual bool MakeCurrentImpl(bool aForce) override;
+
+ virtual bool IsCurrent() override;
+
+ virtual GLenum GetPreferredARGB32Format() const override;
+
+ virtual bool SetupLookupFunction() override;
+
+ virtual bool IsDoubleBuffered() const override;
+
+ virtual bool SupportsRobustness() const override { return false; }
+
+ virtual bool SwapBuffers() override;
+
+ virtual void GetWSIInfo(nsCString* const out) const override;
+};
+
+} // namespace gl
+} // namespace mozilla
+
+#endif // GLCONTEXTCGL_H_
diff --git a/gfx/gl/GLContextEAGL.h b/gfx/gl/GLContextEAGL.h
new file mode 100644
index 0000000000..f677d23d57
--- /dev/null
+++ b/gfx/gl/GLContextEAGL.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef GLCONTEXTEAGL_H_
+#define GLCONTEXTEAGL_H_
+
+#include "GLContext.h"
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <OpenGLES/EAGL.h>
+
+namespace mozilla {
+namespace gl {
+
+class GLContextEAGL : public GLContext
+{
+ friend class GLContextProviderEAGL;
+
+ EAGLContext* const mContext;
+
+public:
+ MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GLContextEAGL, override)
+ GLContextEAGL(CreateContextFlags flags, const SurfaceCaps& caps, EAGLContext* context,
+ GLContext* sharedContext, bool isOffscreen, ContextProfile profile);
+
+ ~GLContextEAGL();
+
+ virtual GLContextType GetContextType() const override {
+ return GLContextType::EAGL;
+ }
+
+ static GLContextEAGL* Cast(GLContext* gl) {
+ MOZ_ASSERT(gl->GetContextType() == GLContextType::EAGL);
+ return static_cast<GLContextEAGL*>(gl);
+ }
+
+ bool Init() override;
+
+ bool AttachToWindow(nsIWidget* aWidget);
+
+ EAGLContext* GetEAGLContext() const { return mContext; }
+
+ virtual bool MakeCurrentImpl(bool aForce) override;
+
+ virtual bool IsCurrent() override;
+
+ virtual bool SetupLookupFunction() override;
+
+ virtual bool IsDoubleBuffered() const override;
+
+ virtual bool SupportsRobustness() const override { return false; }
+
+ virtual bool SwapBuffers() override;
+
+ virtual void GetWSIInfo(nsCString* const out) const override;
+
+ virtual GLuint GetDefaultFramebuffer() override {
+ return mBackbufferFB;
+ }
+
+ virtual bool RenewSurface(nsIWidget* aWidget) override {
+ // FIXME: should use the passed widget instead of the existing one.
+ return RecreateRB();
+ }
+
+private:
+ GLuint mBackbufferRB;
+ GLuint mBackbufferFB;
+
+ void* mLayer;
+
+ bool RecreateRB();
+};
+
+} // namespace gl
+} // namespace mozilla
+
+#endif // GLCONTEXTEAGL_H_
diff --git a/gfx/gl/GLContextProvider.h b/gfx/gl/GLContextProvider.h
index d9b3f04196..6e096c1f7e 100644
--- a/gfx/gl/GLContextProvider.h
+++ b/gfx/gl/GLContextProvider.h
@@ -34,6 +34,13 @@ namespace gl {
#define DEFAULT_IMPL WGL
#endif
+#ifdef XP_MACOSX
+ #define GL_CONTEXT_PROVIDER_NAME GLContextProviderCGL
+ #include "GLContextProviderImpl.h"
+ #undef GL_CONTEXT_PROVIDER_NAME
+ #define GL_CONTEXT_PROVIDER_DEFAULT GLContextProviderCGL
+#endif
+
#if defined(MOZ_X11)
#define GL_CONTEXT_PROVIDER_NAME GLContextProviderGLX
#include "GLContextProviderImpl.h"
diff --git a/gfx/gl/GLContextProviderCGL.mm b/gfx/gl/GLContextProviderCGL.mm
new file mode 100644
index 0000000000..7cc89eac9f
--- /dev/null
+++ b/gfx/gl/GLContextProviderCGL.mm
@@ -0,0 +1,397 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "GLContextProvider.h"
+#include "GLContextCGL.h"
+#include "nsDebug.h"
+#include "nsIWidget.h"
+#include <OpenGL/gl.h>
+#include "gfxFailure.h"
+#include "gfxPrefs.h"
+#include "prenv.h"
+#include "GeckoProfiler.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "mozilla/widget/CompositorWidget.h"
+
+#include <OpenGL/OpenGL.h>
+
+// When running inside a VM, creating an accelerated OpenGL context usually
+// fails. Uncomment this line to emulate that behavior.
+// #define EMULATE_VM
+
+namespace mozilla {
+namespace gl {
+
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+class CGLLibrary
+{
+public:
+ CGLLibrary()
+ : mInitialized(false)
+ , mUseDoubleBufferedWindows(true)
+ , mOGLLibrary(nullptr)
+ {}
+
+ bool EnsureInitialized()
+ {
+ if (mInitialized) {
+ return true;
+ }
+ if (!mOGLLibrary) {
+ mOGLLibrary = PR_LoadLibrary("/System/Library/Frameworks/OpenGL.framework/OpenGL");
+ if (!mOGLLibrary) {
+ NS_WARNING("Couldn't load OpenGL Framework.");
+ return false;
+ }
+ }
+
+ const char* db = PR_GetEnv("MOZ_CGL_DB");
+ if (db) {
+ mUseDoubleBufferedWindows = *db != '0';
+ }
+
+ mInitialized = true;
+ return true;
+ }
+
+ bool UseDoubleBufferedWindows() const {
+ MOZ_ASSERT(mInitialized);
+ return mUseDoubleBufferedWindows;
+ }
+
+private:
+ bool mInitialized;
+ bool mUseDoubleBufferedWindows;
+ PRLibrary* mOGLLibrary;
+};
+
+CGLLibrary sCGLLibrary;
+
+GLContextCGL::GLContextCGL(CreateContextFlags flags, const SurfaceCaps& caps,
+ NSOpenGLContext* context, bool isOffscreen,
+ ContextProfile profile)
+ : GLContext(flags, caps, nullptr, isOffscreen)
+ , mContext(context)
+{
+ SetProfileVersion(profile, 210);
+}
+
+GLContextCGL::~GLContextCGL()
+{
+ MarkDestroyed();
+ [mContext release];
+}
+
+bool
+GLContextCGL::Init()
+{
+ return InitWithPrefix("gl", true);
+}
+
+CGLContextObj
+GLContextCGL::GetCGLContext() const
+{
+ return static_cast<CGLContextObj>([mContext CGLContextObj]);
+}
+
+bool
+GLContextCGL::MakeCurrentImpl(bool aForce)
+{
+ if (IsDestroyed()) {
+ [NSOpenGLContext clearCurrentContext];
+ return false;
+ }
+
+ if (!aForce && [NSOpenGLContext currentContext] == mContext) {
+ return true;
+ }
+
+ if (mContext) {
+ [mContext makeCurrentContext];
+ MOZ_ASSERT(IsCurrent());
+ // Use non-blocking swap in "ASAP mode".
+ // ASAP mode means that rendering is iterated as fast as possible.
+ // ASAP mode is entered when layout.frame_rate=0 (requires restart).
+ // If swapInt is 1, then glSwapBuffers will block and wait for a vblank signal.
+ // When we're iterating as fast as possible, however, we want a non-blocking
+ // glSwapBuffers, which will happen when swapInt==0.
+ GLint swapInt = gfxPrefs::LayoutFrameRate() == 0 ? 0 : 1;
+ [mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
+ }
+ return true;
+}
+
+bool
+GLContextCGL::IsCurrent() {
+ return [NSOpenGLContext currentContext] == mContext;
+}
+
+GLenum
+GLContextCGL::GetPreferredARGB32Format() const
+{
+ return LOCAL_GL_BGRA;
+}
+
+bool
+GLContextCGL::SetupLookupFunction()
+{
+ return false;
+}
+
+bool
+GLContextCGL::IsDoubleBuffered() const
+{
+ return sCGLLibrary.UseDoubleBufferedWindows();
+}
+
+bool
+GLContextCGL::SwapBuffers()
+{
+ PROFILER_LABEL("GLContextCGL", "SwapBuffers",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ [mContext flushBuffer];
+ return true;
+}
+
+void
+GLContextCGL::GetWSIInfo(nsCString* const out) const
+{
+ out->AppendLiteral("CGL");
+}
+
+already_AddRefed<GLContext>
+GLContextProviderCGL::CreateWrappingExisting(void*, void*)
+{
+ return nullptr;
+}
+
+static const NSOpenGLPixelFormatAttribute kAttribs_singleBuffered[] = {
+ NSOpenGLPFAAllowOfflineRenderers,
+ 0
+};
+
+static const NSOpenGLPixelFormatAttribute kAttribs_singleBuffered_accel[] = {
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAAllowOfflineRenderers,
+ 0
+};
+
+static const NSOpenGLPixelFormatAttribute kAttribs_doubleBuffered[] = {
+ NSOpenGLPFAAllowOfflineRenderers,
+ NSOpenGLPFADoubleBuffer,
+ 0
+};
+
+static const NSOpenGLPixelFormatAttribute kAttribs_doubleBuffered_accel[] = {
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAAllowOfflineRenderers,
+ NSOpenGLPFADoubleBuffer,
+ 0
+};
+
+static const NSOpenGLPixelFormatAttribute kAttribs_offscreen[] = {
+ 0
+};
+
+static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_allow_offline[] = {
+ NSOpenGLPFAAllowOfflineRenderers,
+ 0
+};
+
+static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_accel[] = {
+ NSOpenGLPFAAccelerated,
+ 0
+};
+
+static const NSOpenGLPixelFormatAttribute kAttribs_offscreen_coreProfile[] = {
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+ 0
+};
+
+static NSOpenGLContext*
+CreateWithFormat(const NSOpenGLPixelFormatAttribute* attribs)
+{
+ NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc]
+ initWithAttributes:attribs];
+ if (!format) {
+ NS_WARNING("Failed to create NSOpenGLPixelFormat.");
+ return nullptr;
+ }
+
+ NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:format
+ shareContext:nullptr];
+
+ [format release];
+
+ return context;
+}
+
+already_AddRefed<GLContext>
+GLContextProviderCGL::CreateForCompositorWidget(CompositorWidget* aCompositorWidget, bool aForceAccelerated)
+{
+ return CreateForWindow(aCompositorWidget->RealWidget(), aForceAccelerated);
+}
+
+already_AddRefed<GLContext>
+GLContextProviderCGL::CreateForWindow(nsIWidget* aWidget, bool aForceAccelerated)
+{
+ if (!sCGLLibrary.EnsureInitialized()) {
+ return nullptr;
+ }
+
+#ifdef EMULATE_VM
+ if (aForceAccelerated) {
+ return nullptr;
+ }
+#endif
+
+ const NSOpenGLPixelFormatAttribute* attribs;
+ if (sCGLLibrary.UseDoubleBufferedWindows()) {
+ attribs = aForceAccelerated ? kAttribs_doubleBuffered_accel : kAttribs_doubleBuffered;
+ } else {
+ attribs = aForceAccelerated ? kAttribs_singleBuffered_accel : kAttribs_singleBuffered;
+ }
+ NSOpenGLContext* context = CreateWithFormat(attribs);
+ if (!context) {
+ return nullptr;
+ }
+
+ // make the context transparent
+ GLint opaque = 0;
+ [context setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
+
+ SurfaceCaps caps = SurfaceCaps::ForRGBA();
+ ContextProfile profile = ContextProfile::OpenGLCompatibility;
+ RefPtr<GLContextCGL> glContext = new GLContextCGL(CreateContextFlags::NONE, caps,
+ context, false, profile);
+
+ if (!glContext->Init()) {
+ glContext = nullptr;
+ [context release];
+ return nullptr;
+ }
+
+ return glContext.forget();
+}
+
+static already_AddRefed<GLContextCGL>
+CreateOffscreenFBOContext(CreateContextFlags flags)
+{
+ if (!sCGLLibrary.EnsureInitialized()) {
+ return nullptr;
+ }
+
+ ContextProfile profile;
+ NSOpenGLContext* context = nullptr;
+
+ if (!(flags & CreateContextFlags::REQUIRE_COMPAT_PROFILE)) {
+ profile = ContextProfile::OpenGLCore;
+ context = CreateWithFormat(kAttribs_offscreen_coreProfile);
+ }
+ if (!context) {
+ profile = ContextProfile::OpenGLCompatibility;
+
+ if (flags & CreateContextFlags::ALLOW_OFFLINE_RENDERER) {
+ if (gfxPrefs::RequireHardwareGL())
+ context = CreateWithFormat(kAttribs_singleBuffered);
+ else
+ context = CreateWithFormat(kAttribs_offscreen_allow_offline);
+
+ } else {
+ if (gfxPrefs::RequireHardwareGL())
+ context = CreateWithFormat(kAttribs_offscreen_accel);
+ else
+ context = CreateWithFormat(kAttribs_offscreen);
+ }
+ }
+ if (!context) {
+ NS_WARNING("Failed to create NSOpenGLContext.");
+ return nullptr;
+ }
+
+ SurfaceCaps dummyCaps = SurfaceCaps::Any();
+ RefPtr<GLContextCGL> glContext = new GLContextCGL(flags, dummyCaps, context, true,
+ profile);
+
+ if (gfxPrefs::GLMultithreaded()) {
+ CGLEnable(glContext->GetCGLContext(), kCGLCEMPEngine);
+ }
+ return glContext.forget();
+}
+
+already_AddRefed<GLContext>
+GLContextProviderCGL::CreateHeadless(CreateContextFlags flags,
+ nsACString* const out_failureId)
+{
+ RefPtr<GLContextCGL> gl;
+ gl = CreateOffscreenFBOContext(flags);
+ if (!gl) {
+ *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_CGL_FBO");
+ return nullptr;
+ }
+
+ if (!gl->Init()) {
+ *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_CGL_INIT");
+ NS_WARNING("Failed during Init.");
+ return nullptr;
+ }
+
+ return gl.forget();
+}
+
+already_AddRefed<GLContext>
+GLContextProviderCGL::CreateOffscreen(const IntSize& size,
+ const SurfaceCaps& minCaps,
+ CreateContextFlags flags,
+ nsACString* const out_failureId)
+{
+ RefPtr<GLContext> gl = CreateHeadless(flags, out_failureId);
+ if (!gl) {
+ return nullptr;
+ }
+
+ if (!gl->InitOffscreen(size, minCaps)) {
+ *out_failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_CGL_INIT");
+ return nullptr;
+ }
+
+ return gl.forget();
+}
+
+static RefPtr<GLContext> gGlobalContext;
+
+GLContext*
+GLContextProviderCGL::GetGlobalContext()
+{
+ static bool triedToCreateContext = false;
+ if (!triedToCreateContext) {
+ triedToCreateContext = true;
+
+ MOZ_RELEASE_ASSERT(!gGlobalContext);
+ nsCString discardFailureId;
+ RefPtr<GLContext> temp = CreateHeadless(CreateContextFlags::NONE,
+ &discardFailureId);
+ gGlobalContext = temp;
+
+ if (!gGlobalContext) {
+ NS_WARNING("Couldn't init gGlobalContext.");
+ }
+ }
+
+ return gGlobalContext;
+}
+
+void
+GLContextProviderCGL::Shutdown()
+{
+ gGlobalContext = nullptr;
+}
+
+} /* namespace gl */
+} /* namespace mozilla */
diff --git a/gfx/gl/GLContextProviderEAGL.mm b/gfx/gl/GLContextProviderEAGL.mm
new file mode 100644
index 0000000000..11c7cce776
--- /dev/null
+++ b/gfx/gl/GLContextProviderEAGL.mm
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "GLContextProvider.h"
+#include "GLContextEAGL.h"
+#include "nsDebug.h"
+#include "nsIWidget.h"
+#include "gfxPrefs.h"
+#include "gfxFailure.h"
+#include "prenv.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "GeckoProfiler.h"
+
+#import <UIKit/UIKit.h>
+
+namespace mozilla {
+namespace gl {
+
+using namespace mozilla::widget;
+
+GLContextEAGL::GLContextEAGL(CreateContextFlags flags, const SurfaceCaps& caps,
+ EAGLContext* context, GLContext* sharedContext,
+ bool isOffscreen, ContextProfile profile)
+ : GLContext(flags, caps, sharedContext, isOffscreen)
+ , mContext(context)
+ , mBackbufferRB(0)
+ , mBackbufferFB(0)
+ , mLayer(nil)
+{
+ SetProfileVersion(ContextProfile::OpenGLES,
+ [context API] == kEAGLRenderingAPIOpenGLES3 ? 300 : 200);
+}
+
+GLContextEAGL::~GLContextEAGL()
+{
+ if (MakeCurrent()) {
+ if (mBackbufferFB) {
+ fDeleteFramebuffers(1, &mBackbufferFB);
+ }
+ if (mBackbufferRB) {
+ fDeleteRenderbuffers(1, &mBackbufferRB);
+ }
+ }
+
+ mLayer = nil;
+ MarkDestroyed();
+ [mContext release];
+}
+
+bool
+GLContextEAGL::Init()
+{
+ return InitWithPrefix("gl", true);
+}
+
+bool
+GLContextEAGL::AttachToWindow(nsIWidget* aWidget)
+{
+ // This should only be called once
+ MOZ_ASSERT(!mBackbufferFB && !mBackbufferRB);
+
+ UIView* view =
+ reinterpret_cast<UIView*>(aWidget->GetNativeData(NS_NATIVE_WIDGET));
+
+ if (!view) {
+ MOZ_CRASH("no view!");
+ }
+
+ mLayer = [view layer];
+
+ fGenFramebuffers(1, &mBackbufferFB);
+ return RecreateRB();
+}
+
+bool
+GLContextEAGL::RecreateRB()
+{
+ MakeCurrent();
+
+ CAEAGLLayer* layer = (CAEAGLLayer*)mLayer;
+
+ if (mBackbufferRB) {
+ // It doesn't seem to be enough to just call renderbufferStorage: below,
+ // we apparently have to recreate the RB.
+ fDeleteRenderbuffers(1, &mBackbufferRB);
+ mBackbufferRB = 0;
+ }
+
+ fGenRenderbuffers(1, &mBackbufferRB);
+ fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mBackbufferRB);
+
+ [mContext renderbufferStorage:LOCAL_GL_RENDERBUFFER
+ fromDrawable:layer];
+
+ fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mBackbufferFB);
+ fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
+ LOCAL_GL_RENDERBUFFER, mBackbufferRB);
+
+ return LOCAL_GL_FRAMEBUFFER_COMPLETE == fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+}
+
+bool
+GLContextEAGL::MakeCurrentImpl(bool aForce)
+{
+ if (!aForce && [EAGLContext currentContext] == mContext) {
+ return true;
+ }
+
+ if (IsDestroyed()) {
+ [EAGLContext setCurrentContext:nil];
+ return false;
+ }
+
+ return [EAGLContext setCurrentContext:mContext];
+}
+
+bool
+GLContextEAGL::IsCurrent() {
+ return [EAGLContext currentContext] == mContext;
+}
+
+bool
+GLContextEAGL::SetupLookupFunction()
+{
+ return false;
+}
+
+bool
+GLContextEAGL::IsDoubleBuffered() const
+{
+ return true;
+}
+
+bool
+GLContextEAGL::SwapBuffers()
+{
+ PROFILER_LABEL("GLContextEAGL", "SwapBuffers",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ [mContext presentRenderbuffer:LOCAL_GL_RENDERBUFFER];
+ return true;
+}
+
+void
+GLContextEAGL::GetWSIInfo(nsCString* const out) const
+{
+ out->AppendLiteral("EAGL");
+}
+
+already_AddRefed<GLContext>
+GLContextProviderEAGL::CreateWrappingExisting(void*, void*)
+{
+ return nullptr;
+}
+
+static GLContextEAGL*
+GetGlobalContextEAGL()
+{
+ return static_cast<GLContextEAGL*>(GLContextProviderEAGL::GetGlobalContext());
+}
+
+static already_AddRefed<GLContext>
+CreateEAGLContext(CreateContextFlags flags, bool aOffscreen, GLContextEAGL* sharedContext)
+{
+ EAGLRenderingAPI apis[] = { kEAGLRenderingAPIOpenGLES3, kEAGLRenderingAPIOpenGLES2 };
+
+ // Try to create a GLES3 context if we can, otherwise fall back to GLES2
+ EAGLContext* context = nullptr;
+ for (EAGLRenderingAPI api : apis) {
+ if (sharedContext) {
+ context = [[EAGLContext alloc] initWithAPI:api
+ sharegroup:sharedContext->GetEAGLContext().sharegroup];
+ } else {
+ context = [[EAGLContext alloc] initWithAPI:api];
+ }
+
+ if (context) {
+ break;
+ }
+ }
+
+ if (!context) {
+ return nullptr;
+ }
+
+ SurfaceCaps caps = SurfaceCaps::ForRGBA();
+ ContextProfile profile = ContextProfile::OpenGLES;
+ RefPtr<GLContextEAGL> glContext = new GLContextEAGL(flags, caps, context,
+ sharedContext,
+ aOffscreen,
+ profile);
+
+ if (!glContext->Init()) {
+ glContext = nullptr;
+ return nullptr;
+ }
+
+ return glContext.forget();
+}
+
+already_AddRefed<GLContext>
+GLContextProviderEAGL::CreateForCompositorWidget(CompositorWidget* aCompositorWidget, bool aForceAccelerated)
+{
+ return CreateForWindow(aCompositorWidget->RealWidget(), aForceAccelerated);
+}
+
+already_AddRefed<GLContext>
+GLContextProviderEAGL::CreateForWindow(nsIWidget* aWidget, bool aForceAccelerated)
+{
+ RefPtr<GLContext> glContext = CreateEAGLContext(CreateContextFlags::NONE, false,
+ GetGlobalContextEAGL());
+ if (!glContext) {
+ return nullptr;
+ }
+
+ if (!GLContextEAGL::Cast(glContext)->AttachToWindow(aWidget)) {
+ return nullptr;
+ }
+
+ return glContext.forget();
+}
+
+already_AddRefed<GLContext>
+GLContextProviderEAGL::CreateHeadless(CreateContextFlags flags,
+ nsACString* const out_failureId)
+{
+ return CreateEAGLContext(flags, true, GetGlobalContextEAGL());
+}
+
+already_AddRefed<GLContext>
+GLContextProviderEAGL::CreateOffscreen(const mozilla::gfx::IntSize& size,
+ const SurfaceCaps& caps,
+ CreateContextFlags flags,
+ nsACString* const out_failureId)
+{
+ RefPtr<GLContext> glContext = CreateHeadless(flags, out_failureId);
+ if (!glContext->InitOffscreen(size, caps)) {
+ return nullptr;
+ }
+
+ return glContext.forget();
+}
+
+static RefPtr<GLContext> gGlobalContext;
+
+GLContext*
+GLContextProviderEAGL::GetGlobalContext()
+{
+ static bool triedToCreateContext = false;
+ if (!triedToCreateContext) {
+ triedToCreateContext = true;
+
+ MOZ_RELEASE_ASSERT(!gGlobalContext, "GFX: Global GL context already initialized.");
+ RefPtr<GLContext> temp = CreateHeadless(CreateContextFlags::NONE);
+ gGlobalContext = temp;
+
+ if (!gGlobalContext) {
+ MOZ_CRASH("Failed to create global context");
+ }
+ }
+
+ return gGlobalContext;
+}
+
+void
+GLContextProviderEAGL::Shutdown()
+{
+ gGlobalContext = nullptr;
+}
+
+} /* namespace gl */
+} /* namespace mozilla */
diff --git a/gfx/gl/GLScreenBuffer.cpp b/gfx/gl/GLScreenBuffer.cpp
index 0ac0322be4..5d95eb9285 100755
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -24,6 +24,10 @@
#include "mozilla/gfx/DeviceManagerDx.h"
#endif
+#ifdef XP_MACOSX
+#include "SharedSurfaceIO.h"
+#endif
+
#ifdef GL_PROVIDER_GLX
#include "GLXLibrary.h"
#include "SharedSurfaceGLX.h"
@@ -80,7 +84,9 @@ GLScreenBuffer::CreateFactory(GLContext* gl,
if (!gfxPrefs::WebGLForceLayersReadback()) {
switch (backend) {
case mozilla::layers::LayersBackend::LAYERS_OPENGL: {
-#if defined(GL_PROVIDER_GLX)
+#if defined(XP_MACOSX)
+ factory = SurfaceFactory_IOSurface::Create(gl, caps, ipcChannel, flags);
+#elif defined(GL_PROVIDER_GLX)
if (sGLXLibrary.UseTextureFromPixmap())
factory = SurfaceFactory_GLXDrawable::Create(gl, caps, ipcChannel, flags);
#elif defined(MOZ_WIDGET_UIKIT)
diff --git a/gfx/gl/SharedSurfaceIO.cpp b/gfx/gl/SharedSurfaceIO.cpp
new file mode 100644
index 0000000000..50262d6cce
--- /dev/null
+++ b/gfx/gl/SharedSurfaceIO.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
+/* 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 "SharedSurfaceIO.h"
+
+#include "GLContextCGL.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc
+#include "ScopedGLHelpers.h"
+
+namespace mozilla {
+namespace gl {
+
+/*static*/ UniquePtr<SharedSurface_IOSurface>
+SharedSurface_IOSurface::Create(const RefPtr<MacIOSurface>& ioSurf,
+ GLContext* gl,
+ bool hasAlpha)
+{
+ MOZ_ASSERT(ioSurf);
+ MOZ_ASSERT(gl);
+
+ auto size = gfx::IntSize::Truncate(ioSurf->GetWidth(), ioSurf->GetHeight());
+
+ typedef SharedSurface_IOSurface ptrT;
+ UniquePtr<ptrT> ret( new ptrT(ioSurf, gl, size, hasAlpha) );
+ return Move(ret);
+}
+
+void
+SharedSurface_IOSurface::ProducerReleaseImpl()
+{
+ mGL->MakeCurrent();
+ mGL->fFlush();
+}
+
+bool
+SharedSurface_IOSurface::CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
+ GLint x, GLint y, GLsizei width, GLsizei height,
+ GLint border)
+{
+ /* Bug 896693 - OpenGL framebuffers that are backed by IOSurface on OSX expose a bug
+ * in glCopyTexImage2D --- internalformats GL_ALPHA, GL_LUMINANCE, GL_LUMINANCE_ALPHA
+ * return the wrong results. To work around, copy framebuffer to a temporary texture
+ * using GL_RGBA (which works), attach as read framebuffer and glCopyTexImage2D
+ * instead.
+ */
+
+ // https://www.opengl.org/sdk/docs/man3/xhtml/glCopyTexImage2D.xml says that width or
+ // height set to 0 results in a NULL texture. Lets not do any work and punt to
+ // original glCopyTexImage2D, since the FBO below will fail when trying to attach a
+ // texture of 0 width or height.
+ if (width == 0 || height == 0)
+ return false;
+
+ switch (internalformat) {
+ case LOCAL_GL_ALPHA:
+ case LOCAL_GL_LUMINANCE:
+ case LOCAL_GL_LUMINANCE_ALPHA:
+ break;
+
+ default:
+ return false;
+ }
+
+ MOZ_ASSERT(mGL->IsCurrent());
+
+ ScopedTexture destTex(mGL);
+ {
+ ScopedBindTexture bindTex(mGL, destTex.Texture());
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_NEAREST);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_NEAREST);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ mGL->raw_fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, x, y, width,
+ height, 0);
+ }
+
+ ScopedFramebufferForTexture tmpFB(mGL, destTex.Texture(), LOCAL_GL_TEXTURE_2D);
+ ScopedBindFramebuffer bindFB(mGL, tmpFB.FB());
+ mGL->raw_fCopyTexImage2D(target, level, internalformat, x, y, width, height, border);
+
+ return true;
+}
+
+bool
+SharedSurface_IOSurface::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
+ GLenum format, GLenum type, GLvoid* pixels)
+{
+ // Calling glReadPixels when an IOSurface is bound to the current framebuffer
+ // can cause corruption in following glReadPixel calls (even if they aren't
+ // reading from an IOSurface).
+ // We workaround this by copying to a temporary texture, and doing the readback
+ // from that.
+ MOZ_ASSERT(mGL->IsCurrent());
+
+ ScopedTexture destTex(mGL);
+ {
+ ScopedFramebufferForTexture srcFB(mGL, ProdTexture(), ProdTextureTarget());
+
+ ScopedBindFramebuffer bindFB(mGL, srcFB.FB());
+ ScopedBindTexture bindTex(mGL, destTex.Texture());
+ mGL->raw_fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0,
+ mHasAlpha ? LOCAL_GL_RGBA : LOCAL_GL_RGB,
+ x, y,
+ width, height, 0);
+ }
+
+ ScopedFramebufferForTexture destFB(mGL, destTex.Texture());
+
+ ScopedBindFramebuffer bindFB(mGL, destFB.FB());
+ mGL->raw_fReadPixels(0, 0, width, height, format, type, pixels);
+ return true;
+}
+
+static void
+BackTextureWithIOSurf(GLContext* gl, GLuint tex, MacIOSurface* ioSurf)
+{
+ MOZ_ASSERT(gl->IsCurrent());
+
+ ScopedBindTexture texture(gl, tex, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+
+ CGLContextObj cgl = GLContextCGL::Cast(gl)->GetCGLContext();
+ MOZ_ASSERT(cgl);
+
+ ioSurf->CGLTexImageIOSurface2D(cgl);
+}
+
+SharedSurface_IOSurface::SharedSurface_IOSurface(const RefPtr<MacIOSurface>& ioSurf,
+ GLContext* gl,
+ const gfx::IntSize& size,
+ bool hasAlpha)
+ : SharedSurface(SharedSurfaceType::IOSurface,
+ AttachmentType::GLTexture,
+ gl,
+ size,
+ hasAlpha,
+ true)
+ , mIOSurf(ioSurf)
+{
+ gl->MakeCurrent();
+ mProdTex = 0;
+ gl->fGenTextures(1, &mProdTex);
+ BackTextureWithIOSurf(gl, mProdTex, mIOSurf);
+}
+
+SharedSurface_IOSurface::~SharedSurface_IOSurface()
+{
+ if (!mGL || !mGL->MakeCurrent())
+ return;
+
+ mGL->fDeleteTextures(1, &mProdTex);
+}
+
+bool
+SharedSurface_IOSurface::ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor)
+{
+ bool isOpaque = !mHasAlpha;
+ *out_descriptor = layers::SurfaceDescriptorMacIOSurface(mIOSurf->GetIOSurfaceID(),
+ mIOSurf->GetContentsScaleFactor(),
+ isOpaque);
+ return true;
+}
+
+bool
+SharedSurface_IOSurface::ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface)
+{
+ MOZ_ASSERT(out_surface);
+ mIOSurf->Lock();
+ size_t bytesPerRow = mIOSurf->GetBytesPerRow();
+ size_t ioWidth = mIOSurf->GetDevicePixelWidth();
+ size_t ioHeight = mIOSurf->GetDevicePixelHeight();
+
+ const unsigned char* ioData = (unsigned char*)mIOSurf->GetBaseAddress();
+ gfx::DataSourceSurface::ScopedMap map(out_surface, gfx::DataSourceSurface::WRITE);
+ if (!map.IsMapped()) {
+ mIOSurf->Unlock();
+ return false;
+ }
+
+ for (size_t i = 0; i < ioHeight; i++) {
+ memcpy(map.GetData() + i * map.GetStride(),
+ ioData + i * bytesPerRow, ioWidth * 4);
+ }
+
+ mIOSurf->Unlock();
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////
+// SurfaceFactory_IOSurface
+
+/*static*/ UniquePtr<SurfaceFactory_IOSurface>
+SurfaceFactory_IOSurface::Create(GLContext* gl, const SurfaceCaps& caps,
+ const RefPtr<layers::LayersIPCChannel>& allocator,
+ const layers::TextureFlags& flags)
+{
+ auto maxDims = gfx::IntSize::Truncate(MacIOSurface::GetMaxWidth(),
+ MacIOSurface::GetMaxHeight());
+
+ typedef SurfaceFactory_IOSurface ptrT;
+ UniquePtr<ptrT> ret( new ptrT(gl, caps, allocator, flags, maxDims) );
+ return Move(ret);
+}
+
+UniquePtr<SharedSurface>
+SurfaceFactory_IOSurface::CreateShared(const gfx::IntSize& size)
+{
+ if (size.width > mMaxDims.width ||
+ size.height > mMaxDims.height)
+ {
+ return nullptr;
+ }
+
+ bool hasAlpha = mReadCaps.alpha;
+ RefPtr<MacIOSurface> ioSurf;
+ ioSurf = MacIOSurface::CreateIOSurface(size.width, size.height, 1.0,
+ hasAlpha);
+
+ if (!ioSurf) {
+ NS_WARNING("Failed to create MacIOSurface.");
+ return nullptr;
+ }
+
+ return SharedSurface_IOSurface::Create(ioSurf, mGL, hasAlpha);
+}
+
+} // namespace gl
+} // namespace mozilla
diff --git a/gfx/gl/SharedSurfaceIO.h b/gfx/gl/SharedSurfaceIO.h
new file mode 100644
index 0000000000..16e0cbbb7f
--- /dev/null
+++ b/gfx/gl/SharedSurfaceIO.h
@@ -0,0 +1,100 @@
+/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
+/* 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/. */
+
+#ifndef SHARED_SURFACEIO_H_
+#define SHARED_SURFACEIO_H_
+
+#include "mozilla/RefPtr.h"
+#include "SharedSurface.h"
+
+class MacIOSurface;
+
+namespace mozilla {
+namespace gl {
+
+class SharedSurface_IOSurface : public SharedSurface
+{
+private:
+ const RefPtr<MacIOSurface> mIOSurf;
+ GLuint mProdTex;
+
+public:
+ static UniquePtr<SharedSurface_IOSurface> Create(const RefPtr<MacIOSurface>& ioSurf,
+ GLContext* gl,
+ bool hasAlpha);
+
+private:
+ SharedSurface_IOSurface(const RefPtr<MacIOSurface>& ioSurf,
+ GLContext* gl, const gfx::IntSize& size,
+ bool hasAlpha);
+
+public:
+ ~SharedSurface_IOSurface();
+
+ virtual void LockProdImpl() override { }
+ virtual void UnlockProdImpl() override { }
+
+ virtual void ProducerAcquireImpl() override {}
+ virtual void ProducerReleaseImpl() override;
+
+ virtual bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
+ GLint x, GLint y, GLsizei width, GLsizei height,
+ GLint border) override;
+ virtual bool ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
+ GLenum format, GLenum type, GLvoid* pixels) override;
+
+ virtual GLuint ProdTexture() override {
+ return mProdTex;
+ }
+
+ virtual GLenum ProdTextureTarget() const override {
+ return LOCAL_GL_TEXTURE_RECTANGLE_ARB;
+ }
+
+ static SharedSurface_IOSurface* Cast(SharedSurface* surf) {
+ MOZ_ASSERT(surf->mType == SharedSurfaceType::IOSurface);
+ return static_cast<SharedSurface_IOSurface*>(surf);
+ }
+
+ MacIOSurface* GetIOSurface() const {
+ return mIOSurf;
+ }
+
+ virtual bool NeedsIndirectReads() const override {
+ return true;
+ }
+
+ virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) override;
+
+ virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) override;
+};
+
+class SurfaceFactory_IOSurface : public SurfaceFactory
+{
+public:
+ // Infallible.
+ static UniquePtr<SurfaceFactory_IOSurface> Create(GLContext* gl,
+ const SurfaceCaps& caps,
+ const RefPtr<layers::LayersIPCChannel>& allocator,
+ const layers::TextureFlags& flags);
+protected:
+ const gfx::IntSize mMaxDims;
+
+ SurfaceFactory_IOSurface(GLContext* gl, const SurfaceCaps& caps,
+ const RefPtr<layers::LayersIPCChannel>& allocator,
+ const layers::TextureFlags& flags,
+ const gfx::IntSize& maxDims)
+ : SurfaceFactory(SharedSurfaceType::IOSurface, gl, caps, allocator, flags)
+ , mMaxDims(maxDims)
+ { }
+
+ virtual UniquePtr<SharedSurface> CreateShared(const gfx::IntSize& size) override;
+};
+
+} // namespace gl
+
+} /* namespace mozilla */
+
+#endif /* SHARED_SURFACEIO_H_ */
diff --git a/gfx/gl/moz.build b/gfx/gl/moz.build
index 7bf5a4ab04..c490400cbf 100644
--- a/gfx/gl/moz.build
+++ b/gfx/gl/moz.build
@@ -7,6 +7,10 @@ gl_provider = 'Null'
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
gl_provider = 'WGL'
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ gl_provider = 'CGL'
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
+ gl_provider = 'EAGL'
elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
if CONFIG['MOZ_EGL_XRENDER_COMPOSITE']:
gl_provider = 'EGL'
@@ -19,6 +23,7 @@ if CONFIG['MOZ_GL_PROVIDER']:
EXPORTS += [
'DecomposeIntoNoRepeatTriangles.h',
'EGLUtils.h',
+ 'ForceDiscreteGPUHelperCGL.h',
'GfxTexturesReporter.h',
'GLBlitHelper.h',
'GLConsts.h',
@@ -73,7 +78,32 @@ if CONFIG['MOZ_ENABLE_SKIA_GPU']:
# Suppress warnings from Skia header files.
SOURCES['SkiaGLGlue.cpp'].flags += ['-Wno-implicit-fallthrough']
-if gl_provider == 'GLX':
+if gl_provider == 'CGL':
+ # These files include Mac headers that are unfriendly to unified builds
+ SOURCES += [
+ "GLContextProviderCGL.mm",
+ ]
+ EXPORTS += [
+ 'GLContextCGL.h',
+ 'SharedSurfaceIO.h',
+ ]
+ # SharedSurfaceIO.cpp includes MacIOSurface.h which include Mac headers
+ # which define Size and Point types in root namespace with often conflict with
+ # our own types. While I haven't actually hit this issue in the present case,
+ # it's been an issue in gfx/layers so let's not risk it.
+ SOURCES += [
+ 'SharedSurfaceIO.cpp',
+ ]
+elif gl_provider == 'EAGL':
+ # These files include ObjC headers that are unfriendly to unified builds
+ SOURCES += [
+ 'GLContextProviderEAGL.mm',
+ ]
+ EXPORTS += [
+ 'GLContextEAGL.h',
+ ]
+
+elif gl_provider == 'GLX':
# GLContextProviderGLX.cpp needs to be kept out of UNIFIED_SOURCES
# as it includes X11 headers which cause conflicts.
SOURCES += [
diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp
index ecf2d10b41..5e4019e861 100644
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -28,6 +28,10 @@
#include "mozilla/gfx/2D.h"
#include "mozilla/CheckedInt.h"
+#ifdef XP_MACOSX
+#include "mozilla/gfx/QuartzSupport.h"
+#endif
+
#ifdef XP_WIN
#include "gfxWindowsPlatform.h"
#include <d3d10_1.h>
diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h
index ea619b77b5..73085ba906 100644
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -166,6 +166,9 @@ protected:
class GLImage;
class EGLImageImage;
class SharedRGBImage;
+#if defined(XP_MACOSX)
+class MacIOSurfaceImage;
+#endif
/**
* A class representing a buffer of pixel data. The data can be in one
@@ -220,6 +223,9 @@ public:
/* Access to derived classes. */
virtual EGLImageImage* AsEGLImageImage() { return nullptr; }
virtual GLImage* AsGLImage() { return nullptr; }
+#ifdef XP_MACOSX
+ virtual MacIOSurfaceImage* AsMacIOSurfaceImage() { return nullptr; }
+#endif
virtual PlanarYCbCrImage* AsPlanarYCbCrImage() { return nullptr; }
virtual NVImage* AsNVImage() { return nullptr; }
diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp
new file mode 100644
index 0000000000..8834773e4c
--- /dev/null
+++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp
@@ -0,0 +1,107 @@
+/* -*- 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 "MacIOSurfaceTextureHostBasic.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "MacIOSurfaceHelpers.h"
+
+namespace mozilla {
+namespace layers {
+
+MacIOSurfaceTextureSourceBasic::MacIOSurfaceTextureSourceBasic(
+ BasicCompositor* aCompositor,
+ MacIOSurface* aSurface)
+ : mCompositor(aCompositor)
+ , mSurface(aSurface)
+{
+ MOZ_COUNT_CTOR(MacIOSurfaceTextureSourceBasic);
+}
+
+MacIOSurfaceTextureSourceBasic::~MacIOSurfaceTextureSourceBasic()
+{
+ MOZ_COUNT_DTOR(MacIOSurfaceTextureSourceBasic);
+}
+
+gfx::IntSize
+MacIOSurfaceTextureSourceBasic::GetSize() const
+{
+ return gfx::IntSize(mSurface->GetDevicePixelWidth(),
+ mSurface->GetDevicePixelHeight());
+}
+
+gfx::SurfaceFormat
+MacIOSurfaceTextureSourceBasic::GetFormat() const
+{
+ // Set the format the same way as CreateSourceSurfaceFromMacIOSurface.
+ return mSurface->GetFormat() == gfx::SurfaceFormat::NV12
+ ? gfx::SurfaceFormat::B8G8R8X8 : gfx::SurfaceFormat::B8G8R8A8;
+}
+
+MacIOSurfaceTextureHostBasic::MacIOSurfaceTextureHostBasic(
+ TextureFlags aFlags,
+ const SurfaceDescriptorMacIOSurface& aDescriptor
+)
+ : TextureHost(aFlags)
+{
+ mSurface = MacIOSurface::LookupSurface(aDescriptor.surfaceId(),
+ aDescriptor.scaleFactor(),
+ !aDescriptor.isOpaque());
+}
+
+gfx::SourceSurface*
+MacIOSurfaceTextureSourceBasic::GetSurface(gfx::DrawTarget* aTarget)
+{
+ if (!mSourceSurface) {
+ mSourceSurface = CreateSourceSurfaceFromMacIOSurface(mSurface);
+ }
+ return mSourceSurface;
+}
+
+void
+MacIOSurfaceTextureSourceBasic::SetCompositor(Compositor* aCompositor)
+{
+ mCompositor = AssertBasicCompositor(aCompositor);
+}
+
+bool
+MacIOSurfaceTextureHostBasic::Lock()
+{
+ if (!mCompositor) {
+ return false;
+ }
+
+ if (!mTextureSource) {
+ mTextureSource = new MacIOSurfaceTextureSourceBasic(mCompositor, mSurface);
+ }
+ return true;
+}
+
+void
+MacIOSurfaceTextureHostBasic::SetCompositor(Compositor* aCompositor)
+{
+ BasicCompositor* compositor = AssertBasicCompositor(aCompositor);
+ if (!compositor) {
+ mTextureSource = nullptr;
+ return;
+ }
+ mCompositor = compositor;
+ if (mTextureSource) {
+ mTextureSource->SetCompositor(compositor);
+ }
+}
+
+gfx::SurfaceFormat
+MacIOSurfaceTextureHostBasic::GetFormat() const {
+ return mSurface->HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::B8G8R8X8;
+}
+
+gfx::IntSize
+MacIOSurfaceTextureHostBasic::GetSize() const {
+ return gfx::IntSize(mSurface->GetDevicePixelWidth(),
+ mSurface->GetDevicePixelHeight());
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h
new file mode 100644
index 0000000000..7949aecfca
--- /dev/null
+++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h
@@ -0,0 +1,97 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_MACIOSURFACETEXTUREHOST_BASIC_H
+#define MOZILLA_GFX_MACIOSURFACETEXTUREHOST_BASIC_H
+
+#include "mozilla/layers/BasicCompositor.h"
+#include "mozilla/layers/TextureHostBasic.h"
+
+class MacIOSurface;
+
+namespace mozilla {
+namespace layers {
+
+class BasicCompositor;
+
+/**
+ * A texture source meant for use with BasicCompositor.
+ *
+ * It does not own any GL texture, and attaches its shared handle to one of
+ * the compositor's temporary textures when binding.
+ */
+class MacIOSurfaceTextureSourceBasic
+ : public TextureSourceBasic,
+ public TextureSource
+{
+public:
+ MacIOSurfaceTextureSourceBasic(BasicCompositor* aCompositor,
+ MacIOSurface* aSurface);
+ virtual ~MacIOSurfaceTextureSourceBasic();
+
+ virtual const char* Name() const override { return "MacIOSurfaceTextureSourceBasic"; }
+
+ virtual TextureSourceBasic* AsSourceBasic() override { return this; }
+
+ virtual gfx::IntSize GetSize() const override;
+ virtual gfx::SurfaceFormat GetFormat() const override;
+ virtual gfx::SourceSurface* GetSurface(gfx::DrawTarget* aTarget) override;
+
+ virtual void DeallocateDeviceData() override { }
+
+ virtual void SetCompositor(Compositor* aCompositor) override;
+
+protected:
+ RefPtr<BasicCompositor> mCompositor;
+ RefPtr<MacIOSurface> mSurface;
+ RefPtr<gfx::SourceSurface> mSourceSurface;
+};
+
+/**
+ * A TextureHost for shared MacIOSurface
+ *
+ * Most of the logic actually happens in MacIOSurfaceTextureSourceBasic.
+ */
+class MacIOSurfaceTextureHostBasic : public TextureHost
+{
+public:
+ MacIOSurfaceTextureHostBasic(TextureFlags aFlags,
+ const SurfaceDescriptorMacIOSurface& aDescriptor);
+
+ virtual void SetCompositor(Compositor* aCompositor) override;
+
+ virtual Compositor* GetCompositor() override { return mCompositor; }
+
+ virtual bool Lock() override;
+
+ virtual gfx::SurfaceFormat GetFormat() const override;
+
+ virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) override
+ {
+ aTexture = mTextureSource;
+ return !!aTexture;
+ }
+
+ virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override
+ {
+ return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING)
+ }
+
+ virtual gfx::IntSize GetSize() const override;
+
+#ifdef MOZ_LAYERS_HAVE_LOG
+ virtual const char* Name() override { return "MacIOSurfaceTextureHostBasic"; }
+#endif
+
+protected:
+ RefPtr<BasicCompositor> mCompositor;
+ RefPtr<MacIOSurfaceTextureSourceBasic> mTextureSource;
+ RefPtr<MacIOSurface> mSurface;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_BASIC_H
diff --git a/gfx/layers/basic/TextureHostBasic.cpp b/gfx/layers/basic/TextureHostBasic.cpp
index 1d671729e6..8058e43efc 100644
--- a/gfx/layers/basic/TextureHostBasic.cpp
+++ b/gfx/layers/basic/TextureHostBasic.cpp
@@ -4,6 +4,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "TextureHostBasic.h"
+#ifdef XP_MACOSX
+#include "MacIOSurfaceTextureHostBasic.h"
+#endif
using namespace mozilla::gl;
using namespace mozilla::gfx;
@@ -16,6 +19,13 @@ CreateTextureHostBasic(const SurfaceDescriptor& aDesc,
ISurfaceAllocator* aDeallocator,
TextureFlags aFlags)
{
+#ifdef XP_MACOSX
+ if (aDesc.type() == SurfaceDescriptor::TSurfaceDescriptorMacIOSurface) {
+ const SurfaceDescriptorMacIOSurface& desc =
+ aDesc.get_SurfaceDescriptorMacIOSurface();
+ return MakeAndAddRef<MacIOSurfaceTextureHostBasic>(aFlags, desc);
+ }
+#endif
return CreateBackendIndependentTextureHost(aDesc, aDeallocator, aFlags);
}
diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp
index e8139e9a92..33082fea47 100644
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -46,6 +46,10 @@
#endif
#endif
+#ifdef XP_MACOSX
+#include "mozilla/layers/MacIOSurfaceTextureClientOGL.h"
+#endif
+
#if 0
#define RECYCLE_LOG(...) printf_stderr(__VA_ARGS__)
#else
@@ -1060,6 +1064,12 @@ TextureClient::CreateForDrawing(TextureForwarder* aAllocator,
#endif
#endif
+#ifdef XP_MACOSX
+ if (!data && gfxPrefs::UseIOSurfaceTextures()) {
+ data = MacIOSurfaceTextureData::Create(aSize, aFormat, moz2DBackend);
+ }
+#endif
+
if (data) {
return MakeAndAddRef<TextureClient>(data, aTextureFlags, aAllocator);
}
diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp
index 98bc777e4e..0ee11bdfb1 100644
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -24,6 +24,9 @@
#include "UnitTransforms.h" // for ViewAs
#include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController
#include "gfxPrefs.h" // for gfxPrefs
+#ifdef XP_MACOSX
+#include "gfxPlatformMac.h"
+#endif
#include "gfxRect.h" // for gfxRect
#include "gfxUtils.h" // for frame color util
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
@@ -878,6 +881,9 @@ LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, const nsIntRegi
}
mozilla::widget::WidgetRenderingContext widgetContext;
+#if defined(XP_MACOSX)
+ widgetContext.mLayerManager = this;
+#endif
{
PROFILER_LABEL("LayerManagerComposite", "PreRender",
diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp
index d550945ef6..768bf0c3d2 100644
--- a/gfx/layers/composite/TextureHost.cpp
+++ b/gfx/layers/composite/TextureHost.cpp
@@ -39,6 +39,10 @@
#include "mozilla/layers/X11TextureHost.h"
#endif
+#ifdef XP_MACOSX
+#include "../opengl/MacIOSurfaceTextureHostOGL.h"
+#endif
+
#ifdef XP_WIN
#include "mozilla/layers/TextureDIB.h"
#endif
diff --git a/gfx/layers/ipc/ShadowLayerUtils.h b/gfx/layers/ipc/ShadowLayerUtils.h
index faa5041e3e..352d2aa930 100644
--- a/gfx/layers/ipc/ShadowLayerUtils.h
+++ b/gfx/layers/ipc/ShadowLayerUtils.h
@@ -11,6 +11,10 @@
#include "SurfaceTypes.h"
#include "mozilla/WidgetUtils.h"
+#if defined(XP_MACOSX)
+#define MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS
+#endif
+
#if defined(MOZ_X11)
# include "mozilla/layers/ShadowLayerUtilsX11.h"
#else
diff --git a/gfx/layers/ipc/ShadowLayerUtilsMac.cpp b/gfx/layers/ipc/ShadowLayerUtilsMac.cpp
new file mode 100644
index 0000000000..cc5199ea82
--- /dev/null
+++ b/gfx/layers/ipc/ShadowLayerUtilsMac.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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 "mozilla/gfx/Point.h"
+#include "mozilla/layers/PLayerTransaction.h"
+#include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/CompositorTypes.h"
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+
+using namespace mozilla::gl;
+
+namespace mozilla {
+namespace layers {
+
+/*static*/ void
+ShadowLayerForwarder::PlatformSyncBeforeUpdate()
+{
+}
+
+/*static*/ void
+LayerManagerComposite::PlatformSyncBeforeReplyUpdate()
+{
+}
+
+/*static*/ bool
+LayerManagerComposite::SupportsDirectTexturing()
+{
+ return false;
+}
+
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build
index 50c2b8f855..be0c95ba20 100644
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -104,6 +104,7 @@ EXPORTS.mozilla.layers += [
'AxisPhysicsModel.h',
'AxisPhysicsMSDModel.h',
'basic/BasicCompositor.h',
+ 'basic/MacIOSurfaceTextureHostBasic.h',
'basic/TextureHostBasic.h',
'BSPTree.h',
'BufferTexture.h',
@@ -172,6 +173,8 @@ EXPORTS.mozilla.layers += [
'LayersTypes.h',
'opengl/CompositingRenderTargetOGL.h',
'opengl/CompositorOGL.h',
+ 'opengl/MacIOSurfaceTextureClientOGL.h',
+ 'opengl/MacIOSurfaceTextureHostOGL.h',
'opengl/TextureClientOGL.h',
'opengl/TextureHostOGL.h',
'PersistentBufferProvider.h',
@@ -197,6 +200,21 @@ if CONFIG['MOZ_X11']:
'opengl/X11TextureSourceOGL.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ EXPORTS.mozilla.layers += [
+ 'opengl/GLManager.h',
+ ]
+ EXPORTS += [
+ 'MacIOSurfaceHelpers.h',
+ 'MacIOSurfaceImage.h',
+ ]
+ SOURCES += [
+ 'ipc/ShadowLayerUtilsMac.cpp',
+ 'MacIOSurfaceHelpers.cpp',
+ 'MacIOSurfaceImage.cpp',
+ 'opengl/GLManager.cpp',
+ ]
+
SOURCES += [
'apz/public/IAPZCTreeManager.cpp',
'apz/src/APZCTreeManager.cpp',
@@ -348,6 +366,13 @@ if CONFIG['_MSC_VER'] and CONFIG['CPU_ARCH'] == 'x86_64':
]:
SOURCES[src].no_pgo = True
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'basic/MacIOSurfaceTextureHostBasic.cpp',
+ 'opengl/MacIOSurfaceTextureClientOGL.cpp',
+ 'opengl/MacIOSurfaceTextureHostOGL.cpp',
+ ]
+
IPDL_SOURCES = [
'ipc/LayersMessages.ipdlh',
'ipc/LayersSurfaces.ipdlh',
diff --git a/gfx/layers/opengl/GLManager.cpp b/gfx/layers/opengl/GLManager.cpp
new file mode 100644
index 0000000000..e4c0b361fb
--- /dev/null
+++ b/gfx/layers/opengl/GLManager.cpp
@@ -0,0 +1,70 @@
+/* -*- 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 "GLManager.h"
+#include "CompositorOGL.h" // for CompositorOGL
+#include "GLContext.h" // for GLContext
+#include "mozilla/Attributes.h" // for override
+#include "mozilla/RefPtr.h" // for RefPtr
+#include "mozilla/layers/Compositor.h" // for Compositor
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/mozalloc.h" // for operator new, etc
+
+using namespace mozilla::gl;
+
+namespace mozilla {
+namespace layers {
+
+class GLManagerCompositor : public GLManager
+{
+public:
+ explicit GLManagerCompositor(CompositorOGL* aCompositor)
+ : mImpl(aCompositor)
+ {}
+
+ virtual GLContext* gl() const override
+ {
+ return mImpl->gl();
+ }
+
+ virtual void ActivateProgram(ShaderProgramOGL *aProg) override
+ {
+ mImpl->ActivateProgram(aProg);
+ }
+
+ virtual ShaderProgramOGL* GetProgram(GLenum aTarget, gfx::SurfaceFormat aFormat) override
+ {
+ ShaderConfigOGL config = ShaderConfigFromTargetAndFormat(aTarget, aFormat);
+ return mImpl->GetShaderProgramFor(config);
+ }
+
+ virtual const gfx::Matrix4x4& GetProjMatrix() const override
+ {
+ return mImpl->GetProjMatrix();
+ }
+
+ virtual void BindAndDrawQuad(ShaderProgramOGL *aProg,
+ const gfx::Rect& aLayerRect,
+ const gfx::Rect& aTextureRect) override
+ {
+ mImpl->BindAndDrawQuad(aProg, aLayerRect, aTextureRect);
+ }
+
+private:
+ RefPtr<CompositorOGL> mImpl;
+};
+
+/* static */ GLManager*
+GLManager::CreateGLManager(LayerManagerComposite* aManager)
+{
+ if (aManager && aManager->GetCompositor()->GetBackendType() == LayersBackend::LAYERS_OPENGL) {
+ return new GLManagerCompositor(aManager->GetCompositor()->AsCompositorOGL());
+ }
+ return nullptr;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/opengl/GLManager.h b/gfx/layers/opengl/GLManager.h
new file mode 100644
index 0000000000..7c872758e9
--- /dev/null
+++ b/gfx/layers/opengl/GLManager.h
@@ -0,0 +1,44 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_GLMANAGER_H
+#define MOZILLA_GFX_GLMANAGER_H
+
+#include "mozilla/gfx/Types.h" // for SurfaceFormat
+#include "OGLShaderProgram.h"
+
+namespace mozilla {
+namespace gl {
+class GLContext;
+} // namespace gl
+
+namespace layers {
+
+class LayerManagerComposite;
+
+/**
+ * Minimal interface to allow widgets to draw using OpenGL. Abstracts
+ * CompositorOGL. Call CreateGLManager with a LayerManagerComposite
+ * backed by a CompositorOGL.
+ */
+class GLManager
+{
+public:
+ static GLManager* CreateGLManager(LayerManagerComposite* aManager);
+
+ virtual ~GLManager() {}
+
+ virtual gl::GLContext* gl() const = 0;
+ virtual ShaderProgramOGL* GetProgram(GLenum aTarget, gfx::SurfaceFormat aFormat) = 0;
+ virtual void ActivateProgram(ShaderProgramOGL* aPrg) = 0;
+ virtual const gfx::Matrix4x4& GetProjMatrix() const = 0;
+ virtual void BindAndDrawQuad(ShaderProgramOGL *aProg, const gfx::Rect& aLayerRect,
+ const gfx::Rect& aTextureRect) = 0;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
new file mode 100644
index 0000000000..dd522e6503
--- /dev/null
+++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
@@ -0,0 +1,140 @@
+/* -*- 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 "MacIOSurfaceTextureClientOGL.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "MacIOSurfaceHelpers.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace layers {
+
+using namespace gfx;
+
+MacIOSurfaceTextureData::MacIOSurfaceTextureData(MacIOSurface* aSurface,
+ BackendType aBackend)
+ : mSurface(aSurface)
+ , mBackend(aBackend)
+{
+ MOZ_ASSERT(mSurface);
+}
+
+MacIOSurfaceTextureData::~MacIOSurfaceTextureData()
+{}
+
+// static
+MacIOSurfaceTextureData*
+MacIOSurfaceTextureData::Create(MacIOSurface* aSurface, BackendType aBackend)
+{
+ MOZ_ASSERT(aSurface);
+ if (!aSurface) {
+ return nullptr;
+ }
+ return new MacIOSurfaceTextureData(aSurface, aBackend);
+}
+
+MacIOSurfaceTextureData*
+MacIOSurfaceTextureData::Create(const IntSize& aSize,
+ SurfaceFormat aFormat,
+ BackendType aBackend)
+{
+ if (aFormat != SurfaceFormat::B8G8R8A8 &&
+ aFormat != SurfaceFormat::B8G8R8X8) {
+ return nullptr;
+ }
+
+ RefPtr<MacIOSurface> surf = MacIOSurface::CreateIOSurface(aSize.width, aSize.height,
+ 1.0,
+ aFormat == SurfaceFormat::B8G8R8A8);
+ if (!surf) {
+ return nullptr;
+ }
+
+ return Create(surf, aBackend);
+}
+
+bool
+MacIOSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor)
+{
+ aOutDescriptor = SurfaceDescriptorMacIOSurface(mSurface->GetIOSurfaceID(),
+ mSurface->GetContentsScaleFactor(),
+ !mSurface->HasAlpha());
+ return true;
+}
+
+void
+MacIOSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const
+{
+ aInfo.size = gfx::IntSize(mSurface->GetDevicePixelWidth(), mSurface->GetDevicePixelHeight());
+ aInfo.format = mSurface->HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
+ aInfo.hasIntermediateBuffer = false;
+ aInfo.hasSynchronization = false;
+ aInfo.supportsMoz2D = true;
+ aInfo.canExposeMappedData = false;
+}
+
+bool
+MacIOSurfaceTextureData::Lock(OpenMode)
+{
+ mSurface->Lock(false);
+ return true;
+}
+
+void
+MacIOSurfaceTextureData::Unlock()
+{
+ mSurface->Unlock(false);
+}
+
+already_AddRefed<DataSourceSurface>
+MacIOSurfaceTextureData::GetAsSurface()
+{
+ RefPtr<SourceSurface> surf = CreateSourceSurfaceFromMacIOSurface(mSurface);
+ return surf->GetDataSurface();
+}
+
+already_AddRefed<DrawTarget>
+MacIOSurfaceTextureData::BorrowDrawTarget()
+{
+ MOZ_ASSERT(mBackend != BackendType::NONE);
+ if (mBackend == BackendType::NONE) {
+ // shouldn't happen, but degrade gracefully
+ return nullptr;
+ }
+ return Factory::CreateDrawTargetForData(
+ mBackend,
+ (unsigned char*)mSurface->GetBaseAddress(),
+ IntSize(mSurface->GetWidth(), mSurface->GetHeight()),
+ mSurface->GetBytesPerRow(),
+ mSurface->HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8,
+ true);
+}
+
+void
+MacIOSurfaceTextureData::Deallocate(LayersIPCChannel*)
+{
+ mSurface = nullptr;
+}
+
+void
+MacIOSurfaceTextureData::Forget(LayersIPCChannel*)
+{
+ mSurface = nullptr;
+}
+
+bool
+MacIOSurfaceTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface)
+{
+ RefPtr<DrawTarget> dt = BorrowDrawTarget();
+ if (!dt) {
+ return false;
+ }
+
+ dt->CopySurface(aSurface, IntRect(IntPoint(), aSurface->GetSize()), IntPoint());
+ return true;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h
new file mode 100644
index 0000000000..21e4459537
--- /dev/null
+++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H
+#define MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H
+
+#include "mozilla/layers/TextureClientOGL.h"
+
+class MacIOSurface;
+
+namespace mozilla {
+namespace layers {
+
+class MacIOSurfaceTextureData : public TextureData
+{
+public:
+ static MacIOSurfaceTextureData* Create(MacIOSurface* aSurface,
+ gfx::BackendType aBackend);
+
+ static MacIOSurfaceTextureData*
+ Create(const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat,
+ gfx::BackendType aBackend);
+
+ ~MacIOSurfaceTextureData();
+
+ virtual void FillInfo(TextureData::Info& aInfo) const override;
+
+ virtual bool Lock(OpenMode) override;
+
+ virtual void Unlock() override;
+
+ virtual already_AddRefed<gfx::DrawTarget> BorrowDrawTarget() override;
+
+ virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override;
+
+ virtual void Deallocate(LayersIPCChannel*) override;
+
+ virtual void Forget(LayersIPCChannel*) override;
+
+ virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) override;
+
+ // For debugging purposes only.
+ already_AddRefed<gfx::DataSourceSurface> GetAsSurface();
+
+protected:
+ MacIOSurfaceTextureData(MacIOSurface* aSurface,
+ gfx::BackendType aBackend);
+
+ RefPtr<MacIOSurface> mSurface;
+ gfx::BackendType mBackend;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H
diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
new file mode 100644
index 0000000000..9736618f7e
--- /dev/null
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp
@@ -0,0 +1,180 @@
+/* -*- 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 "MacIOSurfaceTextureHostOGL.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "GLContextCGL.h"
+
+namespace mozilla {
+namespace layers {
+
+MacIOSurfaceTextureHostOGL::MacIOSurfaceTextureHostOGL(TextureFlags aFlags,
+ const SurfaceDescriptorMacIOSurface& aDescriptor)
+ : TextureHost(aFlags)
+{
+ MOZ_COUNT_CTOR(MacIOSurfaceTextureHostOGL);
+ mSurface = MacIOSurface::LookupSurface(aDescriptor.surfaceId(),
+ aDescriptor.scaleFactor(),
+ !aDescriptor.isOpaque());
+}
+
+MacIOSurfaceTextureHostOGL::~MacIOSurfaceTextureHostOGL()
+{
+ MOZ_COUNT_DTOR(MacIOSurfaceTextureHostOGL);
+}
+
+GLTextureSource*
+MacIOSurfaceTextureHostOGL::CreateTextureSourceForPlane(size_t aPlane)
+{
+ MOZ_ASSERT(mSurface);
+
+ GLuint textureHandle;
+ gl::GLContext* gl = mCompositor->gl();
+ gl->fGenTextures(1, &textureHandle);
+ gl->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textureHandle);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+ gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
+
+ mSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(gl)->GetCGLContext(), aPlane);
+
+ return new GLTextureSource(mCompositor, textureHandle, LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ gfx::IntSize(mSurface->GetDevicePixelWidth(aPlane),
+ mSurface->GetDevicePixelHeight(aPlane)),
+ // XXX: This isn't really correct (but isn't used), we should be using the
+ // format of the individual plane, not of the whole buffer.
+ mSurface->GetFormat());
+}
+
+bool
+MacIOSurfaceTextureHostOGL::Lock()
+{
+ if (!gl() || !gl()->MakeCurrent() || !mSurface) {
+ return false;
+ }
+
+ if (!mTextureSource) {
+ mTextureSource = CreateTextureSourceForPlane(0);
+
+ RefPtr<TextureSource> prev = mTextureSource;
+ for (size_t i = 1; i < mSurface->GetPlaneCount(); i++) {
+ RefPtr<TextureSource> next = CreateTextureSourceForPlane(i);
+ prev->SetNextSibling(next);
+ prev = next;
+ }
+ }
+ return true;
+}
+
+void
+MacIOSurfaceTextureHostOGL::SetCompositor(Compositor* aCompositor)
+{
+ CompositorOGL* glCompositor = AssertGLCompositor(aCompositor);
+ if (!glCompositor) {
+ mTextureSource = nullptr;
+ mCompositor = nullptr;
+ return;
+ }
+
+ if (mCompositor != glCompositor) {
+ // Cannot share GL texture identifiers across compositors.
+ mTextureSource = nullptr;
+ }
+ mCompositor = glCompositor;
+}
+
+gfx::SurfaceFormat
+MacIOSurfaceTextureHostOGL::GetFormat() const {
+ if (!mSurface) {
+ return gfx::SurfaceFormat::UNKNOWN;
+ }
+ return mSurface->GetFormat();
+}
+
+gfx::SurfaceFormat
+MacIOSurfaceTextureHostOGL::GetReadFormat() const {
+ if (!mSurface) {
+ return gfx::SurfaceFormat::UNKNOWN;
+ }
+ return mSurface->GetReadFormat();
+}
+
+gfx::IntSize
+MacIOSurfaceTextureHostOGL::GetSize() const {
+ if (!mSurface) {
+ return gfx::IntSize();
+ }
+ return gfx::IntSize(mSurface->GetDevicePixelWidth(),
+ mSurface->GetDevicePixelHeight());
+}
+
+gl::GLContext*
+MacIOSurfaceTextureHostOGL::gl() const
+{
+ return mCompositor ? mCompositor->gl() : nullptr;
+}
+
+MacIOSurfaceTextureSourceOGL::MacIOSurfaceTextureSourceOGL(
+ CompositorOGL* aCompositor,
+ MacIOSurface* aSurface)
+ : mCompositor(aCompositor)
+ , mSurface(aSurface)
+{
+ MOZ_ASSERT(aCompositor);
+ MOZ_COUNT_CTOR(MacIOSurfaceTextureSourceOGL);
+}
+
+MacIOSurfaceTextureSourceOGL::~MacIOSurfaceTextureSourceOGL()
+{
+ MOZ_COUNT_DTOR(MacIOSurfaceTextureSourceOGL);
+}
+
+gfx::IntSize
+MacIOSurfaceTextureSourceOGL::GetSize() const
+{
+ return gfx::IntSize(mSurface->GetDevicePixelWidth(),
+ mSurface->GetDevicePixelHeight());
+}
+
+gfx::SurfaceFormat
+MacIOSurfaceTextureSourceOGL::GetFormat() const
+{
+ return mSurface->HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8
+ : gfx::SurfaceFormat::R8G8B8X8;
+}
+
+void
+MacIOSurfaceTextureSourceOGL::BindTexture(GLenum aTextureUnit,
+ gfx::SamplingFilter aSamplingFilter)
+{
+ gl::GLContext* gl = this->gl();
+ if (!gl || !gl->MakeCurrent()) {
+ NS_WARNING("Trying to bind a texture without a working GLContext");
+ return;
+ }
+ GLuint tex = mCompositor->GetTemporaryTexture(GetTextureTarget(), aTextureUnit);
+
+ gl->fActiveTexture(aTextureUnit);
+ gl->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, tex);
+ mSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(gl)->GetCGLContext());
+ ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+}
+
+void
+MacIOSurfaceTextureSourceOGL::SetCompositor(Compositor* aCompositor)
+{
+ mCompositor = AssertGLCompositor(aCompositor);
+ if (mCompositor && mNextSibling) {
+ mNextSibling->SetCompositor(aCompositor);
+ }
+}
+
+gl::GLContext*
+MacIOSurfaceTextureSourceOGL::gl() const
+{
+ return mCompositor ? mCompositor->gl() : nullptr;
+}
+
+} // namespace layers
+} // namespace mozilla
diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h
new file mode 100644
index 0000000000..55e2f5019a
--- /dev/null
+++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+#ifndef MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H
+#define MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H
+
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/TextureHostOGL.h"
+
+class MacIOSurface;
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * A texture source meant for use with MacIOSurfaceTextureHostOGL.
+ *
+ * It does not own any GL texture, and attaches its shared handle to one of
+ * the compositor's temporary textures when binding.
+ */
+class MacIOSurfaceTextureSourceOGL : public TextureSource
+ , public TextureSourceOGL
+{
+public:
+ MacIOSurfaceTextureSourceOGL(CompositorOGL* aCompositor,
+ MacIOSurface* aSurface);
+ virtual ~MacIOSurfaceTextureSourceOGL();
+
+ virtual const char* Name() const override { return "MacIOSurfaceTextureSourceOGL"; }
+
+ virtual TextureSourceOGL* AsSourceOGL() override { return this; }
+
+ virtual void BindTexture(GLenum activetex,
+ gfx::SamplingFilter aSamplingFilter) override;
+
+ virtual bool IsValid() const override { return !!gl(); }
+
+ virtual gfx::IntSize GetSize() const override;
+
+ virtual gfx::SurfaceFormat GetFormat() const override;
+
+ virtual GLenum GetTextureTarget() const override { return LOCAL_GL_TEXTURE_RECTANGLE_ARB; }
+
+ virtual GLenum GetWrapMode() const override { return LOCAL_GL_CLAMP_TO_EDGE; }
+
+ // MacIOSurfaceTextureSourceOGL doesn't own any gl texture
+ virtual void DeallocateDeviceData() override {}
+
+ virtual void SetCompositor(Compositor* aCompositor) override;
+
+ gl::GLContext* gl() const;
+
+protected:
+ RefPtr<CompositorOGL> mCompositor;
+ RefPtr<MacIOSurface> mSurface;
+};
+
+/**
+ * A TextureHost for shared MacIOSurface
+ *
+ * Most of the logic actually happens in MacIOSurfaceTextureSourceOGL.
+ */
+class MacIOSurfaceTextureHostOGL : public TextureHost
+{
+public:
+ MacIOSurfaceTextureHostOGL(TextureFlags aFlags,
+ const SurfaceDescriptorMacIOSurface& aDescriptor);
+ virtual ~MacIOSurfaceTextureHostOGL();
+
+ // MacIOSurfaceTextureSourceOGL doesn't own any GL texture
+ virtual void DeallocateDeviceData() override {}
+
+ virtual void SetCompositor(Compositor* aCompositor) override;
+
+ virtual Compositor* GetCompositor() override { return mCompositor; }
+
+ virtual bool Lock() override;
+
+ virtual gfx::SurfaceFormat GetFormat() const override;
+ virtual gfx::SurfaceFormat GetReadFormat() const override;
+
+ virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) override
+ {
+ aTexture = mTextureSource;
+ return !!aTexture;
+ }
+
+ virtual already_AddRefed<gfx::DataSourceSurface> GetAsSurface() override
+ {
+ return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING)
+ }
+
+ gl::GLContext* gl() const;
+
+ virtual gfx::IntSize GetSize() const override;
+
+#ifdef MOZ_LAYERS_HAVE_LOG
+ virtual const char* Name() override { return "MacIOSurfaceTextureHostOGL"; }
+#endif
+
+protected:
+ GLTextureSource* CreateTextureSourceForPlane(size_t aPlane);
+
+ RefPtr<CompositorOGL> mCompositor;
+ RefPtr<GLTextureSource> mTextureSource;
+ RefPtr<MacIOSurface> mSurface;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H
diff --git a/gfx/layers/opengl/TextureHostOGL.cpp b/gfx/layers/opengl/TextureHostOGL.cpp
index 35c83686a7..bc06444b0f 100644
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -20,6 +20,10 @@
#include "GLBlitTextureImageHelper.h"
#include "GeckoProfiler.h"
+#ifdef XP_MACOSX
+#include "mozilla/layers/MacIOSurfaceTextureHostOGL.h"
+#endif
+
using namespace mozilla::gl;
using namespace mozilla::gfx;
@@ -45,6 +49,15 @@ CreateTextureHostOGL(const SurfaceDescriptor& aDesc,
break;
}
+#ifdef XP_MACOSX
+ case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
+ const SurfaceDescriptorMacIOSurface& desc =
+ aDesc.get_SurfaceDescriptorMacIOSurface();
+ result = new MacIOSurfaceTextureHostOGL(aFlags, desc);
+ break;
+ }
+#endif
+
case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: {
const auto& desc = aDesc.get_SurfaceDescriptorSharedGLTexture();
result = new GLTextureHost(aFlags, desc.texture(),
diff --git a/gfx/skia/generate_mozbuild.py b/gfx/skia/generate_mozbuild.py
index 22801ea9a8..a15fd40861 100755
--- a/gfx/skia/generate_mozbuild.py
+++ b/gfx/skia/generate_mozbuild.py
@@ -65,6 +65,7 @@ LOCAL_INCLUDES += [
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] in {
+ 'cocoa',
'gtk2',
'gtk3',
'uikit',
@@ -443,7 +444,7 @@ def write_mozbuild(sources):
f.write("if CONFIG['MOZ_ENABLE_SKIA_GPU']:\n")
write_sources(f, sources['gpu'], 4)
- f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] in {'uikit'}:\n")
+ f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] in {'cocoa', 'uikit'}:\n")
write_sources(f, sources['mac'], 4)
f.write("if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:\n")
diff --git a/gfx/skia/moz.build b/gfx/skia/moz.build
index 1d9d342f41..75a66dd28e 100644
--- a/gfx/skia/moz.build
+++ b/gfx/skia/moz.build
@@ -516,7 +516,7 @@ if CONFIG['MOZ_ENABLE_SKIA_GPU']:
'skia/src/gpu/GrResourceCache.cpp',
'skia/src/image/SkImage_Gpu.cpp',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] in {'uikit'}:
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in {'cocoa', 'uikit'}:
SOURCES += [
'skia/src/ports/SkDebug_stdio.cpp',
'skia/src/ports/SkOSFile_posix.cpp',
@@ -663,6 +663,7 @@ LOCAL_INCLUDES += [
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] in {
+ 'cocoa',
'gtk2',
'gtk3',
'uikit',
diff --git a/gfx/src/nsDeviceContext.cpp b/gfx/src/nsDeviceContext.cpp
index e4288e47e3..db69e21e98 100644
--- a/gfx/src/nsDeviceContext.cpp
+++ b/gfx/src/nsDeviceContext.cpp
@@ -239,7 +239,11 @@ nsDeviceContext::FontMetricsDeleted(const nsFontMetrics* aFontMetrics)
bool
nsDeviceContext::IsPrinterContext()
{
- return mPrintTarget != nullptr;
+ return mPrintTarget != nullptr
+#ifdef XP_MACOSX
+ || mCachedPrintTarget != nullptr
+#endif
+ ;
}
void
@@ -343,6 +347,17 @@ nsDeviceContext::CreateRenderingContextCommon(bool aWantReferenceContext)
MOZ_ASSERT(mWidth > 0 && mHeight > 0);
RefPtr<PrintTarget> printingTarget = mPrintTarget;
+#ifdef XP_MACOSX
+ // CreateRenderingContext() can be called (on reflow) after EndPage()
+ // but before BeginPage(). On OS X (and only there) mPrintTarget
+ // will in this case be null, because OS X printing surfaces are
+ // per-page, and therefore only truly valid between calls to BeginPage()
+ // and EndPage(). But we can get away with fudging things here, if need
+ // be, by using a cached copy.
+ if (!printingTarget) {
+ printingTarget = mCachedPrintTarget;
+ }
+#endif
// This will usually be null, depending on the pref print.print_via_parent.
RefPtr<DrawEventRecorder> recorder;
@@ -363,6 +378,9 @@ nsDeviceContext::CreateRenderingContextCommon(bool aWantReferenceContext)
return nullptr;
}
+#ifdef XP_MACOSX
+ dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
+#endif
dt->AddUserData(&sDisablePixelSnapping, (void*)0x1, nullptr);
RefPtr<gfxContext> pContext = gfxContext::CreateOrNull(dt);
@@ -525,6 +543,12 @@ nsDeviceContext::BeginPage(void)
if (NS_FAILED(rv)) return rv;
+#ifdef XP_MACOSX
+ // We need to get a new surface for each page on the Mac, as the
+ // CGContextRefs are only good for one page.
+ mPrintTarget = mDeviceContextSpec->MakePrintTarget();
+#endif
+
rv = mPrintTarget->BeginPage();
return rv;
@@ -535,6 +559,18 @@ nsDeviceContext::EndPage(void)
{
nsresult rv = mPrintTarget->EndPage();
+#ifdef XP_MACOSX
+ // We need to release the CGContextRef in the surface here, plus it's
+ // not something you would want anyway, as these CGContextRefs are only
+ // good for one page. But we need to keep a cached reference to it, since
+ // CreateRenderingContext() may try to access it when mPrintTarget
+ // would normally be null. See bug 665218. If we just stop nulling out
+ // mPrintTarget here (and thereby make that our cached copy), we'll
+ // break all our null checks on mPrintTarget. See bug 684622.
+ mCachedPrintTarget = mPrintTarget;
+ mPrintTarget = nullptr;
+#endif
+
if (mDeviceContextSpec)
mDeviceContextSpec->EndPage();
diff --git a/gfx/src/nsDeviceContext.h b/gfx/src/nsDeviceContext.h
index edb3f5d6a8..1115757eb1 100644
--- a/gfx/src/nsDeviceContext.h
+++ b/gfx/src/nsDeviceContext.h
@@ -300,6 +300,9 @@ private:
nsCOMPtr<nsIScreenManager> mScreenManager;
nsCOMPtr<nsIDeviceContextSpec> mDeviceContextSpec;
RefPtr<PrintTarget> mPrintTarget;
+#ifdef XP_MACOSX
+ RefPtr<PrintTarget> mCachedPrintTarget;
+#endif
#ifdef DEBUG
bool mIsInitialized;
#endif
diff --git a/gfx/thebes/PrintTargetCG.cpp b/gfx/thebes/PrintTargetCG.cpp
new file mode 100644
index 0000000000..5fe838182a
--- /dev/null
+++ b/gfx/thebes/PrintTargetCG.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "PrintTargetCG.h"
+
+#include "cairo.h"
+#include "cairo-quartz.h"
+#include "mozilla/gfx/HelpersCairo.h"
+
+namespace mozilla {
+namespace gfx {
+
+PrintTargetCG::PrintTargetCG(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize)
+ : PrintTarget(aCairoSurface, aSize)
+{
+ // TODO: Add memory reporting like gfxQuartzSurface.
+ //RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
+}
+
+/* static */ already_AddRefed<PrintTargetCG>
+PrintTargetCG::CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat)
+{
+ if (!Factory::CheckSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ unsigned int width = static_cast<unsigned int>(aSize.width);
+ unsigned int height = static_cast<unsigned int>(aSize.height);
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(aFormat);
+ cairo_surface_t* surface =
+ cairo_quartz_surface_create(cformat, width, height);
+
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetCG> target = new PrintTargetCG(surface, aSize);
+
+ return target.forget();
+}
+
+/* static */ already_AddRefed<PrintTargetCG>
+PrintTargetCG::CreateOrNull(CGContextRef aContext, const IntSize& aSize)
+{
+ if (!Factory::CheckSurfaceSize(aSize)) {
+ return nullptr;
+ }
+
+ unsigned int width = static_cast<unsigned int>(aSize.width);
+ unsigned int height = static_cast<unsigned int>(aSize.height);
+
+ cairo_surface_t* surface =
+ cairo_quartz_surface_create_for_cg_context(aContext, width, height);
+
+ if (cairo_surface_status(surface)) {
+ return nullptr;
+ }
+
+ // The new object takes ownership of our surface reference.
+ RefPtr<PrintTargetCG> target = new PrintTargetCG(surface, aSize);
+
+ return target.forget();
+}
+
+static size_t
+PutBytesNull(void* info, const void* buffer, size_t count)
+{
+ return count;
+}
+
+already_AddRefed<DrawTarget>
+PrintTargetCG::GetReferenceDrawTarget(DrawEventRecorder* aRecorder)
+{
+ if (!mRefDT) {
+ const IntSize size(1, 1);
+
+ CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr};
+ CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks);
+ CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr);
+ CGDataConsumerRelease(consumer);
+
+ cairo_surface_t* similar =
+ cairo_quartz_surface_create_for_cg_context(
+ pdfContext, size.width, size.height);
+
+ CGContextRelease(pdfContext);
+
+ if (cairo_surface_status(similar)) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> dt =
+ Factory::CreateDrawTargetForCairoSurface(similar, size);
+
+ // The DT addrefs the surface, so we need drop our own reference to it:
+ cairo_surface_destroy(similar);
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ if (aRecorder) {
+ dt = CreateRecordingDrawTarget(aRecorder, dt);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+ }
+
+ mRefDT = dt.forget();
+ }
+ return do_AddRef(mRefDT);
+}
+
+} // namespace gfx
+} // namespace mozilla
diff --git a/gfx/thebes/PrintTargetCG.h b/gfx/thebes/PrintTargetCG.h
new file mode 100644
index 0000000000..87dbdbc2c7
--- /dev/null
+++ b/gfx/thebes/PrintTargetCG.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef MOZILLA_GFX_PRINTTARGETCG_H
+#define MOZILLA_GFX_PRINTTARGETCG_H
+
+#include <Carbon/Carbon.h>
+#include "PrintTarget.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * CoreGraphics printing target.
+ *
+ * Note that a CGContextRef obtained from PMSessionGetCGGraphicsContext is
+ * valid only for the current page. As a consequence instances of this class
+ * should only be used to print a single page.
+ */
+class PrintTargetCG final : public PrintTarget
+{
+public:
+ static already_AddRefed<PrintTargetCG>
+ CreateOrNull(const IntSize& aSize, gfxImageFormat aFormat);
+
+ static already_AddRefed<PrintTargetCG>
+ CreateOrNull(CGContextRef aContext, const IntSize& aSize);
+
+ virtual already_AddRefed<DrawTarget>
+ GetReferenceDrawTarget(DrawEventRecorder* aRecorder) final;
+
+private:
+ PrintTargetCG(cairo_surface_t* aCairoSurface,
+ const IntSize& aSize);
+};
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_PRINTTARGETCG_H */
diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp
new file mode 100644
index 0000000000..08217b82f9
--- /dev/null
+++ b/gfx/thebes/gfxCoreTextShaper.cpp
@@ -0,0 +1,800 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "mozilla/ArrayUtils.h"
+#include "gfxCoreTextShaper.h"
+#include "gfxMacFont.h"
+#include "gfxFontUtils.h"
+#include "gfxTextRun.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include <algorithm>
+
+#include <dlfcn.h>
+
+using namespace mozilla;
+
+// standard font descriptors that we construct the first time they're needed
+CTFontDescriptorRef gfxCoreTextShaper::sDefaultFeaturesDescriptor = nullptr;
+CTFontDescriptorRef gfxCoreTextShaper::sDisableLigaturesDescriptor = nullptr;
+CTFontDescriptorRef gfxCoreTextShaper::sIndicFeaturesDescriptor = nullptr;
+CTFontDescriptorRef gfxCoreTextShaper::sIndicDisableLigaturesDescriptor = nullptr;
+
+static CFStringRef sCTWritingDirectionAttributeName = nullptr;
+
+// See CTStringAttributes.h
+enum {
+ kMyCTWritingDirectionEmbedding = (0 << 1),
+ kMyCTWritingDirectionOverride = (1 << 1)
+};
+
+// Helper to create a CFDictionary with the right attributes for shaping our
+// text, including imposing the given directionality.
+// This will only be called if we're on 10.8 or later.
+CFDictionaryRef
+gfxCoreTextShaper::CreateAttrDict(bool aRightToLeft)
+{
+ // Because we always shape unidirectional runs, and may have applied
+ // directional overrides, we want to force a direction rather than
+ // allowing CoreText to do its own unicode-based bidi processing.
+ SInt16 dirOverride = kMyCTWritingDirectionOverride |
+ (aRightToLeft ? kCTWritingDirectionRightToLeft
+ : kCTWritingDirectionLeftToRight);
+ CFNumberRef dirNumber =
+ ::CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberSInt16Type, &dirOverride);
+ CFArrayRef dirArray =
+ ::CFArrayCreate(kCFAllocatorDefault,
+ (const void **) &dirNumber, 1,
+ &kCFTypeArrayCallBacks);
+ ::CFRelease(dirNumber);
+ CFTypeRef attrs[] = { kCTFontAttributeName, sCTWritingDirectionAttributeName };
+ CFTypeRef values[] = { mCTFont, dirArray };
+ CFDictionaryRef attrDict =
+ ::CFDictionaryCreate(kCFAllocatorDefault,
+ attrs, values, ArrayLength(attrs),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ ::CFRelease(dirArray);
+ return attrDict;
+}
+
+CFDictionaryRef
+gfxCoreTextShaper::CreateAttrDictWithoutDirection()
+{
+ CFTypeRef attrs[] = { kCTFontAttributeName };
+ CFTypeRef values[] = { mCTFont };
+ CFDictionaryRef attrDict =
+ ::CFDictionaryCreate(kCFAllocatorDefault,
+ attrs, values, ArrayLength(attrs),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ return attrDict;
+}
+
+gfxCoreTextShaper::gfxCoreTextShaper(gfxMacFont *aFont)
+ : gfxFontShaper(aFont)
+ , mAttributesDictLTR(nullptr)
+ , mAttributesDictRTL(nullptr)
+{
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ CFStringRef* pstr = (CFStringRef*)
+ dlsym(RTLD_DEFAULT, "kCTWritingDirectionAttributeName");
+ if (pstr) {
+ sCTWritingDirectionAttributeName = *pstr;
+ }
+ sInitialized = true;
+ }
+
+ // Create our CTFontRef
+ mCTFont = CreateCTFontWithFeatures(aFont->GetAdjustedSize(),
+ GetDefaultFeaturesDescriptor());
+}
+
+gfxCoreTextShaper::~gfxCoreTextShaper()
+{
+ if (mAttributesDictLTR) {
+ ::CFRelease(mAttributesDictLTR);
+ }
+ if (mAttributesDictRTL) {
+ ::CFRelease(mAttributesDictRTL);
+ }
+ if (mCTFont) {
+ ::CFRelease(mCTFont);
+ }
+}
+
+static bool
+IsBuggyIndicScript(unicode::Script aScript)
+{
+ return aScript == unicode::Script::BENGALI ||
+ aScript == unicode::Script::KANNADA;
+}
+
+bool
+gfxCoreTextShaper::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ // Create a CFAttributedString with text and style info, so we can use CoreText to lay it out.
+ bool isRightToLeft = aShapedText->IsRightToLeft();
+ const UniChar* text = reinterpret_cast<const UniChar*>(aText);
+ uint32_t length = aLength;
+
+ uint32_t startOffset;
+ CFStringRef stringObj;
+ CFDictionaryRef attrObj;
+
+ if (sCTWritingDirectionAttributeName) {
+ startOffset = 0;
+ stringObj = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
+ text, length,
+ kCFAllocatorNull);
+
+ // Get an attributes dictionary suitable for shaping text in the
+ // current direction, creating it if necessary.
+ attrObj = isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR;
+ if (!attrObj) {
+ attrObj = CreateAttrDict(isRightToLeft);
+ (isRightToLeft ? mAttributesDictRTL : mAttributesDictLTR) = attrObj;
+ }
+ } else {
+ // OS is too old to support kCTWritingDirectionAttributeName:
+ // we need to bidi-wrap the text if the run is RTL,
+ // or if it is an LTR run but may contain (overridden) RTL chars
+ bool bidiWrap = isRightToLeft;
+ if (!bidiWrap && !aShapedText->TextIs8Bit()) {
+ uint32_t i;
+ for (i = 0; i < length; ++i) {
+ if (gfxFontUtils::PotentialRTLChar(aText[i])) {
+ bidiWrap = true;
+ break;
+ }
+ }
+ }
+
+ // If there's a possibility of any bidi, we wrap the text with
+ // direction overrides to ensure neutrals or characters that were
+ // bidi-overridden in HTML behave properly.
+ static const UniChar beginLTR[] = { 0x202d, 0x20 };
+ static const UniChar beginRTL[] = { 0x202e, 0x20 };
+ static const UniChar endBidiWrap[] = { 0x20, 0x2e, 0x202c };
+
+ if (bidiWrap) {
+ startOffset = isRightToLeft ? ArrayLength(beginRTL)
+ : ArrayLength(beginLTR);
+ CFMutableStringRef mutableString =
+ ::CFStringCreateMutable(kCFAllocatorDefault,
+ length + startOffset +
+ ArrayLength(endBidiWrap));
+ ::CFStringAppendCharacters(mutableString,
+ isRightToLeft ? beginRTL : beginLTR,
+ startOffset);
+ ::CFStringAppendCharacters(mutableString, text, length);
+ ::CFStringAppendCharacters(mutableString, endBidiWrap,
+ ArrayLength(endBidiWrap));
+ stringObj = mutableString;
+ } else {
+ startOffset = 0;
+ stringObj =
+ ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
+ text, length,
+ kCFAllocatorNull);
+ }
+
+ // Get an attributes dictionary suitable for shaping text,
+ // creating it if necessary. (This dict is not LTR-specific,
+ // but we use that field to store it anyway.)
+ if (!mAttributesDictLTR) {
+ mAttributesDictLTR = CreateAttrDictWithoutDirection();
+ }
+ attrObj = mAttributesDictLTR;
+ }
+
+ CTFontRef tempCTFont = nullptr;
+ if (IsBuggyIndicScript(aScript)) {
+ // To work around buggy Indic AAT fonts shipped with OS X,
+ // we re-enable the Line Initial Smart Swashes feature that is needed
+ // for "split vowels" to work in at least Bengali and Kannada fonts.
+ // Affected fonts include Bangla MN, Bangla Sangam MN, Kannada MN,
+ // Kannada Sangam MN. See bugs 686225, 728557, 953231, 1145515.
+ tempCTFont =
+ CreateCTFontWithFeatures(::CTFontGetSize(mCTFont),
+ aShapedText->DisableLigatures()
+ ? GetIndicDisableLigaturesDescriptor()
+ : GetIndicFeaturesDescriptor());
+ } else if (aShapedText->DisableLigatures()) {
+ // For letterspacing (or maybe other situations) we need to make
+ // a copy of the CTFont with the ligature feature disabled.
+ tempCTFont =
+ CreateCTFontWithFeatures(::CTFontGetSize(mCTFont),
+ GetDisableLigaturesDescriptor());
+ }
+
+ // For the disabled-ligature or buggy-indic-font case, we need to replace
+ // the standard CTFont in the attribute dictionary with a tweaked version.
+ CFMutableDictionaryRef mutableAttr = nullptr;
+ if (tempCTFont) {
+ mutableAttr = ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 2,
+ attrObj);
+ ::CFDictionaryReplaceValue(mutableAttr,
+ kCTFontAttributeName, tempCTFont);
+ // Having created the dict, we're finished with our temporary
+ // Indic and/or ligature-disabled CTFontRef.
+ ::CFRelease(tempCTFont);
+ attrObj = mutableAttr;
+ }
+
+ // Now we can create an attributed string
+ CFAttributedStringRef attrStringObj =
+ ::CFAttributedStringCreate(kCFAllocatorDefault, stringObj, attrObj);
+ ::CFRelease(stringObj);
+
+ // Create the CoreText line from our string, then we're done with it
+ CTLineRef line = ::CTLineCreateWithAttributedString(attrStringObj);
+ ::CFRelease(attrStringObj);
+
+ // and finally retrieve the glyph data and store into the gfxTextRun
+ CFArrayRef glyphRuns = ::CTLineGetGlyphRuns(line);
+ uint32_t numRuns = ::CFArrayGetCount(glyphRuns);
+
+ // Iterate through the glyph runs.
+ // Note that this includes the bidi wrapper, so we have to be careful
+ // not to include the extra glyphs from there
+ bool success = true;
+ for (uint32_t runIndex = 0; runIndex < numRuns; runIndex++) {
+ CTRunRef aCTRun =
+ (CTRunRef)::CFArrayGetValueAtIndex(glyphRuns, runIndex);
+ // If the range is purely within bidi-wrapping text, ignore it.
+ CFRange range = ::CTRunGetStringRange(aCTRun);
+ if (uint32_t(range.location + range.length) <= startOffset ||
+ range.location - startOffset >= aLength) {
+ continue;
+ }
+ CFDictionaryRef runAttr = ::CTRunGetAttributes(aCTRun);
+ if (runAttr != attrObj) {
+ // If Core Text manufactured a new dictionary, this may indicate
+ // unexpected font substitution. In that case, we fail (and fall
+ // back to harfbuzz shaping)...
+ const void* font1 =
+ ::CFDictionaryGetValue(attrObj, kCTFontAttributeName);
+ const void* font2 =
+ ::CFDictionaryGetValue(runAttr, kCTFontAttributeName);
+ if (font1 != font2) {
+ // ...except that if the fallback was only for a variation
+ // selector or join control that is otherwise unsupported,
+ // we just ignore it.
+ if (range.length == 1) {
+ char16_t ch = aText[range.location - startOffset];
+ if (gfxFontUtils::IsJoinControl(ch) ||
+ gfxFontUtils::IsVarSelector(ch)) {
+ continue;
+ }
+ }
+ NS_WARNING("unexpected font fallback in Core Text");
+ success = false;
+ break;
+ }
+ }
+ if (SetGlyphsFromRun(aShapedText, aOffset, aLength, aCTRun,
+ startOffset) != NS_OK) {
+ success = false;
+ break;
+ }
+ }
+
+ if (mutableAttr) {
+ ::CFRelease(mutableAttr);
+ }
+ ::CFRelease(line);
+
+ return success;
+}
+
+#define SMALL_GLYPH_RUN 128 // preallocated size of our auto arrays for per-glyph data;
+ // some testing indicates that 90%+ of glyph runs will fit
+ // without requiring a separate allocation
+
+nsresult
+gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText *aShapedText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ CTRunRef aCTRun,
+ int32_t aStringOffset)
+{
+ // The word has been bidi-wrapped; aStringOffset is the number
+ // of chars at the beginning of the CTLine that we should skip.
+ // aCTRun is a glyph run from the CoreText layout process.
+
+ int32_t direction = aShapedText->IsRightToLeft() ? -1 : 1;
+
+ int32_t numGlyphs = ::CTRunGetGlyphCount(aCTRun);
+ if (numGlyphs == 0) {
+ return NS_OK;
+ }
+
+ int32_t wordLength = aLength;
+
+ // character offsets get really confusing here, as we have to keep track of
+ // (a) the text in the actual textRun we're constructing
+ // (c) the string that was handed to CoreText, which contains the text of the font run
+ // plus directional-override padding
+ // (d) the CTRun currently being processed, which may be a sub-run of the CoreText line
+ // (but may extend beyond the actual font run into the bidi wrapping text).
+ // aStringOffset tells us how many initial characters of the line to ignore.
+
+ // get the source string range within the CTLine's text
+ CFRange stringRange = ::CTRunGetStringRange(aCTRun);
+ // skip the run if it is entirely outside the actual range of the font run
+ if (stringRange.location - aStringOffset + stringRange.length <= 0 ||
+ stringRange.location - aStringOffset >= wordLength) {
+ return NS_OK;
+ }
+
+ // retrieve the laid-out glyph data from the CTRun
+ UniquePtr<CGGlyph[]> glyphsArray;
+ UniquePtr<CGPoint[]> positionsArray;
+ UniquePtr<CFIndex[]> glyphToCharArray;
+ const CGGlyph* glyphs = nullptr;
+ const CGPoint* positions = nullptr;
+ const CFIndex* glyphToChar = nullptr;
+
+ // Testing indicates that CTRunGetGlyphsPtr (almost?) always succeeds,
+ // and so allocating a new array and copying data with CTRunGetGlyphs
+ // will be extremely rare.
+ // If this were not the case, we could use an AutoTArray<> to
+ // try and avoid the heap allocation for small runs.
+ // It's possible that some future change to CoreText will mean that
+ // CTRunGetGlyphsPtr fails more often; if this happens, AutoTArray<>
+ // may become an attractive option.
+ glyphs = ::CTRunGetGlyphsPtr(aCTRun);
+ if (!glyphs) {
+ glyphsArray = MakeUniqueFallible<CGGlyph[]>(numGlyphs);
+ if (!glyphsArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ ::CTRunGetGlyphs(aCTRun, ::CFRangeMake(0, 0), glyphsArray.get());
+ glyphs = glyphsArray.get();
+ }
+
+ positions = ::CTRunGetPositionsPtr(aCTRun);
+ if (!positions) {
+ positionsArray = MakeUniqueFallible<CGPoint[]>(numGlyphs);
+ if (!positionsArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ ::CTRunGetPositions(aCTRun, ::CFRangeMake(0, 0), positionsArray.get());
+ positions = positionsArray.get();
+ }
+
+ // Remember that the glyphToChar indices relate to the CoreText line,
+ // not to the beginning of the textRun, the font run,
+ // or the stringRange of the glyph run
+ glyphToChar = ::CTRunGetStringIndicesPtr(aCTRun);
+ if (!glyphToChar) {
+ glyphToCharArray = MakeUniqueFallible<CFIndex[]>(numGlyphs);
+ if (!glyphToCharArray) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ ::CTRunGetStringIndices(aCTRun, ::CFRangeMake(0, 0), glyphToCharArray.get());
+ glyphToChar = glyphToCharArray.get();
+ }
+
+ double runWidth = ::CTRunGetTypographicBounds(aCTRun, ::CFRangeMake(0, 0),
+ nullptr, nullptr, nullptr);
+
+ AutoTArray<gfxShapedText::DetailedGlyph,1> detailedGlyphs;
+ gfxShapedText::CompressedGlyph *charGlyphs =
+ aShapedText->GetCharacterGlyphs() + aOffset;
+
+ // CoreText gives us the glyphindex-to-charindex mapping, which relates each glyph
+ // to a source text character; we also need the charindex-to-glyphindex mapping to
+ // find the glyph for a given char. Note that some chars may not map to any glyph
+ // (ligature continuations), and some may map to several glyphs (eg Indic split vowels).
+ // We set the glyph index to NO_GLYPH for chars that have no associated glyph, and we
+ // record the last glyph index for cases where the char maps to several glyphs,
+ // so that our clumping will include all the glyph fragments for the character.
+
+ // The charToGlyph array is indexed by char position within the stringRange of the glyph run.
+
+ static const int32_t NO_GLYPH = -1;
+ AutoTArray<int32_t,SMALL_GLYPH_RUN> charToGlyphArray;
+ if (!charToGlyphArray.SetLength(stringRange.length, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ int32_t *charToGlyph = charToGlyphArray.Elements();
+ for (int32_t offset = 0; offset < stringRange.length; ++offset) {
+ charToGlyph[offset] = NO_GLYPH;
+ }
+ for (int32_t i = 0; i < numGlyphs; ++i) {
+ int32_t loc = glyphToChar[i] - stringRange.location;
+ if (loc >= 0 && loc < stringRange.length) {
+ charToGlyph[loc] = i;
+ }
+ }
+
+ // Find character and glyph clumps that correspond, allowing for ligatures,
+ // indic reordering, split glyphs, etc.
+ //
+ // The idea is that we'll find a character sequence starting at the first char of stringRange,
+ // and extend it until it includes the character associated with the first glyph;
+ // we also extend it as long as there are "holes" in the range of glyphs. So we
+ // will eventually have a contiguous sequence of characters, starting at the beginning
+ // of the range, that map to a contiguous sequence of glyphs, starting at the beginning
+ // of the glyph array. That's a clump; then we update the starting positions and repeat.
+ //
+ // NB: In the case of RTL layouts, we iterate over the stringRange in reverse.
+ //
+
+ // This may find characters that fall outside the range 0:wordLength,
+ // so we won't necessarily use everything we find here.
+
+ bool isRightToLeft = aShapedText->IsRightToLeft();
+ int32_t glyphStart = 0; // looking for a clump that starts at this glyph index
+ int32_t charStart = isRightToLeft ?
+ stringRange.length - 1 : 0; // and this char index (in the stringRange of the glyph run)
+
+ while (glyphStart < numGlyphs) { // keep finding groups until all glyphs are accounted for
+ bool inOrder = true;
+ int32_t charEnd = glyphToChar[glyphStart] - stringRange.location;
+ NS_WARNING_ASSERTION(
+ charEnd >= 0 && charEnd < stringRange.length,
+ "glyph-to-char mapping points outside string range");
+ // clamp charEnd to the valid range of the string
+ charEnd = std::max(charEnd, 0);
+ charEnd = std::min(charEnd, int32_t(stringRange.length));
+
+ int32_t glyphEnd = glyphStart;
+ int32_t charLimit = isRightToLeft ? -1 : stringRange.length;
+ do {
+ // This is normally executed once for each iteration of the outer loop,
+ // but in unusual cases where the character/glyph association is complex,
+ // the initial character range might correspond to a non-contiguous
+ // glyph range with "holes" in it. If so, we will repeat this loop to
+ // extend the character range until we have a contiguous glyph sequence.
+ NS_ASSERTION((direction > 0 && charEnd < charLimit) ||
+ (direction < 0 && charEnd > charLimit),
+ "no characters left in range?");
+ charEnd += direction;
+ while (charEnd != charLimit && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd += direction;
+ }
+
+ // find the maximum glyph index covered by the clump so far
+ if (isRightToLeft) {
+ for (int32_t i = charStart; i > charEnd; --i) {
+ if (charToGlyph[i] != NO_GLYPH) {
+ // update extent of glyph range
+ glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
+ }
+ }
+ } else {
+ for (int32_t i = charStart; i < charEnd; ++i) {
+ if (charToGlyph[i] != NO_GLYPH) {
+ // update extent of glyph range
+ glyphEnd = std::max(glyphEnd, charToGlyph[i] + 1);
+ }
+ }
+ }
+
+ if (glyphEnd == glyphStart + 1) {
+ // for the common case of a single-glyph clump, we can skip the following checks
+ break;
+ }
+
+ if (glyphEnd == glyphStart) {
+ // no glyphs, try to extend the clump
+ continue;
+ }
+
+ // check whether all glyphs in the range are associated with the characters
+ // in our clump; if not, we have a discontinuous range, and should extend it
+ // unless we've reached the end of the text
+ bool allGlyphsAreWithinCluster = true;
+ int32_t prevGlyphCharIndex = charStart;
+ for (int32_t i = glyphStart; i < glyphEnd; ++i) {
+ int32_t glyphCharIndex = glyphToChar[i] - stringRange.location;
+ if (isRightToLeft) {
+ if (glyphCharIndex > charStart || glyphCharIndex <= charEnd) {
+ allGlyphsAreWithinCluster = false;
+ break;
+ }
+ if (glyphCharIndex > prevGlyphCharIndex) {
+ inOrder = false;
+ }
+ prevGlyphCharIndex = glyphCharIndex;
+ } else {
+ if (glyphCharIndex < charStart || glyphCharIndex >= charEnd) {
+ allGlyphsAreWithinCluster = false;
+ break;
+ }
+ if (glyphCharIndex < prevGlyphCharIndex) {
+ inOrder = false;
+ }
+ prevGlyphCharIndex = glyphCharIndex;
+ }
+ }
+ if (allGlyphsAreWithinCluster) {
+ break;
+ }
+ } while (charEnd != charLimit);
+
+ NS_WARNING_ASSERTION(glyphStart < glyphEnd,
+ "character/glyph clump contains no glyphs!");
+ if (glyphStart == glyphEnd) {
+ ++glyphStart; // make progress - avoid potential infinite loop
+ charStart = charEnd;
+ continue;
+ }
+
+ NS_WARNING_ASSERTION(charStart != charEnd,
+ "character/glyph clump contains no characters!");
+ if (charStart == charEnd) {
+ glyphStart = glyphEnd; // this is bad - we'll discard the glyph(s),
+ // as there's nowhere to attach them
+ continue;
+ }
+
+ // Now charStart..charEnd is a ligature clump, corresponding to glyphStart..glyphEnd;
+ // Set baseCharIndex to the char we'll actually attach the glyphs to (1st of ligature),
+ // and endCharIndex to the limit (position beyond the last char),
+ // adjusting for the offset of the stringRange relative to the textRun.
+ int32_t baseCharIndex, endCharIndex;
+ if (isRightToLeft) {
+ while (charEnd >= 0 && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd--;
+ }
+ baseCharIndex = charEnd + stringRange.location - aStringOffset + 1;
+ endCharIndex = charStart + stringRange.location - aStringOffset + 1;
+ } else {
+ while (charEnd < stringRange.length && charToGlyph[charEnd] == NO_GLYPH) {
+ charEnd++;
+ }
+ baseCharIndex = charStart + stringRange.location - aStringOffset;
+ endCharIndex = charEnd + stringRange.location - aStringOffset;
+ }
+
+ // Then we check if the clump falls outside our actual string range; if so, just go to the next.
+ if (endCharIndex <= 0 || baseCharIndex >= wordLength) {
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ continue;
+ }
+ // Ensure we won't try to go beyond the valid length of the word's text
+ baseCharIndex = std::max(baseCharIndex, 0);
+ endCharIndex = std::min(endCharIndex, wordLength);
+
+ // Now we're ready to set the glyph info in the textRun; measure the glyph width
+ // of the first (perhaps only) glyph, to see if it is "Simple"
+ int32_t appUnitsPerDevUnit = aShapedText->GetAppUnitsPerDevUnit();
+ double toNextGlyph;
+ if (glyphStart < numGlyphs-1) {
+ toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
+ } else {
+ toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
+ }
+ int32_t advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
+
+ // Check if it's a simple one-to-one mapping
+ int32_t glyphsInClump = glyphEnd - glyphStart;
+ if (glyphsInClump == 1 &&
+ gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyphs[glyphStart]) &&
+ gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
+ charGlyphs[baseCharIndex].IsClusterStart() &&
+ positions[glyphStart].y == 0.0)
+ {
+ charGlyphs[baseCharIndex].SetSimpleGlyph(advance,
+ glyphs[glyphStart]);
+ } else {
+ // collect all glyphs in a list to be assigned to the first char;
+ // there must be at least one in the clump, and we already measured its advance,
+ // hence the placement of the loop-exit test and the measurement of the next glyph
+ while (1) {
+ gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
+ details->mGlyphID = glyphs[glyphStart];
+ details->mXOffset = 0;
+ details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit;
+ details->mAdvance = advance;
+ if (++glyphStart >= glyphEnd) {
+ break;
+ }
+ if (glyphStart < numGlyphs-1) {
+ toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
+ } else {
+ toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
+ }
+ advance = int32_t(toNextGlyph * appUnitsPerDevUnit);
+ }
+
+ gfxTextRun::CompressedGlyph textRunGlyph;
+ textRunGlyph.SetComplex(charGlyphs[baseCharIndex].IsClusterStart(),
+ true, detailedGlyphs.Length());
+ aShapedText->SetGlyphs(aOffset + baseCharIndex, textRunGlyph,
+ detailedGlyphs.Elements());
+
+ detailedGlyphs.Clear();
+ }
+
+ // the rest of the chars in the group are ligature continuations, no associated glyphs
+ while (++baseCharIndex != endCharIndex && baseCharIndex < wordLength) {
+ gfxShapedText::CompressedGlyph &shapedTextGlyph = charGlyphs[baseCharIndex];
+ NS_ASSERTION(!shapedTextGlyph.IsSimpleGlyph(), "overwriting a simple glyph");
+ shapedTextGlyph.SetComplex(inOrder && shapedTextGlyph.IsClusterStart(), false, 0);
+ }
+
+ glyphStart = glyphEnd;
+ charStart = charEnd;
+ }
+
+ return NS_OK;
+}
+
+#undef SMALL_GLYPH_RUN
+
+// Construct the font attribute descriptor that we'll apply by default when
+// creating a CTFontRef. This will turn off line-edge swashes by default,
+// because we don't know the actual line breaks when doing glyph shaping.
+
+// We also cache feature descriptors for shaping with disabled ligatures, and
+// for buggy Indic AAT font workarounds, created on an as-needed basis.
+
+#define MAX_FEATURES 3 // max used by any of our Get*Descriptor functions
+
+CTFontDescriptorRef
+gfxCoreTextShaper::CreateFontFeaturesDescriptor(
+ const std::pair<SInt16,SInt16> aFeatures[],
+ size_t aCount)
+{
+ MOZ_ASSERT(aCount <= MAX_FEATURES);
+
+ CFDictionaryRef featureSettings[MAX_FEATURES];
+
+ for (size_t i = 0; i < aCount; i++) {
+ CFNumberRef type = ::CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberSInt16Type,
+ &aFeatures[i].first);
+ CFNumberRef selector = ::CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberSInt16Type,
+ &aFeatures[i].second);
+
+ CFTypeRef keys[] = { kCTFontFeatureTypeIdentifierKey,
+ kCTFontFeatureSelectorIdentifierKey };
+ CFTypeRef values[] = { type, selector };
+ featureSettings[i] =
+ ::CFDictionaryCreate(kCFAllocatorDefault,
+ (const void **) keys,
+ (const void **) values,
+ ArrayLength(keys),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ ::CFRelease(selector);
+ ::CFRelease(type);
+ }
+
+ CFArrayRef featuresArray =
+ ::CFArrayCreate(kCFAllocatorDefault,
+ (const void **) featureSettings,
+ aCount, // not ArrayLength(featureSettings), as we
+ // may not have used all the allocated slots
+ &kCFTypeArrayCallBacks);
+
+ for (size_t i = 0; i < aCount; i++) {
+ ::CFRelease(featureSettings[i]);
+ }
+
+ const CFTypeRef attrKeys[] = { kCTFontFeatureSettingsAttribute };
+ const CFTypeRef attrValues[] = { featuresArray };
+ CFDictionaryRef attributesDict =
+ ::CFDictionaryCreate(kCFAllocatorDefault,
+ (const void **) attrKeys,
+ (const void **) attrValues,
+ ArrayLength(attrKeys),
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ ::CFRelease(featuresArray);
+
+ CTFontDescriptorRef descriptor =
+ ::CTFontDescriptorCreateWithAttributes(attributesDict);
+ ::CFRelease(attributesDict);
+
+ return descriptor;
+}
+
+CTFontDescriptorRef
+gfxCoreTextShaper::GetDefaultFeaturesDescriptor()
+{
+ if (sDefaultFeaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kDefaultFeatures[] = {
+ { kSmartSwashType, kLineInitialSwashesOffSelector },
+ { kSmartSwashType, kLineFinalSwashesOffSelector }
+ };
+ sDefaultFeaturesDescriptor =
+ CreateFontFeaturesDescriptor(kDefaultFeatures,
+ ArrayLength(kDefaultFeatures));
+ }
+ return sDefaultFeaturesDescriptor;
+}
+
+CTFontDescriptorRef
+gfxCoreTextShaper::GetDisableLigaturesDescriptor()
+{
+ if (sDisableLigaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kDisableLigatures[] = {
+ { kSmartSwashType, kLineInitialSwashesOffSelector },
+ { kSmartSwashType, kLineFinalSwashesOffSelector },
+ { kLigaturesType, kCommonLigaturesOffSelector }
+ };
+ sDisableLigaturesDescriptor =
+ CreateFontFeaturesDescriptor(kDisableLigatures,
+ ArrayLength(kDisableLigatures));
+ }
+ return sDisableLigaturesDescriptor;
+}
+
+CTFontDescriptorRef
+gfxCoreTextShaper::GetIndicFeaturesDescriptor()
+{
+ if (sIndicFeaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kIndicFeatures[] = {
+ { kSmartSwashType, kLineFinalSwashesOffSelector }
+ };
+ sIndicFeaturesDescriptor =
+ CreateFontFeaturesDescriptor(kIndicFeatures,
+ ArrayLength(kIndicFeatures));
+ }
+ return sIndicFeaturesDescriptor;
+}
+
+CTFontDescriptorRef
+gfxCoreTextShaper::GetIndicDisableLigaturesDescriptor()
+{
+ if (sIndicDisableLigaturesDescriptor == nullptr) {
+ const std::pair<SInt16,SInt16> kIndicDisableLigatures[] = {
+ { kSmartSwashType, kLineFinalSwashesOffSelector },
+ { kLigaturesType, kCommonLigaturesOffSelector }
+ };
+ sIndicDisableLigaturesDescriptor =
+ CreateFontFeaturesDescriptor(kIndicDisableLigatures,
+ ArrayLength(kIndicDisableLigatures));
+ }
+ return sIndicDisableLigaturesDescriptor;
+}
+
+CTFontRef
+gfxCoreTextShaper::CreateCTFontWithFeatures(CGFloat aSize,
+ CTFontDescriptorRef aDescriptor)
+{
+ gfxMacFont *f = static_cast<gfxMacFont*>(mFont);
+ return ::CTFontCreateWithGraphicsFont(f->GetCGFontRef(), aSize, nullptr,
+ aDescriptor);
+}
+
+void
+gfxCoreTextShaper::Shutdown() // [static]
+{
+ if (sIndicDisableLigaturesDescriptor != nullptr) {
+ ::CFRelease(sIndicDisableLigaturesDescriptor);
+ sIndicDisableLigaturesDescriptor = nullptr;
+ }
+ if (sIndicFeaturesDescriptor != nullptr) {
+ ::CFRelease(sIndicFeaturesDescriptor);
+ sIndicFeaturesDescriptor = nullptr;
+ }
+ if (sDisableLigaturesDescriptor != nullptr) {
+ ::CFRelease(sDisableLigaturesDescriptor);
+ sDisableLigaturesDescriptor = nullptr;
+ }
+ if (sDefaultFeaturesDescriptor != nullptr) {
+ ::CFRelease(sDefaultFeaturesDescriptor);
+ sDefaultFeaturesDescriptor = nullptr;
+ }
+}
diff --git a/gfx/thebes/gfxCoreTextShaper.h b/gfx/thebes/gfxCoreTextShaper.h
new file mode 100644
index 0000000000..8e5d24f91f
--- /dev/null
+++ b/gfx/thebes/gfxCoreTextShaper.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef GFX_CORETEXTSHAPER_H
+#define GFX_CORETEXTSHAPER_H
+
+#include "gfxFont.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+class gfxMacFont;
+
+class gfxCoreTextShaper : public gfxFontShaper {
+public:
+ explicit gfxCoreTextShaper(gfxMacFont *aFont);
+
+ virtual ~gfxCoreTextShaper();
+
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText);
+
+ // clean up static objects that may have been cached
+ static void Shutdown();
+
+protected:
+ CTFontRef mCTFont;
+
+ // attributes for shaping text with LTR or RTL directionality
+ CFDictionaryRef mAttributesDictLTR;
+ CFDictionaryRef mAttributesDictRTL;
+
+ nsresult SetGlyphsFromRun(gfxShapedText *aShapedText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ CTRunRef aCTRun,
+ int32_t aStringOffset);
+
+ CTFontRef CreateCTFontWithFeatures(CGFloat aSize,
+ CTFontDescriptorRef aDescriptor);
+
+ CFDictionaryRef CreateAttrDict(bool aRightToLeft);
+ CFDictionaryRef CreateAttrDictWithoutDirection();
+
+ static CTFontDescriptorRef
+ CreateFontFeaturesDescriptor(const std::pair<SInt16,SInt16> aFeatures[],
+ size_t aCount);
+
+ static CTFontDescriptorRef GetDefaultFeaturesDescriptor();
+ static CTFontDescriptorRef GetDisableLigaturesDescriptor();
+ static CTFontDescriptorRef GetIndicFeaturesDescriptor();
+ static CTFontDescriptorRef GetIndicDisableLigaturesDescriptor();
+
+ // cached font descriptor, created the first time it's needed
+ static CTFontDescriptorRef sDefaultFeaturesDescriptor;
+
+ // cached descriptor for adding disable-ligatures setting to a font
+ static CTFontDescriptorRef sDisableLigaturesDescriptor;
+
+ // feature descriptors for buggy Indic AAT font workaround
+ static CTFontDescriptorRef sIndicFeaturesDescriptor;
+ static CTFontDescriptorRef sIndicDisableLigaturesDescriptor;
+};
+
+#endif /* GFX_CORETEXTSHAPER_H */
diff --git a/gfx/thebes/gfxFontUtils.cpp b/gfx/thebes/gfxFontUtils.cpp
index 0d4b309f6c..54ca03ff6c 100644
--- a/gfx/thebes/gfxFontUtils.cpp
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -379,14 +379,24 @@ gfxFontUtils::ReadCMAPTableFormat14(const uint8_t *aBuf, uint32_t aLength,
return NS_OK;
}
-// For fonts with two format-4 tables, we allow the first one (Unicode platform)
-// to be replaced by the Microsoft-platform subtable.
+// For fonts with two format-4 tables, the first one (Unicode platform) is preferred on the Mac;
+// on other platforms we allow the Microsoft-platform subtable to replace it.
-#define acceptableFormat4(p,e,k) (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) || \
- ((p) == PLATFORM_ID_UNICODE))
+#if defined(XP_MACOSX)
+ #define acceptableFormat4(p,e,k) (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft && !(k)) || \
+ ((p) == PLATFORM_ID_UNICODE))
-#define acceptableUCS4Encoding(p, e, k) \
+ #define acceptableUCS4Encoding(p, e, k) \
+ (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform) && (k) != 12 || \
+ ((p) == PLATFORM_ID_UNICODE && \
+ ((e) != EncodingIDUVSForUnicodePlatform)))
+#else
+ #define acceptableFormat4(p,e,k) (((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDMicrosoft) || \
+ ((p) == PLATFORM_ID_UNICODE))
+
+ #define acceptableUCS4Encoding(p, e, k) \
((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDUCS4ForMicrosoftPlatform)
+#endif
#define acceptablePlatform(p) ((p) == PLATFORM_ID_UNICODE || (p) == PLATFORM_ID_MICROSOFT)
#define isSymbol(p,e) ((p) == PLATFORM_ID_MICROSOFT && (e) == EncodingIDSymbol)
@@ -1220,8 +1230,13 @@ gfxFontUtils::GetFamilyNameFromTable(hb_blob_t *aNameTable,
}
enum {
+#if defined(XP_MACOSX)
+ CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MAC_ENGLISH,
+ PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MAC
+#else
CANONICAL_LANG_ID = gfxFontUtils::LANG_ID_MICROSOFT_EN_US,
PLATFORM_ID = gfxFontUtils::PLATFORM_ID_MICROSOFT
+#endif
};
nsresult
@@ -1262,6 +1277,22 @@ gfxFontUtils::ReadCanonicalName(const char *aNameData, uint32_t aDataLen,
NS_ENSURE_SUCCESS(rv, rv);
}
+#if defined(XP_MACOSX)
+ // may be dealing with font that only has Microsoft name entries
+ if (names.Length() == 0) {
+ rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ID_MICROSOFT_EN_US,
+ PLATFORM_ID_MICROSOFT, names);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // getting really desperate now, take anything!
+ if (names.Length() == 0) {
+ rv = ReadNames(aNameData, aDataLen, aNameID, LANG_ALL,
+ PLATFORM_ID_MICROSOFT, names);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+#endif
+
// return the first name (99.9% of the time names will
// contain a single English name)
if (names.Length()) {
diff --git a/gfx/thebes/gfxMacFont.cpp b/gfx/thebes/gfxMacFont.cpp
new file mode 100644
index 0000000000..f512c689f7
--- /dev/null
+++ b/gfx/thebes/gfxMacFont.cpp
@@ -0,0 +1,475 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxMacFont.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Sprintf.h"
+
+#include "gfxCoreTextShaper.h"
+#include <algorithm>
+#include "gfxPlatformMac.h"
+#include "gfxContext.h"
+#include "gfxFontUtils.h"
+#include "gfxMacPlatformFontList.h"
+#include "gfxFontConstants.h"
+#include "gfxTextRun.h"
+
+#include "cairo-quartz.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+gfxMacFont::gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ bool aNeedsBold)
+ : gfxFont(aFontEntry, aFontStyle),
+ mCGFont(nullptr),
+ mCTFont(nullptr),
+ mFontFace(nullptr)
+{
+ mApplySyntheticBold = aNeedsBold;
+
+ mCGFont = aFontEntry->GetFontRef();
+ if (!mCGFont) {
+ mIsValid = false;
+ return;
+ }
+
+ // InitMetrics will handle the sizeAdjust factor and set mAdjustedSize
+ InitMetrics();
+ if (!mIsValid) {
+ return;
+ }
+
+ mFontFace = cairo_quartz_font_face_create_for_cgfont(mCGFont);
+
+ cairo_status_t cairoerr = cairo_font_face_status(mFontFace);
+ if (cairoerr != CAIRO_STATUS_SUCCESS) {
+ mIsValid = false;
+#ifdef DEBUG
+ char warnBuf[1024];
+ SprintfLiteral(warnBuf,
+ "Failed to create Cairo font face: %s status: %d",
+ NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
+ NS_WARNING(warnBuf);
+#endif
+ return;
+ }
+
+ cairo_matrix_t sizeMatrix, ctm;
+ cairo_matrix_init_identity(&ctm);
+ cairo_matrix_init_scale(&sizeMatrix, mAdjustedSize, mAdjustedSize);
+
+ // synthetic oblique by skewing via the font matrix
+ bool needsOblique = mFontEntry != nullptr &&
+ mFontEntry->IsUpright() &&
+ mStyle.style != NS_FONT_STYLE_NORMAL &&
+ mStyle.allowSyntheticStyle;
+
+ if (needsOblique) {
+ cairo_matrix_t style;
+ cairo_matrix_init(&style,
+ 1, //xx
+ 0, //yx
+ -1 * OBLIQUE_SKEW_FACTOR, //xy
+ 1, //yy
+ 0, //x0
+ 0); //y0
+ cairo_matrix_multiply(&sizeMatrix, &sizeMatrix, &style);
+ }
+
+ cairo_font_options_t *fontOptions = cairo_font_options_create();
+
+ // turn off font anti-aliasing based on user pref setting
+ if (mAdjustedSize <=
+ (gfxFloat)gfxPlatformMac::GetPlatform()->GetAntiAliasingThreshold()) {
+ cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_NONE);
+ mAntialiasOption = kAntialiasNone;
+ } else if (mStyle.useGrayscaleAntialiasing) {
+ cairo_font_options_set_antialias(fontOptions, CAIRO_ANTIALIAS_GRAY);
+ mAntialiasOption = kAntialiasGrayscale;
+ }
+
+ mScaledFont = cairo_scaled_font_create(mFontFace, &sizeMatrix, &ctm,
+ fontOptions);
+ cairo_font_options_destroy(fontOptions);
+
+ cairoerr = cairo_scaled_font_status(mScaledFont);
+ if (cairoerr != CAIRO_STATUS_SUCCESS) {
+ mIsValid = false;
+#ifdef DEBUG
+ char warnBuf[1024];
+ SprintfLiteral(warnBuf, "Failed to create scaled font: %s status: %d",
+ NS_ConvertUTF16toUTF8(GetName()).get(), cairoerr);
+ NS_WARNING(warnBuf);
+#endif
+ }
+}
+
+gfxMacFont::~gfxMacFont()
+{
+ if (mCTFont) {
+ ::CFRelease(mCTFont);
+ }
+ if (mScaledFont) {
+ cairo_scaled_font_destroy(mScaledFont);
+ }
+ if (mFontFace) {
+ cairo_font_face_destroy(mFontFace);
+ }
+}
+
+bool
+gfxMacFont::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ if (!mIsValid) {
+ NS_WARNING("invalid font! expect incorrect text rendering");
+ return false;
+ }
+
+ // Currently, we don't support vertical shaping via CoreText,
+ // so we ignore RequiresAATLayout if vertical is requested.
+ if (static_cast<MacOSFontEntry*>(GetFontEntry())->RequiresAATLayout() &&
+ !aVertical) {
+ if (!mCoreTextShaper) {
+ mCoreTextShaper = MakeUnique<gfxCoreTextShaper>(this);
+ }
+ if (mCoreTextShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText)) {
+ PostShapingFixup(aDrawTarget, aText, aOffset,
+ aLength, aVertical, aShapedText);
+ return true;
+ }
+ }
+
+ return gfxFont::ShapeText(aDrawTarget, aText, aOffset, aLength, aScript,
+ aVertical, aShapedText);
+}
+
+bool
+gfxMacFont::SetupCairoFont(DrawTarget* aDrawTarget)
+{
+ if (cairo_scaled_font_status(mScaledFont) != CAIRO_STATUS_SUCCESS) {
+ // Don't cairo_set_scaled_font as that would propagate the error to
+ // the cairo_t, precluding any further drawing.
+ return false;
+ }
+ cairo_set_scaled_font(gfxFont::RefCairo(aDrawTarget), mScaledFont);
+ return true;
+}
+
+gfxFont::RunMetrics
+gfxMacFont::Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget *aRefDrawTarget,
+ Spacing *aSpacing,
+ uint16_t aOrientation)
+{
+ gfxFont::RunMetrics metrics =
+ gfxFont::Measure(aTextRun, aStart, aEnd,
+ aBoundingBoxType, aRefDrawTarget, aSpacing,
+ aOrientation);
+
+ // if aBoundingBoxType is not TIGHT_HINTED_OUTLINE_EXTENTS then we need to add
+ // a pixel column each side of the bounding box in case of antialiasing "bleed"
+ if (aBoundingBoxType != TIGHT_HINTED_OUTLINE_EXTENTS &&
+ metrics.mBoundingBox.width > 0) {
+ metrics.mBoundingBox.x -= aTextRun->GetAppUnitsPerDevUnit();
+ metrics.mBoundingBox.width += aTextRun->GetAppUnitsPerDevUnit() * 2;
+ }
+
+ return metrics;
+}
+
+void
+gfxMacFont::InitMetrics()
+{
+ mIsValid = false;
+ ::memset(&mMetrics, 0, sizeof(mMetrics));
+
+ uint32_t upem = 0;
+
+ // try to get unitsPerEm from sfnt head table, to avoid calling CGFont
+ // if possible (bug 574368) and because CGFontGetUnitsPerEm does not
+ // return the true value for OpenType/CFF fonts (it normalizes to 1000,
+ // which then leads to metrics errors when we read the 'hmtx' table to
+ // get glyph advances for HarfBuzz, see bug 580863)
+ CFDataRef headData =
+ ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('h','e','a','d'));
+ if (headData) {
+ if (size_t(::CFDataGetLength(headData)) >= sizeof(HeadTable)) {
+ const HeadTable *head =
+ reinterpret_cast<const HeadTable*>(::CFDataGetBytePtr(headData));
+ upem = head->unitsPerEm;
+ }
+ ::CFRelease(headData);
+ }
+ if (!upem) {
+ upem = ::CGFontGetUnitsPerEm(mCGFont);
+ }
+
+ if (upem < 16 || upem > 16384) {
+ // See http://www.microsoft.com/typography/otspec/head.htm
+#ifdef DEBUG
+ char warnBuf[1024];
+ SprintfLiteral(warnBuf,
+ "Bad font metrics for: %s (invalid unitsPerEm value)",
+ NS_ConvertUTF16toUTF8(mFontEntry->Name()).get());
+ NS_WARNING(warnBuf);
+#endif
+ return;
+ }
+
+ mAdjustedSize = std::max(mStyle.size, 1.0);
+ mFUnitsConvFactor = mAdjustedSize / upem;
+
+ // For CFF fonts, when scaling values read from CGFont* APIs, we need to
+ // use CG's idea of unitsPerEm, which may differ from the "true" value in
+ // the head table of the font (see bug 580863)
+ gfxFloat cgConvFactor;
+ if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) {
+ cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont);
+ } else {
+ cgConvFactor = mFUnitsConvFactor;
+ }
+
+ // Try to read 'sfnt' metrics; for local, non-sfnt fonts ONLY, fall back to
+ // platform APIs. The InitMetrics...() functions will set mIsValid on success.
+ if (!InitMetricsFromSfntTables(mMetrics) &&
+ (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) {
+ InitMetricsFromPlatform();
+ }
+ if (!mIsValid) {
+ return;
+ }
+
+ if (mMetrics.xHeight == 0.0) {
+ mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor;
+ }
+
+ if (mMetrics.capHeight == 0.0) {
+ mMetrics.capHeight = ::CGFontGetCapHeight(mCGFont) * cgConvFactor;
+ }
+
+ if (mStyle.sizeAdjust > 0.0 && mStyle.size > 0.0 &&
+ mMetrics.xHeight > 0.0) {
+ // apply font-size-adjust, and recalculate metrics
+ gfxFloat aspect = mMetrics.xHeight / mStyle.size;
+ mAdjustedSize = mStyle.GetAdjustedSize(aspect);
+ mFUnitsConvFactor = mAdjustedSize / upem;
+ if (static_cast<MacOSFontEntry*>(mFontEntry.get())->IsCFF()) {
+ cgConvFactor = mAdjustedSize / ::CGFontGetUnitsPerEm(mCGFont);
+ } else {
+ cgConvFactor = mFUnitsConvFactor;
+ }
+ mMetrics.xHeight = 0.0;
+ if (!InitMetricsFromSfntTables(mMetrics) &&
+ (!mFontEntry->IsUserFont() || mFontEntry->IsLocalUserFont())) {
+ InitMetricsFromPlatform();
+ }
+ if (!mIsValid) {
+ // this shouldn't happen, as we succeeded earlier before applying
+ // the size-adjust factor! But check anyway, for paranoia's sake.
+ return;
+ }
+ if (mMetrics.xHeight == 0.0) {
+ mMetrics.xHeight = ::CGFontGetXHeight(mCGFont) * cgConvFactor;
+ }
+ }
+
+ // Once we reach here, we've got basic metrics and set mIsValid = TRUE;
+ // there should be no further points of actual failure in InitMetrics().
+ // (If one is introduced, be sure to reset mIsValid to FALSE!)
+
+ mMetrics.emHeight = mAdjustedSize;
+
+ // Measure/calculate additional metrics, independent of whether we used
+ // the tables directly or ATS metrics APIs
+
+ CFDataRef cmap =
+ ::CGFontCopyTableForTag(mCGFont, TRUETYPE_TAG('c','m','a','p'));
+
+ uint32_t glyphID;
+ if (mMetrics.aveCharWidth <= 0) {
+ mMetrics.aveCharWidth = GetCharWidth(cmap, 'x', &glyphID,
+ cgConvFactor);
+ if (glyphID == 0) {
+ // we didn't find 'x', so use maxAdvance rather than zero
+ mMetrics.aveCharWidth = mMetrics.maxAdvance;
+ }
+ }
+ if (IsSyntheticBold()) {
+ mMetrics.aveCharWidth += GetSyntheticBoldOffset();
+ mMetrics.maxAdvance += GetSyntheticBoldOffset();
+ }
+
+ mMetrics.spaceWidth = GetCharWidth(cmap, ' ', &glyphID, cgConvFactor);
+ if (glyphID == 0) {
+ // no space glyph?!
+ mMetrics.spaceWidth = mMetrics.aveCharWidth;
+ }
+ mSpaceGlyph = glyphID;
+
+ mMetrics.zeroOrAveCharWidth = GetCharWidth(cmap, '0', &glyphID,
+ cgConvFactor);
+ if (glyphID == 0) {
+ mMetrics.zeroOrAveCharWidth = mMetrics.aveCharWidth;
+ }
+
+ if (cmap) {
+ ::CFRelease(cmap);
+ }
+
+ CalculateDerivedMetrics(mMetrics);
+
+ SanitizeMetrics(&mMetrics, mFontEntry->mIsBadUnderlineFont);
+
+#if 0
+ fprintf (stderr, "Font: %p (%s) size: %f\n", this,
+ NS_ConvertUTF16toUTF8(GetName()).get(), mStyle.size);
+// fprintf (stderr, " fbounds.origin.x %f y %f size.width %f height %f\n", fbounds.origin.x, fbounds.origin.y, fbounds.size.width, fbounds.size.height);
+ fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
+ fprintf (stderr, " maxAscent: %f maxDescent: %f maxAdvance: %f\n", mMetrics.maxAscent, mMetrics.maxDescent, mMetrics.maxAdvance);
+ fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.internalLeading, mMetrics.externalLeading);
+ fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f capHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight, mMetrics.capHeight);
+ fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
+#endif
+}
+
+gfxFloat
+gfxMacFont::GetCharWidth(CFDataRef aCmap, char16_t aUniChar,
+ uint32_t *aGlyphID, gfxFloat aConvFactor)
+{
+ CGGlyph glyph = 0;
+
+ if (aCmap) {
+ glyph = gfxFontUtils::MapCharToGlyph(::CFDataGetBytePtr(aCmap),
+ ::CFDataGetLength(aCmap),
+ aUniChar);
+ }
+
+ if (aGlyphID) {
+ *aGlyphID = glyph;
+ }
+
+ if (glyph) {
+ int advance;
+ if (::CGFontGetGlyphAdvances(mCGFont, &glyph, 1, &advance)) {
+ return advance * aConvFactor;
+ }
+ }
+
+ return 0;
+}
+
+int32_t
+gfxMacFont::GetGlyphWidth(DrawTarget& aDrawTarget, uint16_t aGID)
+{
+ if (!mCTFont) {
+ mCTFont = ::CTFontCreateWithGraphicsFont(mCGFont, mAdjustedSize,
+ nullptr, nullptr);
+ if (!mCTFont) { // shouldn't happen, but let's be safe
+ NS_WARNING("failed to create CTFontRef to measure glyph width");
+ return 0;
+ }
+ }
+
+ CGSize advance;
+ ::CTFontGetAdvancesForGlyphs(mCTFont, kCTFontDefaultOrientation, &aGID,
+ &advance, 1);
+ return advance.width * 0x10000;
+}
+
+// Try to initialize font metrics via platform APIs (CG/CT),
+// and set mIsValid = TRUE on success.
+// We ONLY call this for local (platform) fonts that are not sfnt format;
+// for sfnts, including ALL downloadable fonts, we prefer to use
+// InitMetricsFromSfntTables and avoid platform APIs.
+void
+gfxMacFont::InitMetricsFromPlatform()
+{
+ CTFontRef ctFont = ::CTFontCreateWithGraphicsFont(mCGFont,
+ mAdjustedSize,
+ nullptr, nullptr);
+ if (!ctFont) {
+ return;
+ }
+
+ mMetrics.underlineOffset = ::CTFontGetUnderlinePosition(ctFont);
+ mMetrics.underlineSize = ::CTFontGetUnderlineThickness(ctFont);
+
+ mMetrics.externalLeading = ::CTFontGetLeading(ctFont);
+
+ mMetrics.maxAscent = ::CTFontGetAscent(ctFont);
+ mMetrics.maxDescent = ::CTFontGetDescent(ctFont);
+
+ // this is not strictly correct, but neither CTFont nor CGFont seems to
+ // provide maxAdvance, unless we were to iterate over all the glyphs
+ // (which isn't worth the cost here)
+ CGRect r = ::CTFontGetBoundingBox(ctFont);
+ mMetrics.maxAdvance = r.size.width;
+
+ // aveCharWidth is also not provided, so leave it at zero
+ // (fallback code in gfxMacFont::InitMetrics will then try measuring 'x');
+ // this could lead to less-than-"perfect" text field sizing when width is
+ // specified as a number of characters, and the font in use is a non-sfnt
+ // legacy font, but that's a sufficiently obscure edge case that we can
+ // ignore the potential discrepancy.
+ mMetrics.aveCharWidth = 0;
+
+ mMetrics.xHeight = ::CTFontGetXHeight(ctFont);
+ mMetrics.capHeight = ::CTFontGetCapHeight(ctFont);
+
+ ::CFRelease(ctFont);
+
+ mIsValid = true;
+}
+
+already_AddRefed<ScaledFont>
+gfxMacFont::GetScaledFont(DrawTarget *aTarget)
+{
+ if (!mAzureScaledFont) {
+ NativeFont nativeFont;
+ nativeFont.mType = NativeFontType::MAC_FONT_FACE;
+ nativeFont.mFont = GetCGFontRef();
+ mAzureScaledFont = mozilla::gfx::Factory::CreateScaledFontWithCairo(nativeFont, GetAdjustedSize(), mScaledFont);
+ }
+
+ RefPtr<ScaledFont> scaledFont(mAzureScaledFont);
+ return scaledFont.forget();
+}
+
+already_AddRefed<mozilla::gfx::GlyphRenderingOptions>
+gfxMacFont::GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams)
+{
+ if (aRunParams) {
+ return mozilla::gfx::Factory::CreateCGGlyphRenderingOptions(aRunParams->fontSmoothingBGColor);
+ }
+ return nullptr;
+}
+
+void
+gfxMacFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ gfxFont::AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+ // mCGFont is shared with the font entry, so not counted here;
+ // and we don't have APIs to measure the cairo mFontFace object
+}
+
+void
+gfxMacFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
diff --git a/gfx/thebes/gfxMacFont.h b/gfx/thebes/gfxMacFont.h
new file mode 100644
index 0000000000..d12cc717ba
--- /dev/null
+++ b/gfx/thebes/gfxMacFont.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef GFX_MACFONT_H
+#define GFX_MACFONT_H
+
+#include "mozilla/MemoryReporting.h"
+#include "gfxFont.h"
+#include "cairo.h"
+#include <ApplicationServices/ApplicationServices.h>
+
+class MacOSFontEntry;
+
+class gfxMacFont : public gfxFont
+{
+public:
+ gfxMacFont(MacOSFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ bool aNeedsBold);
+
+ virtual ~gfxMacFont();
+
+ CGFontRef GetCGFontRef() const { return mCGFont; }
+
+ /* overrides for the pure virtual methods in gfxFont */
+ virtual uint32_t GetSpaceGlyph() override {
+ return mSpaceGlyph;
+ }
+
+ virtual bool SetupCairoFont(DrawTarget* aDrawTarget) override;
+
+ /* override Measure to add padding for antialiasing */
+ virtual RunMetrics Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget *aDrawTargetForTightBoundingBox,
+ Spacing *aSpacing,
+ uint16_t aOrientation) override;
+
+ // We need to provide hinted (non-linear) glyph widths if using a font
+ // with embedded color bitmaps (Apple Color Emoji), as Core Text renders
+ // the glyphs with non-linear scaling at small pixel sizes.
+ virtual bool ProvidesGlyphWidths() const override {
+ return mFontEntry->HasFontTable(TRUETYPE_TAG('s','b','i','x'));
+ }
+
+ virtual int32_t GetGlyphWidth(DrawTarget& aDrawTarget,
+ uint16_t aGID) override;
+
+ virtual already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFont(mozilla::gfx::DrawTarget *aTarget) override;
+
+ virtual already_AddRefed<mozilla::gfx::GlyphRenderingOptions>
+ GetGlyphRenderingOptions(const TextRunDrawParams* aRunParams = nullptr) override;
+
+ virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const override;
+
+ virtual FontType GetType() const override { return FONT_TYPE_MAC; }
+
+protected:
+ virtual const Metrics& GetHorizontalMetrics() override {
+ return mMetrics;
+ }
+
+ // override to prefer CoreText shaping with fonts that depend on AAT
+ virtual bool ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText) override;
+
+ void InitMetrics();
+ void InitMetricsFromPlatform();
+
+ // Get width and glyph ID for a character; uses aConvFactor
+ // to convert font units as returned by CG to actual dimensions
+ gfxFloat GetCharWidth(CFDataRef aCmap, char16_t aUniChar,
+ uint32_t *aGlyphID, gfxFloat aConvFactor);
+
+ // a weak reference to the CoreGraphics font: this is owned by the
+ // MacOSFontEntry, it is not retained or released by gfxMacFont
+ CGFontRef mCGFont;
+
+ // a Core Text font reference, created only if we're using CT to measure
+ // glyph widths; otherwise null.
+ CTFontRef mCTFont;
+
+ cairo_font_face_t *mFontFace;
+
+ mozilla::UniquePtr<gfxFontShaper> mCoreTextShaper;
+
+ Metrics mMetrics;
+ uint32_t mSpaceGlyph;
+};
+
+#endif /* GFX_MACFONT_H */
diff --git a/gfx/thebes/gfxMacPlatformFontList.h b/gfx/thebes/gfxMacPlatformFontList.h
new file mode 100644
index 0000000000..0ab062dca4
--- /dev/null
+++ b/gfx/thebes/gfxMacPlatformFontList.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef gfxMacPlatformFontList_H_
+#define gfxMacPlatformFontList_H_
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "mozilla/MemoryReporting.h"
+#include "nsDataHashtable.h"
+#include "nsRefPtrHashtable.h"
+
+#include "gfxPlatformFontList.h"
+#include "gfxPlatform.h"
+#include "gfxPlatformMac.h"
+
+#include "nsUnicharUtils.h"
+#include "nsTArray.h"
+#include "mozilla/LookAndFeel.h"
+
+class gfxMacPlatformFontList;
+
+// a single member of a font family (i.e. a single face, such as Times Italic)
+class MacOSFontEntry : public gfxFontEntry
+{
+public:
+ friend class gfxMacPlatformFontList;
+
+ MacOSFontEntry(const nsAString& aPostscriptName, int32_t aWeight,
+ bool aIsStandardFace = false,
+ double aSizeHint = 0.0);
+
+ // for use with data fonts
+ MacOSFontEntry(const nsAString& aPostscriptName, CGFontRef aFontRef,
+ uint16_t aWeight, uint16_t aStretch, uint8_t aStyle,
+ bool aIsDataUserFont, bool aIsLocal);
+
+ virtual ~MacOSFontEntry() {
+ ::CGFontRelease(mFontRef);
+ }
+
+ virtual CGFontRef GetFontRef();
+
+ // override gfxFontEntry table access function to bypass table cache,
+ // use CGFontRef API to get direct access to system font data
+ virtual hb_blob_t *GetFontTable(uint32_t aTag) override;
+
+ virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const override;
+
+ nsresult ReadCMAP(FontInfoData *aFontInfoData = nullptr) override;
+
+ bool RequiresAATLayout() const { return mRequiresAAT; }
+
+ bool IsCFF();
+
+protected:
+ virtual gfxFont* CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold) override;
+
+ virtual bool HasFontTable(uint32_t aTableTag) override;
+
+ static void DestroyBlobFunc(void* aUserData);
+
+ CGFontRef mFontRef; // owning reference to the CGFont, released on destruction
+
+ double mSizeHint;
+
+ bool mFontRefInitialized;
+ bool mRequiresAAT;
+ bool mIsCFF;
+ bool mIsCFFInitialized;
+ nsTHashtable<nsUint32HashKey> mAvailableTables;
+};
+
+class gfxMacPlatformFontList : public gfxPlatformFontList {
+public:
+ static gfxMacPlatformFontList* PlatformFontList() {
+ return static_cast<gfxMacPlatformFontList*>(sPlatformFontList);
+ }
+
+ static int32_t AppleWeightToCSSWeight(int32_t aAppleWeight);
+
+ bool GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName) override;
+
+ gfxFontEntry* LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle) override;
+
+ gfxFontEntry* MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength) override;
+
+ bool FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle = nullptr,
+ gfxFloat aDevToCssSize = 1.0) override;
+
+ // lookup the system font for a particular system font type and set
+ // the name and style characteristics
+ void LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID,
+ nsAString& aSystemFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel);
+
+protected:
+ virtual gfxFontFamily*
+ GetDefaultFontForPlatform(const gfxFontStyle* aStyle) override;
+
+private:
+ friend class gfxPlatformMac;
+
+ gfxMacPlatformFontList();
+ virtual ~gfxMacPlatformFontList();
+
+ // initialize font lists
+ virtual nsresult InitFontListForPlatform() override;
+
+ // special case font faces treated as font families (set via prefs)
+ void InitSingleFaceList();
+
+ // initialize system fonts
+ void InitSystemFontNames();
+
+ // helper function to lookup in both hidden system fonts and normal fonts
+ gfxFontFamily* FindSystemFontFamily(const nsAString& aFamily);
+
+ static void RegisteredFontsChangedNotificationCallback(CFNotificationCenterRef center,
+ void *observer,
+ CFStringRef name,
+ const void *object,
+ CFDictionaryRef userInfo);
+
+ // attempt to use platform-specific fallback for the given character
+ // return null if no usable result found
+ gfxFontEntry*
+ PlatformGlobalFontFallback(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** aMatchedFamily) override;
+
+ bool UsesSystemFallback() override { return true; }
+
+ already_AddRefed<FontInfoData> CreateFontInfoData() override;
+
+ // Add the specified family to mSystemFontFamilies or mFontFamilies.
+ // Ideally we'd use NSString* instead of CFStringRef here, but this header
+ // file is included in .cpp files, so we can't use objective C classes here.
+ // But CFStringRef and NSString* are the same thing anyway (they're
+ // toll-free bridged).
+ void AddFamily(CFStringRef aFamily);
+
+#ifdef MOZ_BUNDLED_FONTS
+ void ActivateBundledFonts();
+#endif
+
+ enum {
+ kATSGenerationInitial = -1
+ };
+
+ // default font for use with system-wide font fallback
+ CTFontRef mDefaultFont;
+
+ // hidden system fonts used within UI elements, there may be a whole set
+ // for different locales (e.g. .Helvetica Neue UI, .SF NS Text)
+ FontFamilyTable mSystemFontFamilies;
+
+ // font families that -apple-system maps to
+ // Pre-10.11 this was always a single font family, such as Lucida Grande
+ // or Helvetica Neue. For OSX 10.11, Apple uses pair of families
+ // for the UI, one for text sizes and another for display sizes
+ bool mUseSizeSensitiveSystemFont;
+ nsString mSystemTextFontFamilyName;
+ nsString mSystemDisplayFontFamilyName; // only used on OSX 10.11
+};
+
+#endif /* gfxMacPlatformFontList_H_ */
diff --git a/gfx/thebes/gfxMacPlatformFontList.mm b/gfx/thebes/gfxMacPlatformFontList.mm
new file mode 100644
index 0000000000..4536ab527b
--- /dev/null
+++ b/gfx/thebes/gfxMacPlatformFontList.mm
@@ -0,0 +1,1444 @@
+/* -*- Mode: ObjC; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: BSD
+ *
+ * Copyright (C) 2006-2009 Mozilla Corporation. All rights reserved.
+ *
+ * Contributor(s):
+ * Vladimir Vukicevic <vladimir@pobox.com>
+ * Masayuki Nakano <masayuki@d-toybox.com>
+ * John Daggett <jdaggett@mozilla.com>
+ * Jonathan Kew <jfkthame@gmail.com>
+ *
+ * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "mozilla/Logging.h"
+
+#include <algorithm>
+
+#import <AppKit/AppKit.h>
+
+#include "gfxPlatformMac.h"
+#include "gfxMacPlatformFontList.h"
+#include "gfxMacFont.h"
+#include "gfxUserFontSet.h"
+#include "harfbuzz/hb.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsISimpleEnumerator.h"
+#include "nsCharTraits.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "gfxFontConstants.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/gfx/2D.h"
+
+#include <unistd.h>
+#include <time.h>
+#include <dlfcn.h>
+
+using namespace mozilla;
+
+// indexes into the NSArray objects that the Cocoa font manager returns
+// as the available members of a family
+#define INDEX_FONT_POSTSCRIPT_NAME 0
+#define INDEX_FONT_FACE_NAME 1
+#define INDEX_FONT_WEIGHT 2
+#define INDEX_FONT_TRAITS 3
+
+static const int kAppleMaxWeight = 14;
+static const int kAppleExtraLightWeight = 3;
+static const int kAppleUltraLightWeight = 2;
+
+static const int gAppleWeightToCSSWeight[] = {
+ 0,
+ 1, // 1.
+ 1, // 2. W1, ultralight
+ 2, // 3. W2, extralight
+ 3, // 4. W3, light
+ 4, // 5. W4, semilight
+ 5, // 6. W5, medium
+ 6, // 7.
+ 6, // 8. W6, semibold
+ 7, // 9. W7, bold
+ 8, // 10. W8, extrabold
+ 8, // 11.
+ 9, // 12. W9, ultrabold
+ 9, // 13
+ 9 // 14
+};
+
+// cache Cocoa's "shared font manager" for performance
+static NSFontManager *sFontManager;
+
+static void GetStringForNSString(const NSString *aSrc, nsAString& aDist)
+{
+ aDist.SetLength([aSrc length]);
+ [aSrc getCharacters:reinterpret_cast<unichar*>(aDist.BeginWriting())];
+}
+
+static NSString* GetNSStringForString(const nsAString& aSrc)
+{
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSrc.BeginReading())
+ length:aSrc.Length()];
+}
+
+#define LOG_FONTLIST(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontlist), \
+ mozilla::LogLevel::Debug, args)
+#define LOG_FONTLIST_ENABLED() MOZ_LOG_TEST( \
+ gfxPlatform::GetLog(eGfxLog_fontlist), \
+ mozilla::LogLevel::Debug)
+#define LOG_CMAPDATA_ENABLED() MOZ_LOG_TEST( \
+ gfxPlatform::GetLog(eGfxLog_cmapdata), \
+ mozilla::LogLevel::Debug)
+
+#pragma mark-
+
+// Complex scripts will not render correctly unless appropriate AAT or OT
+// layout tables are present.
+// For OpenType, we also check that the GSUB table supports the relevant
+// script tag, to avoid using things like Arial Unicode MS for Lao (it has
+// the characters, but lacks OpenType support).
+
+// TODO: consider whether we should move this to gfxFontEntry and do similar
+// cmap-masking on other platforms to avoid using fonts that won't shape
+// properly.
+
+nsresult
+MacOSFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
+{
+ // attempt this once, if errors occur leave a blank cmap
+ if (mCharacterMap) {
+ return NS_OK;
+ }
+
+ RefPtr<gfxCharacterMap> charmap;
+ nsresult rv;
+ bool symbolFont = false; // currently ignored
+
+ if (aFontInfoData && (charmap = GetCMAPFromFontInfo(aFontInfoData,
+ mUVSOffset,
+ symbolFont))) {
+ rv = NS_OK;
+ } else {
+ uint32_t kCMAP = TRUETYPE_TAG('c','m','a','p');
+ charmap = new gfxCharacterMap();
+ AutoTable cmapTable(this, kCMAP);
+
+ if (cmapTable) {
+ bool unicodeFont = false; // currently ignored
+ uint32_t cmapLen;
+ const uint8_t* cmapData =
+ reinterpret_cast<const uint8_t*>(hb_blob_get_data(cmapTable,
+ &cmapLen));
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen,
+ *charmap, mUVSOffset,
+ unicodeFont, symbolFont);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && !HasGraphiteTables()) {
+ // We assume a Graphite font knows what it's doing,
+ // and provides whatever shaping is needed for the
+ // characters it supports, so only check/clear the
+ // complex-script ranges for non-Graphite fonts
+
+ // for layout support, check for the presence of mort/morx and/or
+ // opentype layout tables
+ bool hasAATLayout = HasFontTable(TRUETYPE_TAG('m','o','r','x')) ||
+ HasFontTable(TRUETYPE_TAG('m','o','r','t'));
+ bool hasGSUB = HasFontTable(TRUETYPE_TAG('G','S','U','B'));
+ bool hasGPOS = HasFontTable(TRUETYPE_TAG('G','P','O','S'));
+ if (hasAATLayout && !(hasGSUB || hasGPOS)) {
+ mRequiresAAT = true; // prefer CoreText if font has no OTL tables
+ }
+
+ for (const ScriptRange* sr = gfxPlatformFontList::sComplexScriptRanges;
+ sr->rangeStart; sr++) {
+ // check to see if the cmap includes complex script codepoints
+ if (charmap->TestRange(sr->rangeStart, sr->rangeEnd)) {
+ if (hasAATLayout) {
+ // prefer CoreText for Apple's complex-script fonts,
+ // even if they also have some OpenType tables
+ // (e.g. Geeza Pro Bold on 10.6; see bug 614903)
+ mRequiresAAT = true;
+ // and don't mask off complex-script ranges, we assume
+ // the AAT tables will provide the necessary shaping
+ continue;
+ }
+
+ // We check for GSUB here, as GPOS alone would not be ok.
+ if (hasGSUB && SupportsScriptInGSUB(sr->tags)) {
+ continue;
+ }
+
+ charmap->ClearRange(sr->rangeStart, sr->rangeEnd);
+ }
+ }
+
+ // Bug 1360309, 1393624: several of Apple's Chinese fonts have spurious
+ // blank glyphs for obscure Tibetan and Arabic-script codepoints.
+ // Blacklist these so that font fallback will not use them.
+ if (mRequiresAAT && (FamilyName().EqualsLiteral("Songti SC") ||
+ FamilyName().EqualsLiteral("Songti TC") ||
+ FamilyName().EqualsLiteral("STSong") ||
+ // Bug 1390980: on 10.11, the Kaiti fonts are also affected.
+ FamilyName().EqualsLiteral("Kaiti SC") ||
+ FamilyName().EqualsLiteral("Kaiti TC") ||
+ FamilyName().EqualsLiteral("STKaiti"))) {
+ charmap->ClearRange(0x0f6b, 0x0f70);
+ charmap->ClearRange(0x0f8c, 0x0f8f);
+ charmap->clear(0x0f98);
+ charmap->clear(0x0fbd);
+ charmap->ClearRange(0x0fcd, 0x0fff);
+ charmap->clear(0x0620);
+ charmap->clear(0x065f);
+ charmap->ClearRange(0x06ee, 0x06ef);
+ charmap->clear(0x06ff);
+ }
+ }
+
+ mHasCmapTable = NS_SUCCEEDED(rv);
+ if (mHasCmapTable) {
+ gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
+ mCharacterMap = pfl->FindCharMap(charmap);
+ } else {
+ // if error occurred, initialize to null cmap
+ mCharacterMap = new gfxCharacterMap();
+ }
+
+ LOG_FONTLIST(("(fontlist-cmap) name: %s, size: %d hash: %8.8x%s\n",
+ NS_ConvertUTF16toUTF8(mName).get(),
+ charmap->SizeOfIncludingThis(moz_malloc_size_of),
+ charmap->mHash, mCharacterMap == charmap ? " new" : ""));
+ if (LOG_CMAPDATA_ENABLED()) {
+ char prefix[256];
+ SprintfLiteral(prefix, "(cmapdata) name: %.220s",
+ NS_ConvertUTF16toUTF8(mName).get());
+ charmap->Dump(prefix, eGfxLog_cmapdata);
+ }
+
+ return rv;
+}
+
+gfxFont*
+MacOSFontEntry::CreateFontInstance(const gfxFontStyle *aFontStyle, bool aNeedsBold)
+{
+ return new gfxMacFont(this, aFontStyle, aNeedsBold);
+}
+
+bool
+MacOSFontEntry::IsCFF()
+{
+ if (!mIsCFFInitialized) {
+ mIsCFFInitialized = true;
+ mIsCFF = HasFontTable(TRUETYPE_TAG('C','F','F',' '));
+ }
+
+ return mIsCFF;
+}
+
+MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
+ int32_t aWeight,
+ bool aIsStandardFace,
+ double aSizeHint)
+ : gfxFontEntry(aPostscriptName, aIsStandardFace),
+ mFontRef(NULL),
+ mSizeHint(aSizeHint),
+ mFontRefInitialized(false),
+ mRequiresAAT(false),
+ mIsCFF(false),
+ mIsCFFInitialized(false)
+{
+ mWeight = aWeight;
+}
+
+MacOSFontEntry::MacOSFontEntry(const nsAString& aPostscriptName,
+ CGFontRef aFontRef,
+ uint16_t aWeight, uint16_t aStretch,
+ uint8_t aStyle,
+ bool aIsDataUserFont,
+ bool aIsLocalUserFont)
+ : gfxFontEntry(aPostscriptName, false),
+ mFontRef(NULL),
+ mSizeHint(0.0),
+ mFontRefInitialized(false),
+ mRequiresAAT(false),
+ mIsCFF(false),
+ mIsCFFInitialized(false)
+{
+ mFontRef = aFontRef;
+ mFontRefInitialized = true;
+ ::CFRetain(mFontRef);
+
+ mWeight = aWeight;
+ mStretch = aStretch;
+ mFixedPitch = false; // xxx - do we need this for downloaded fonts?
+ mStyle = aStyle;
+
+ NS_ASSERTION(!(aIsDataUserFont && aIsLocalUserFont),
+ "userfont is either a data font or a local font");
+ mIsDataUserFont = aIsDataUserFont;
+ mIsLocalUserFont = aIsLocalUserFont;
+}
+
+CGFontRef
+MacOSFontEntry::GetFontRef()
+{
+ if (!mFontRefInitialized) {
+ mFontRefInitialized = true;
+ NSString *psname = GetNSStringForString(mName);
+ mFontRef = ::CGFontCreateWithFontName(CFStringRef(psname));
+ if (!mFontRef) {
+ // This happens on macOS 10.12 for font entry names that start with
+ // .AppleSystemUIFont. For those fonts, we need to go through NSFont
+ // to get the correct CGFontRef.
+ // Both the Text and the Display variant of the display font use
+ // .AppleSystemUIFontSomethingSomething as their member names.
+ // That's why we're carrying along mSizeHint to this place so that
+ // we get the variant that we want for this family.
+ NSFont* font = [NSFont fontWithName:psname size:mSizeHint];
+ if (font) {
+ mFontRef = CTFontCopyGraphicsFont((CTFontRef)font, nullptr);
+ }
+ }
+ }
+ return mFontRef;
+}
+
+// For a logging build, we wrap the CFDataRef in a FontTableRec so that we can
+// use the MOZ_COUNT_[CD]TOR macros in it. A release build without logging
+// does not get this overhead.
+class FontTableRec {
+public:
+ explicit FontTableRec(CFDataRef aDataRef)
+ : mDataRef(aDataRef)
+ {
+ MOZ_COUNT_CTOR(FontTableRec);
+ }
+
+ ~FontTableRec() {
+ MOZ_COUNT_DTOR(FontTableRec);
+ ::CFRelease(mDataRef);
+ }
+
+private:
+ CFDataRef mDataRef;
+};
+
+/*static*/ void
+MacOSFontEntry::DestroyBlobFunc(void* aUserData)
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+ FontTableRec *ftr = static_cast<FontTableRec*>(aUserData);
+ delete ftr;
+#else
+ ::CFRelease((CFDataRef)aUserData);
+#endif
+}
+
+hb_blob_t *
+MacOSFontEntry::GetFontTable(uint32_t aTag)
+{
+ CGFontRef fontRef = GetFontRef();
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ CFDataRef dataRef = ::CGFontCopyTableForTag(fontRef, aTag);
+ if (dataRef) {
+ return hb_blob_create((const char*)::CFDataGetBytePtr(dataRef),
+ ::CFDataGetLength(dataRef),
+ HB_MEMORY_MODE_READONLY,
+#ifdef NS_BUILD_REFCNT_LOGGING
+ new FontTableRec(dataRef),
+#else
+ (void*)dataRef,
+#endif
+ DestroyBlobFunc);
+ }
+
+ return nullptr;
+}
+
+bool
+MacOSFontEntry::HasFontTable(uint32_t aTableTag)
+{
+ if (mAvailableTables.Count() == 0) {
+ nsAutoreleasePool localPool;
+
+ CGFontRef fontRef = GetFontRef();
+ if (!fontRef) {
+ return false;
+ }
+ CFArrayRef tags = ::CGFontCopyTableTags(fontRef);
+ if (!tags) {
+ return false;
+ }
+ int numTags = (int) ::CFArrayGetCount(tags);
+ for (int t = 0; t < numTags; t++) {
+ uint32_t tag = (uint32_t)(uintptr_t)::CFArrayGetValueAtIndex(tags, t);
+ mAvailableTables.PutEntry(tag);
+ }
+ ::CFRelease(tags);
+ }
+
+ return mAvailableTables.GetEntry(aTableTag);
+}
+
+void
+MacOSFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontListSizes* aSizes) const
+{
+ aSizes->mFontListSize += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+/* gfxMacFontFamily */
+#pragma mark-
+
+class gfxMacFontFamily : public gfxFontFamily
+{
+public:
+ explicit gfxMacFontFamily(nsAString& aName, double aSizeHint) :
+ gfxFontFamily(aName),
+ mSizeHint(aSizeHint)
+ {}
+
+ virtual ~gfxMacFontFamily() {}
+
+ virtual void LocalizedName(nsAString& aLocalizedName);
+
+ virtual void FindStyleVariations(FontInfoData *aFontInfoData = nullptr);
+
+protected:
+ double mSizeHint;
+};
+
+void
+gfxMacFontFamily::LocalizedName(nsAString& aLocalizedName)
+{
+ nsAutoreleasePool localPool;
+
+ if (!HasOtherFamilyNames()) {
+ aLocalizedName = mName;
+ return;
+ }
+
+ NSString *family = GetNSStringForString(mName);
+ NSString *localized = [sFontManager
+ localizedNameForFamily:family
+ face:nil];
+
+ if (localized) {
+ GetStringForNSString(localized, aLocalizedName);
+ return;
+ }
+
+ // failed to get localized name, just use the canonical one
+ aLocalizedName = mName;
+}
+
+// Return the CSS weight value to use for the given face, overriding what
+// AppKit gives us (used to adjust families with bad weight values, see
+// bug 931426).
+// A return value of 0 indicates no override - use the existing weight.
+static inline int
+GetWeightOverride(const nsAString& aPSName)
+{
+ nsAutoCString prefName("font.weight-override.");
+ // The PostScript name is required to be ASCII; if it's not, the font is
+ // broken anyway, so we really don't care that this is lossy.
+ LossyAppendUTF16toASCII(aPSName, prefName);
+ return Preferences::GetInt(prefName.get(), 0);
+}
+
+void
+gfxMacFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
+{
+ if (mHasStyles)
+ return;
+
+ nsAutoreleasePool localPool;
+
+ NSString *family = GetNSStringForString(mName);
+
+ // create a font entry for each face
+ NSArray *fontfaces = [sFontManager
+ availableMembersOfFontFamily:family]; // returns an array of [psname, style name, weight, traits] elements, goofy api
+ int faceCount = [fontfaces count];
+ int faceIndex;
+
+ for (faceIndex = 0; faceIndex < faceCount; faceIndex++) {
+ NSArray *face = [fontfaces objectAtIndex:faceIndex];
+ NSString *psname = [face objectAtIndex:INDEX_FONT_POSTSCRIPT_NAME];
+ int32_t appKitWeight = [[face objectAtIndex:INDEX_FONT_WEIGHT] unsignedIntValue];
+ uint32_t macTraits = [[face objectAtIndex:INDEX_FONT_TRAITS] unsignedIntValue];
+ NSString *facename = [face objectAtIndex:INDEX_FONT_FACE_NAME];
+ bool isStandardFace = false;
+
+ if (appKitWeight == kAppleExtraLightWeight) {
+ // if the facename contains UltraLight, set the weight to the ultralight weight value
+ NSRange range = [facename rangeOfString:@"ultralight" options:NSCaseInsensitiveSearch];
+ if (range.location != NSNotFound) {
+ appKitWeight = kAppleUltraLightWeight;
+ }
+ }
+
+ // make a nsString
+ nsAutoString postscriptFontName;
+ GetStringForNSString(psname, postscriptFontName);
+
+ int32_t cssWeight = GetWeightOverride(postscriptFontName);
+ if (cssWeight) {
+ // scale down and clamp, to get a value from 1..9
+ cssWeight = ((cssWeight + 50) / 100);
+ cssWeight = std::max(1, std::min(cssWeight, 9));
+ } else {
+ cssWeight =
+ gfxMacPlatformFontList::AppleWeightToCSSWeight(appKitWeight);
+ }
+ cssWeight *= 100; // scale up to CSS values
+
+ if ([facename isEqualToString:@"Regular"] ||
+ [facename isEqualToString:@"Bold"] ||
+ [facename isEqualToString:@"Italic"] ||
+ [facename isEqualToString:@"Oblique"] ||
+ [facename isEqualToString:@"Bold Italic"] ||
+ [facename isEqualToString:@"Bold Oblique"])
+ {
+ isStandardFace = true;
+ }
+
+ // create a font entry
+ MacOSFontEntry *fontEntry =
+ new MacOSFontEntry(postscriptFontName, cssWeight, isStandardFace, mSizeHint);
+ if (!fontEntry) {
+ break;
+ }
+
+ // set additional properties based on the traits reported by Cocoa
+ if (macTraits & (NSCondensedFontMask | NSNarrowFontMask | NSCompressedFontMask)) {
+ fontEntry->mStretch = NS_FONT_STRETCH_CONDENSED;
+ } else if (macTraits & NSExpandedFontMask) {
+ fontEntry->mStretch = NS_FONT_STRETCH_EXPANDED;
+ }
+ // Cocoa fails to set the Italic traits bit for HelveticaLightItalic,
+ // at least (see bug 611855), so check for style name endings as well
+ if ((macTraits & NSItalicFontMask) ||
+ [facename hasSuffix:@"Italic"] ||
+ [facename hasSuffix:@"Oblique"])
+ {
+ fontEntry->mStyle = NS_FONT_STYLE_ITALIC;
+ }
+ if (macTraits & NSFixedPitchFontMask) {
+ fontEntry->mFixedPitch = true;
+ }
+
+ if (LOG_FONTLIST_ENABLED()) {
+ LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
+ " with style: %s weight: %d stretch: %d"
+ " (apple-weight: %d macTraits: %8.8x)",
+ NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
+ NS_ConvertUTF16toUTF8(Name()).get(),
+ fontEntry->IsItalic() ? "italic" : "normal",
+ cssWeight, fontEntry->Stretch(),
+ appKitWeight, macTraits));
+ }
+
+ // insert into font entry array of family
+ AddFontEntry(fontEntry);
+ }
+
+ SortAvailableFonts();
+ SetHasStyles(true);
+
+ if (mIsBadUnderlineFamily) {
+ SetBadUnderlineFonts();
+ }
+}
+
+/* gfxSingleFaceMacFontFamily */
+#pragma mark-
+
+class gfxSingleFaceMacFontFamily : public gfxFontFamily
+{
+public:
+ explicit gfxSingleFaceMacFontFamily(nsAString& aName) :
+ gfxFontFamily(aName)
+ {
+ mFaceNamesInitialized = true; // omit from face name lists
+ }
+
+ virtual ~gfxSingleFaceMacFontFamily() {}
+
+ virtual void LocalizedName(nsAString& aLocalizedName);
+
+ virtual void ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList);
+};
+
+void
+gfxSingleFaceMacFontFamily::LocalizedName(nsAString& aLocalizedName)
+{
+ nsAutoreleasePool localPool;
+
+ if (!HasOtherFamilyNames()) {
+ aLocalizedName = mName;
+ return;
+ }
+
+ gfxFontEntry *fe = mAvailableFonts[0];
+ NSFont *font = [NSFont fontWithName:GetNSStringForString(fe->Name())
+ size:0.0];
+ if (font) {
+ NSString *localized = [font displayName];
+ if (localized) {
+ GetStringForNSString(localized, aLocalizedName);
+ return;
+ }
+ }
+
+ // failed to get localized name, just use the canonical one
+ aLocalizedName = mName;
+}
+
+void
+gfxSingleFaceMacFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList)
+{
+ if (mOtherFamilyNamesInitialized) {
+ return;
+ }
+
+ gfxFontEntry *fe = mAvailableFonts[0];
+ if (!fe) {
+ return;
+ }
+
+ const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e');
+
+ gfxFontEntry::AutoTable nameTable(fe, kNAME);
+ if (!nameTable) {
+ return;
+ }
+
+ mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList,
+ nameTable,
+ true);
+
+ mOtherFamilyNamesInitialized = true;
+}
+
+/* gfxMacPlatformFontList */
+#pragma mark-
+
+gfxMacPlatformFontList::gfxMacPlatformFontList() :
+ gfxPlatformFontList(false),
+ mDefaultFont(nullptr),
+ mUseSizeSensitiveSystemFont(false)
+{
+#ifdef MOZ_BUNDLED_FONTS
+ ActivateBundledFonts();
+#endif
+
+ ::CFNotificationCenterAddObserver(::CFNotificationCenterGetLocalCenter(),
+ this,
+ RegisteredFontsChangedNotificationCallback,
+ kCTFontManagerRegisteredFontsChangedNotification,
+ 0,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+
+ // cache this in a static variable so that MacOSFontFamily objects
+ // don't have to repeatedly look it up
+ sFontManager = [NSFontManager sharedFontManager];
+}
+
+gfxMacPlatformFontList::~gfxMacPlatformFontList()
+{
+ if (mDefaultFont) {
+ ::CFRelease(mDefaultFont);
+ }
+}
+
+void
+gfxMacPlatformFontList::AddFamily(CFStringRef aFamily)
+{
+ NSString* family = (NSString*)aFamily;
+
+ // CTFontManager includes weird internal family names and
+ // LastResort, skip over those
+ if (!family || [family caseInsensitiveCompare:@"LastResort"] == NSOrderedSame) {
+ return;
+ }
+
+ bool hiddenSystemFont = [family hasPrefix:@"."];
+
+ FontFamilyTable& table =
+ hiddenSystemFont ? mSystemFontFamilies : mFontFamilies;
+
+ nsAutoString familyName;
+ nsCocoaUtils::GetStringForNSString(family, familyName);
+
+ double sizeHint = 0.0;
+ if (hiddenSystemFont && mUseSizeSensitiveSystemFont &&
+ mSystemDisplayFontFamilyName.Equals(familyName)) {
+ sizeHint = 128.0;
+ }
+
+ nsAutoString key;
+ ToLowerCase(familyName, key);
+
+ RefPtr<gfxFontFamily> familyEntry = new gfxMacFontFamily(familyName, sizeHint);
+ table.Put(key, familyEntry);
+
+ // check the bad underline blacklist
+ if (mBadUnderlineFamilyNames.Contains(key)) {
+ familyEntry->SetBadUnderlineFamily();
+ }
+}
+
+nsresult
+gfxMacPlatformFontList::InitFontListForPlatform()
+{
+ nsAutoreleasePool localPool;
+
+ // reset system font list
+ mSystemFontFamilies.Clear();
+
+ // iterate over available families
+
+ InitSystemFontNames();
+
+ CFArrayRef familyNames = CTFontManagerCopyAvailableFontFamilyNames();
+
+ for (NSString* familyName in (NSArray*)familyNames) {
+ AddFamily((CFStringRef)familyName);
+ }
+
+ CFRelease(familyNames);
+
+ InitSingleFaceList();
+
+ // to avoid full search of font name tables, seed the other names table with localized names from
+ // some of the prefs fonts which are accessed via their localized names. changes in the pref fonts will only cause
+ // a font lookup miss earlier. this is a simple optimization, it's not required for correctness
+ PreloadNamesList();
+
+ // start the delayed cmap loader
+ GetPrefsAndStartLoader();
+
+ return NS_OK;
+}
+
+void
+gfxMacPlatformFontList::InitSingleFaceList()
+{
+ AutoTArray<nsString, 10> singleFaceFonts;
+ gfxFontUtils::GetPrefsFontList("font.single-face-list", singleFaceFonts);
+
+ for (const auto& singleFaceFamily : singleFaceFonts) {
+ LOG_FONTLIST(("(fontlist-singleface) face name: %s\n",
+ NS_ConvertUTF16toUTF8(singleFaceFamily).get()));
+ // Each entry in the "single face families" list is expected to be a
+ // colon-separated pair of FaceName:Family,
+ // where FaceName is the individual face name (psname) of a font
+ // that should be exposed as a separate family name,
+ // and Family is the standard family to which that face belongs.
+ // The only such face listed by default is
+ // Osaka-Mono:Osaka
+ nsAutoString familyName(singleFaceFamily);
+ auto colon = familyName.FindChar(':');
+ if (colon == kNotFound) {
+ continue;
+ }
+
+ // Look for the parent family in the main font family list,
+ // and ensure we have loaded its list of available faces.
+ nsAutoString key(Substring(familyName, colon + 1));
+ ToLowerCase(key);
+ gfxFontFamily* family = mFontFamilies.GetWeak(key);
+ if (!family) {
+ continue;
+ }
+ family->FindStyleVariations();
+
+ // Truncate the entry from prefs at the colon, so now it is just the
+ // desired single-face-family name.
+ familyName.Truncate(colon);
+
+ // Look through the family's faces to see if this one is present.
+ const gfxFontEntry* fe = nullptr;
+ for (const auto& face : family->GetFontList()) {
+ if (face->Name().Equals(familyName)) {
+ fe = face;
+ break;
+ }
+ }
+ if (!fe) {
+ continue;
+ }
+
+ // We found the correct face, so create the single-face family record.
+ GenerateFontListKey(familyName, key);
+ LOG_FONTLIST(("(fontlist-singleface) family name: %s, key: %s\n",
+ NS_ConvertUTF16toUTF8(familyName).get(),
+ NS_ConvertUTF16toUTF8(key).get()));
+
+ // add only if doesn't exist already
+ if (!mFontFamilies.GetWeak(key)) {
+ RefPtr<gfxFontFamily> familyEntry =
+ new gfxSingleFaceMacFontFamily(familyName);
+ // We need a separate font entry, because its family name will
+ // differ from the one we found in the main list.
+ MacOSFontEntry* fontEntry =
+ new MacOSFontEntry(fe->Name(), fe->mWeight, true,
+ static_cast<const MacOSFontEntry*>(fe)->
+ mSizeHint);
+ familyEntry->AddFontEntry(fontEntry);
+ familyEntry->SetHasStyles(true);
+ mFontFamilies.Put(key, familyEntry);
+ LOG_FONTLIST(("(fontlist-singleface) added new family\n",
+ NS_ConvertUTF16toUTF8(familyName).get(),
+ NS_ConvertUTF16toUTF8(key).get()));
+ }
+ }
+}
+
+// System fonts under OSX may contain weird "meta" names but if we create
+// a new font using just the Postscript name, the NSFont api returns an object
+// with the actual real family name. For example, under OSX 10.11:
+//
+// [[NSFont menuFontOfSize:8.0] familyName] ==> .AppleSystemUIFont
+// [[NSFont fontWithName:[[[NSFont menuFontOfSize:8.0] fontDescriptor] postscriptName]
+// size:8.0] familyName] ==> .SF NS Text
+
+static NSString* GetRealFamilyName(NSFont* aFont)
+{
+ NSFont* f = [NSFont fontWithName: [[aFont fontDescriptor] postscriptName]
+ size: 0.0];
+ return [f familyName];
+}
+
+// System fonts under OSX 10.11 use a combination of two families, one
+// for text sizes and another for larger, display sizes. Each has a
+// different number of weights. There aren't efficient API's for looking
+// this information up, so hard code the logic here but confirm via
+// debug assertions that the logic is correct.
+
+const CGFloat kTextDisplayCrossover = 20.0; // use text family below this size
+
+void
+gfxMacPlatformFontList::InitSystemFontNames()
+{
+ // system font under 10.11 are two distinct families for text/display sizes
+ if (nsCocoaFeatures::OnElCapitanOrLater()) {
+ mUseSizeSensitiveSystemFont = true;
+ }
+
+ // text font family
+ NSFont* sys = [NSFont systemFontOfSize: 0.0];
+ NSString* textFamilyName = GetRealFamilyName(sys);
+ nsAutoString familyName;
+ nsCocoaUtils::GetStringForNSString(textFamilyName, familyName);
+ mSystemTextFontFamilyName = familyName;
+
+ // display font family, if on OSX 10.11
+ if (mUseSizeSensitiveSystemFont) {
+ NSFont* displaySys = [NSFont systemFontOfSize: 128.0];
+ NSString* displayFamilyName = GetRealFamilyName(displaySys);
+ nsCocoaUtils::GetStringForNSString(displayFamilyName, familyName);
+ mSystemDisplayFontFamilyName = familyName;
+
+#if DEBUG
+ // confirm that the optical size switch is at 20.0
+ NS_ASSERTION([textFamilyName compare:displayFamilyName] != NSOrderedSame,
+ "system text/display fonts are the same!");
+ NSString* fam19 = GetRealFamilyName([NSFont systemFontOfSize:
+ (kTextDisplayCrossover - 1.0)]);
+ NSString* fam20 = GetRealFamilyName([NSFont systemFontOfSize:
+ kTextDisplayCrossover]);
+ NS_ASSERTION(fam19 && fam20 && [fam19 compare:fam20] != NSOrderedSame,
+ "system text/display font size switch point is not as expected!");
+#endif
+ }
+
+#ifdef DEBUG
+ // different system font API's always map to the same family under OSX, so
+ // just assume that and emit a warning if that ever changes
+ NSString *sysFamily = GetRealFamilyName([NSFont systemFontOfSize:0.0]);
+ if ([sysFamily compare:GetRealFamilyName([NSFont boldSystemFontOfSize:0.0])] != NSOrderedSame ||
+ [sysFamily compare:GetRealFamilyName([NSFont controlContentFontOfSize:0.0])] != NSOrderedSame ||
+ [sysFamily compare:GetRealFamilyName([NSFont menuBarFontOfSize:0.0])] != NSOrderedSame ||
+ [sysFamily compare:GetRealFamilyName([NSFont toolTipsFontOfSize:0.0])] != NSOrderedSame) {
+ NS_WARNING("system font types map to different font families"
+ " -- please log a bug!!");
+ }
+#endif
+}
+
+gfxFontFamily*
+gfxMacPlatformFontList::FindSystemFontFamily(const nsAString& aFamily)
+{
+ nsAutoString key;
+ GenerateFontListKey(aFamily, key);
+
+ gfxFontFamily* familyEntry;
+
+ // lookup in hidden system family name list
+ if ((familyEntry = mSystemFontFamilies.GetWeak(key))) {
+ return CheckFamily(familyEntry);
+ }
+
+ // lookup in user-exposed family name list
+ if ((familyEntry = mFontFamilies.GetWeak(key))) {
+ return CheckFamily(familyEntry);
+ }
+
+ return nullptr;
+}
+
+bool
+gfxMacPlatformFontList::GetStandardFamilyName(const nsAString& aFontName, nsAString& aFamilyName)
+{
+ gfxFontFamily *family = FindFamily(aFontName);
+ if (family) {
+ family->LocalizedName(aFamilyName);
+ return true;
+ }
+
+ return false;
+}
+
+void
+gfxMacPlatformFontList::RegisteredFontsChangedNotificationCallback(CFNotificationCenterRef center,
+ void *observer,
+ CFStringRef name,
+ const void *object,
+ CFDictionaryRef userInfo)
+{
+ if (!::CFEqual(name, kCTFontManagerRegisteredFontsChangedNotification)) {
+ return;
+ }
+
+ gfxMacPlatformFontList* fl = static_cast<gfxMacPlatformFontList*>(observer);
+
+ // xxx - should be carefully pruning the list of fonts, not rebuilding it from scratch
+ fl->UpdateFontList();
+
+ // modify a preference that will trigger reflow everywhere
+ fl->ForceGlobalReflow();
+}
+
+gfxFontEntry*
+gfxMacPlatformFontList::PlatformGlobalFontFallback(const uint32_t aCh,
+ Script aRunScript,
+ const gfxFontStyle* aMatchStyle,
+ gfxFontFamily** aMatchedFamily)
+{
+ CFStringRef str;
+ UniChar ch[2];
+ CFIndex length = 1;
+
+ if (IS_IN_BMP(aCh)) {
+ ch[0] = aCh;
+ str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 1,
+ kCFAllocatorNull);
+ } else {
+ ch[0] = H_SURROGATE(aCh);
+ ch[1] = L_SURROGATE(aCh);
+ str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, ch, 2,
+ kCFAllocatorNull);
+ if (!str) {
+ return nullptr;
+ }
+ length = 2;
+ }
+
+ // use CoreText to find the fallback family
+
+ gfxFontEntry *fontEntry = nullptr;
+ CTFontRef fallback;
+ bool cantUseFallbackFont = false;
+
+ if (!mDefaultFont) {
+ mDefaultFont = ::CTFontCreateWithName(CFSTR("LucidaGrande"), 12.f,
+ NULL);
+ }
+
+ fallback = ::CTFontCreateForString(mDefaultFont, str,
+ ::CFRangeMake(0, length));
+
+ if (fallback) {
+ CFStringRef familyNameRef = ::CTFontCopyFamilyName(fallback);
+ ::CFRelease(fallback);
+
+ if (familyNameRef &&
+ ::CFStringCompare(familyNameRef, CFSTR("LastResort"),
+ kCFCompareCaseInsensitive) != kCFCompareEqualTo)
+ {
+ AutoTArray<UniChar, 1024> buffer;
+ CFIndex familyNameLen = ::CFStringGetLength(familyNameRef);
+ buffer.SetLength(familyNameLen+1);
+ ::CFStringGetCharacters(familyNameRef, ::CFRangeMake(0, familyNameLen),
+ buffer.Elements());
+ buffer[familyNameLen] = 0;
+ nsDependentString familyNameString(reinterpret_cast<char16_t*>(buffer.Elements()), familyNameLen);
+
+ bool needsBold; // ignored in the system fallback case
+
+ gfxFontFamily *family = FindFamily(familyNameString);
+ if (family) {
+ fontEntry = family->FindFontForStyle(*aMatchStyle, needsBold);
+ if (fontEntry) {
+ if (fontEntry->HasCharacter(aCh)) {
+ *aMatchedFamily = family;
+ } else {
+ fontEntry = nullptr;
+ cantUseFallbackFont = true;
+ }
+ }
+ }
+ }
+
+ if (familyNameRef) {
+ ::CFRelease(familyNameRef);
+ }
+ }
+
+ ::CFRelease(str);
+
+ return fontEntry;
+}
+
+gfxFontFamily*
+gfxMacPlatformFontList::GetDefaultFontForPlatform(const gfxFontStyle* aStyle)
+{
+ nsAutoreleasePool localPool;
+
+ NSString *defaultFamily = [[NSFont userFontOfSize:aStyle->size] familyName];
+ nsAutoString familyName;
+
+ GetStringForNSString(defaultFamily, familyName);
+ return FindFamily(familyName);
+}
+
+int32_t
+gfxMacPlatformFontList::AppleWeightToCSSWeight(int32_t aAppleWeight)
+{
+ if (aAppleWeight < 1)
+ aAppleWeight = 1;
+ else if (aAppleWeight > kAppleMaxWeight)
+ aAppleWeight = kAppleMaxWeight;
+ return gAppleWeightToCSSWeight[aAppleWeight];
+}
+
+gfxFontEntry*
+gfxMacPlatformFontList::LookupLocalFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle)
+{
+ nsAutoreleasePool localPool;
+
+ NSString *faceName = GetNSStringForString(aFontName);
+ MacOSFontEntry *newFontEntry;
+
+ // lookup face based on postscript or full name
+ CGFontRef fontRef = ::CGFontCreateWithFontName(CFStringRef(faceName));
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ NS_ASSERTION(aWeight >= 100 && aWeight <= 900,
+ "bogus font weight value!");
+
+ newFontEntry =
+ new MacOSFontEntry(aFontName, fontRef, aWeight, aStretch, aStyle,
+ false, true);
+ ::CFRelease(fontRef);
+
+ return newFontEntry;
+}
+
+static void ReleaseData(void *info, const void *data, size_t size)
+{
+ free((void*)data);
+}
+
+gfxFontEntry*
+gfxMacPlatformFontList::MakePlatformFont(const nsAString& aFontName,
+ uint16_t aWeight,
+ int16_t aStretch,
+ uint8_t aStyle,
+ const uint8_t* aFontData,
+ uint32_t aLength)
+{
+ NS_ASSERTION(aFontData, "MakePlatformFont called with null data");
+
+ NS_ASSERTION(aWeight >= 100 && aWeight <= 900, "bogus font weight value!");
+
+ // create the font entry
+ nsAutoString uniqueName;
+
+ nsresult rv = gfxFontUtils::MakeUniqueUserFontName(uniqueName);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ CGDataProviderRef provider =
+ ::CGDataProviderCreateWithData(nullptr, aFontData, aLength,
+ &ReleaseData);
+ CGFontRef fontRef = ::CGFontCreateWithDataProvider(provider);
+ ::CGDataProviderRelease(provider);
+
+ if (!fontRef) {
+ return nullptr;
+ }
+
+ auto newFontEntry =
+ MakeUnique<MacOSFontEntry>(uniqueName, fontRef, aWeight, aStretch,
+ aStyle, true, false);
+ ::CFRelease(fontRef);
+
+ // if succeeded and font cmap is good, return the new font
+ if (newFontEntry->mIsValid && NS_SUCCEEDED(newFontEntry->ReadCMAP())) {
+ return newFontEntry.release();
+ }
+
+ // if something is funky about this font, delete immediately
+
+#if DEBUG
+ NS_WARNING("downloaded font not loaded properly");
+#endif
+
+ return nullptr;
+}
+
+// Webkit code uses a system font meta name, so mimic that here
+// WebCore/platform/graphics/mac/FontCacheMac.mm
+static const char kSystemFont_system[] = "-apple-system";
+
+bool
+gfxMacPlatformFontList::FindAndAddFamilies(const nsAString& aFamily,
+ nsTArray<gfxFontFamily*>* aOutput,
+ gfxFontStyle* aStyle,
+ gfxFloat aDevToCssSize)
+{
+ // search for special system font name, -apple-system
+ if (aFamily.EqualsLiteral(kSystemFont_system)) {
+ if (mUseSizeSensitiveSystemFont &&
+ aStyle && (aStyle->size * aDevToCssSize) >= kTextDisplayCrossover) {
+ aOutput->AppendElement(FindSystemFontFamily(mSystemDisplayFontFamilyName));
+ return true;
+ }
+ aOutput->AppendElement(FindSystemFontFamily(mSystemTextFontFamilyName));
+ return true;
+ }
+
+ return gfxPlatformFontList::FindAndAddFamilies(aFamily, aOutput, aStyle,
+ aDevToCssSize);
+}
+
+void
+gfxMacPlatformFontList::LookupSystemFont(LookAndFeel::FontID aSystemFontID,
+ nsAString& aSystemFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ // code moved here from widget/cocoa/nsLookAndFeel.mm
+ NSFont *font = nullptr;
+ char* systemFontName = nullptr;
+ switch (aSystemFontID) {
+ case LookAndFeel::eFont_MessageBox:
+ case LookAndFeel::eFont_StatusBar:
+ case LookAndFeel::eFont_List:
+ case LookAndFeel::eFont_Field:
+ case LookAndFeel::eFont_Button:
+ case LookAndFeel::eFont_Widget:
+ font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_SmallCaption:
+ font = [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_Icon: // used in urlbar; tried labelFont, but too small
+ case LookAndFeel::eFont_Workspace:
+ case LookAndFeel::eFont_Desktop:
+ case LookAndFeel::eFont_Info:
+ font = [NSFont controlContentFontOfSize:0.0];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_PullDownMenu:
+ font = [NSFont menuBarFontOfSize:0.0];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_Tooltips:
+ font = [NSFont toolTipsFontOfSize:0.0];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+
+ case LookAndFeel::eFont_Caption:
+ case LookAndFeel::eFont_Menu:
+ case LookAndFeel::eFont_Dialog:
+ default:
+ font = [NSFont systemFontOfSize:0.0];
+ systemFontName = (char*) kSystemFont_system;
+ break;
+ }
+ NS_ASSERTION(font, "system font not set");
+ NS_ASSERTION(systemFontName, "system font name not set");
+
+ if (systemFontName) {
+ aSystemFontName.AssignASCII(systemFontName);
+ }
+
+ NSFontSymbolicTraits traits = [[font fontDescriptor] symbolicTraits];
+ aFontStyle.style =
+ (traits & NSFontItalicTrait) ? NS_FONT_STYLE_ITALIC : NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight =
+ (traits & NSFontBoldTrait) ? NS_FONT_WEIGHT_BOLD : NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch =
+ (traits & NSFontExpandedTrait) ?
+ NS_FONT_STRETCH_EXPANDED : (traits & NSFontCondensedTrait) ?
+ NS_FONT_STRETCH_CONDENSED : NS_FONT_STRETCH_NORMAL;
+ // convert size from css pixels to device pixels
+ aFontStyle.size = [font pointSize] * aDevPixPerCSSPixel;
+ aFontStyle.systemFont = true;
+}
+
+// used to load system-wide font info on off-main thread
+class MacFontInfo : public FontInfoData {
+public:
+ MacFontInfo(bool aLoadOtherNames,
+ bool aLoadFaceNames,
+ bool aLoadCmaps) :
+ FontInfoData(aLoadOtherNames, aLoadFaceNames, aLoadCmaps)
+ {}
+
+ virtual ~MacFontInfo() {}
+
+ virtual void Load() {
+ nsAutoreleasePool localPool;
+ FontInfoData::Load();
+ }
+
+ // loads font data for all members of a given family
+ virtual void LoadFontFamilyData(const nsAString& aFamilyName);
+};
+
+void
+MacFontInfo::LoadFontFamilyData(const nsAString& aFamilyName)
+{
+ // family name ==> CTFontDescriptor
+ NSString *famName = GetNSStringForString(aFamilyName);
+ CFStringRef family = CFStringRef(famName);
+
+ CFMutableDictionaryRef attr =
+ CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, family);
+ CTFontDescriptorRef fd = CTFontDescriptorCreateWithAttributes(attr);
+ CFRelease(attr);
+ CFArrayRef matchingFonts =
+ CTFontDescriptorCreateMatchingFontDescriptors(fd, NULL);
+ CFRelease(fd);
+ if (!matchingFonts) {
+ return;
+ }
+
+ nsTArray<nsString> otherFamilyNames;
+ bool hasOtherFamilyNames = true;
+
+ // iterate over faces in the family
+ int f, numFaces = (int) CFArrayGetCount(matchingFonts);
+ for (f = 0; f < numFaces; f++) {
+ mLoadStats.fonts++;
+
+ CTFontDescriptorRef faceDesc =
+ (CTFontDescriptorRef)CFArrayGetValueAtIndex(matchingFonts, f);
+ if (!faceDesc) {
+ continue;
+ }
+ CTFontRef fontRef = CTFontCreateWithFontDescriptor(faceDesc,
+ 0.0, nullptr);
+ if (!fontRef) {
+ NS_WARNING("failed to create a CTFontRef");
+ continue;
+ }
+
+ if (mLoadCmaps) {
+ // face name
+ CFStringRef faceName = (CFStringRef)
+ CTFontDescriptorCopyAttribute(faceDesc, kCTFontNameAttribute);
+
+ AutoTArray<UniChar, 1024> buffer;
+ CFIndex len = CFStringGetLength(faceName);
+ buffer.SetLength(len+1);
+ CFStringGetCharacters(faceName, ::CFRangeMake(0, len),
+ buffer.Elements());
+ buffer[len] = 0;
+ nsAutoString fontName(reinterpret_cast<char16_t*>(buffer.Elements()),
+ len);
+
+ // load the cmap data
+ FontFaceData fontData;
+ CFDataRef cmapTable = CTFontCopyTable(fontRef, kCTFontTableCmap,
+ kCTFontTableOptionNoOptions);
+
+ if (cmapTable) {
+ const uint8_t *cmapData =
+ (const uint8_t*)CFDataGetBytePtr(cmapTable);
+ uint32_t cmapLen = CFDataGetLength(cmapTable);
+ RefPtr<gfxCharacterMap> charmap = new gfxCharacterMap();
+ uint32_t offset;
+ bool unicodeFont = false; // ignored
+ bool symbolFont = false;
+ nsresult rv;
+
+ rv = gfxFontUtils::ReadCMAP(cmapData, cmapLen, *charmap, offset,
+ unicodeFont, symbolFont);
+ if (NS_SUCCEEDED(rv)) {
+ fontData.mCharacterMap = charmap;
+ fontData.mUVSOffset = offset;
+ fontData.mSymbolFont = symbolFont;
+ mLoadStats.cmaps++;
+ }
+ CFRelease(cmapTable);
+ }
+
+ mFontFaceData.Put(fontName, fontData);
+ CFRelease(faceName);
+ }
+
+ if (mLoadOtherNames && hasOtherFamilyNames) {
+ CFDataRef nameTable = CTFontCopyTable(fontRef, kCTFontTableName,
+ kCTFontTableOptionNoOptions);
+
+ if (nameTable) {
+ const char *nameData = (const char*)CFDataGetBytePtr(nameTable);
+ uint32_t nameLen = CFDataGetLength(nameTable);
+ gfxFontFamily::ReadOtherFamilyNamesForFace(aFamilyName,
+ nameData, nameLen,
+ otherFamilyNames,
+ false);
+ hasOtherFamilyNames = otherFamilyNames.Length() != 0;
+ CFRelease(nameTable);
+ }
+ }
+
+ CFRelease(fontRef);
+ }
+ CFRelease(matchingFonts);
+
+ // if found other names, insert them in the hash table
+ if (otherFamilyNames.Length() != 0) {
+ mOtherFamilyNames.Put(aFamilyName, otherFamilyNames);
+ mLoadStats.othernames += otherFamilyNames.Length();
+ }
+}
+
+already_AddRefed<FontInfoData>
+gfxMacPlatformFontList::CreateFontInfoData()
+{
+ bool loadCmaps = !UsesSystemFallback() ||
+ gfxPlatform::GetPlatform()->UseCmapsDuringSystemFallback();
+
+ RefPtr<MacFontInfo> fi =
+ new MacFontInfo(true, NeedFullnamePostscriptNames(), loadCmaps);
+ return fi.forget();
+}
+
+#ifdef MOZ_BUNDLED_FONTS
+
+void
+gfxMacPlatformFontList::ActivateBundledFonts()
+{
+ nsCOMPtr<nsIFile> localDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("fonts")))) {
+ return;
+ }
+ bool isDir;
+ if (NS_FAILED(localDir->IsDirectory(&isDir)) || !isDir) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = localDir->GetDirectoryEntries(getter_AddRefs(e));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool hasMore;
+ while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> entry;
+ if (NS_FAILED(e->GetNext(getter_AddRefs(entry)))) {
+ break;
+ }
+ nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+ if (!file) {
+ continue;
+ }
+ nsCString path;
+ if (NS_FAILED(file->GetNativePath(path))) {
+ continue;
+ }
+ CFURLRef fontURL =
+ ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+ (uint8_t*)path.get(),
+ path.Length(),
+ false);
+ if (fontURL) {
+ CFErrorRef error = nullptr;
+ ::CTFontManagerRegisterFontsForURL(fontURL,
+ kCTFontManagerScopeProcess,
+ &error);
+ ::CFRelease(fontURL);
+ }
+ }
+}
+
+#endif
diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp
index 21e15bf06f..7ec9139c0a 100644
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -37,6 +37,9 @@
#if defined(XP_WIN)
#include "gfxWindowsPlatform.h"
+#elif defined(XP_MACOSX)
+#include "gfxPlatformMac.h"
+#include "gfxQuartzSurface.h"
#elif defined(MOZ_WIDGET_GTK)
#include "gfxPlatformGtk.h"
#elif defined(ANDROID)
@@ -571,6 +574,8 @@ gfxPlatform::Init()
#if defined(XP_WIN)
gPlatform = new gfxWindowsPlatform;
+#elif defined(XP_MACOSX)
+ gPlatform = new gfxPlatformMac;
#elif defined(MOZ_WIDGET_GTK)
gPlatform = new gfxPlatformGtk;
#elif defined(ANDROID)
@@ -2127,7 +2132,7 @@ bool
gfxPlatform::AccelerateLayersByDefault()
{
// Note: add any new platform defines here that should get HWA by default.
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_UIKIT)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_UIKIT)
return true;
#elif defined(MOZ_GL_PROVIDER)
// GL provider manually declared
diff --git a/gfx/thebes/gfxPlatformMac.cpp b/gfx/thebes/gfxPlatformMac.cpp
new file mode 100644
index 0000000000..75c5236a87
--- /dev/null
+++ b/gfx/thebes/gfxPlatformMac.cpp
@@ -0,0 +1,617 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/gfx/2D.h"
+#include "mozilla/gfx/MacIOSurface.h"
+
+#include "gfxMacPlatformFontList.h"
+#include "gfxMacFont.h"
+#include "gfxCoreTextShaper.h"
+#include "gfxTextRun.h"
+#include "gfxUserFontSet.h"
+
+#include "nsTArray.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "nsUnicodeProperties.h"
+#include "qcms.h"
+#include "gfx2DGlue.h"
+
+#include <dlfcn.h>
+#include <CoreVideo/CoreVideo.h>
+
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "VsyncSource.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::unicode;
+
+// 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();
+
+ uint32_t canvasMask = BackendTypeBit(BackendType::SKIA);
+ uint32_t contentMask = BackendTypeBit(BackendType::SKIA);
+ InitBackendPrefs(canvasMask, BackendType::SKIA,
+ contentMask, BackendType::SKIA);
+
+ // XXX: Bug 1036682 - we run out of fds on Mac when using tiled layers because
+ // with 256x256 tiles we can easily hit the soft limit of 800 when using double
+ // buffered tiles in e10s, so let's bump the soft limit to the hard limit for the OS
+ // up to a new cap of OPEN_MAX.
+ struct rlimit limits;
+ if (getrlimit(RLIMIT_NOFILE, &limits) == 0) {
+ limits.rlim_cur = std::min(rlim_t(OPEN_MAX), limits.rlim_max);
+ if (setrlimit(RLIMIT_NOFILE, &limits) != 0) {
+ NS_WARNING("Unable to bump RLIMIT_NOFILE to the maximum number on this OS");
+ }
+ }
+
+ MacIOSurfaceLib::LoadLibrary();
+}
+
+gfxPlatformMac::~gfxPlatformMac()
+{
+ gfxCoreTextShaper::Shutdown();
+}
+
+gfxPlatformFontList*
+gfxPlatformMac::CreatePlatformFontList()
+{
+ gfxPlatformFontList* list = new gfxMacPlatformFontList();
+ if (NS_SUCCEEDED(list->InitFontList())) {
+ return list;
+ }
+ gfxPlatformFontList::Shutdown();
+ return nullptr;
+}
+
+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();
+}
+
+already_AddRefed<ScaledFont>
+gfxPlatformMac::GetScaledFontForFont(DrawTarget* aTarget, gfxFont *aFont)
+{
+ gfxMacFont *font = static_cast<gfxMacFont*>(aFont);
+ return font->GetScaledFont(aTarget);
+}
+
+gfxFontGroup *
+gfxPlatformMac::CreateFontGroup(const FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize)
+{
+ return new gfxFontGroup(aFontFamilyList, aStyle, aTextPerf,
+ aUserFontSet, aDevToCssSize);
+}
+
+bool
+gfxPlatformMac::IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags)
+{
+ // check for strange format flags
+ NS_ASSERTION(!(aFormatFlags & gfxUserFontSet::FLAG_FORMAT_NOT_USED),
+ "strange font format hint set");
+
+ // accept supported formats
+ if (aFormatFlags & (gfxUserFontSet::FLAG_FORMATS_COMMON |
+ gfxUserFontSet::FLAG_FORMAT_TRUETYPE_AAT)) {
+ return true;
+ }
+
+ // reject all other formats, known and unknown
+ if (aFormatFlags != 0) {
+ return false;
+ }
+
+ // no format hint set, need to look at data
+ return true;
+}
+
+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 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 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";
+
+void
+gfxPlatformMac::GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList)
+{
+ EmojiPresentation emoji = GetEmojiPresentation(aCh);
+ if (emoji != EmojiPresentation::TextOnly) {
+ if (aNextCh == kVariationSelector16 ||
+ (aNextCh != kVariationSelector15 &&
+ emoji == EmojiPresentation::EmojiDefault)) {
+ // if char is followed by VS16, try for a color emoji glyph
+ 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(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(kFontSTHeiti);
+ aFontList.AppendElement(kFontEuphemiaUCAS);
+ break;
+ case 0x19: // Khmer
+ aFontList.AppendElement(kFontKhmerMN);
+ aFontList.AppendElement(kFontMicrosoftTaiLe);
+ break;
+ case 0x1d:
+ case 0x1e:
+ aFontList.AppendElement(kFontGeneva);
+ break;
+ case 0x20: // Symbol ranges
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ case 0x26:
+ case 0x27:
+ 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,
+ nsAString& aSystemFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ gfxMacPlatformFontList* pfl = gfxMacPlatformFontList::PlatformFontList();
+ return pfl->LookupSystemFont(aSystemFontID, aSystemFontName, aFontStyle,
+ aDevPixPerCSSPixel);
+}
+
+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;
+}
+
+// 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()
+ {
+ }
+
+ virtual Display& GetGlobalDisplay() override
+ {
+ return mGlobalDisplay;
+ }
+
+ class OSXDisplay final : public VsyncSource::Display
+ {
+ public:
+ OSXDisplay()
+ : mDisplayLink(nullptr)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+
+ ~OSXDisplay()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ static void RetryEnableVsync(nsITimer* aTimer, void* aOsxDisplay)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ OSXDisplay* osxDisplay = static_cast<OSXDisplay*>(aOsxDisplay);
+ MOZ_ASSERT(osxDisplay);
+ osxDisplay->EnableVsync();
+ }
+
+ virtual void EnableVsync() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsVsyncEnabled()) {
+ return;
+ }
+
+ // 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.
+ if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) {
+ NS_WARNING("Could not create a display link with all active displays. Retrying");
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = 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->InitWithFuncCallback(RetryEnableVsync, this, delay, nsITimer::TYPE_ONE_SHOT);
+ return;
+ }
+
+ if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) {
+ NS_WARNING("Could not set displaylink output callback");
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = nullptr;
+ return;
+ }
+
+ mPreviousTimestamp = TimeStamp::Now();
+ if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) {
+ NS_WARNING("Could not activate the display link");
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = nullptr;
+ }
+
+ CVTime vsyncRate = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(mDisplayLink);
+ 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);
+ }
+ }
+
+ virtual void DisableVsync() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!IsVsyncEnabled()) {
+ return;
+ }
+
+ // Release the display link
+ if (mDisplayLink) {
+ CVDisplayLinkRelease(mDisplayLink);
+ mDisplayLink = nullptr;
+ }
+ }
+
+ virtual bool IsVsyncEnabled() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDisplayLink != nullptr;
+ }
+
+ virtual TimeDuration GetVsyncRate() override
+ {
+ return mVsyncRate;
+ }
+
+ virtual 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:
+ // Manages the display link render thread
+ CVDisplayLinkRef mDisplayLink;
+ RefPtr<nsITimer> mTimer;
+ TimeDuration mVsyncRate;
+ }; // OSXDisplay
+
+private:
+ virtual ~OSXVsyncSource()
+ {
+ }
+
+ OSXDisplay 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;
+ int64_t nextVsyncTimestamp = aOutputTime->hostTime;
+
+ mozilla::TimeStamp nextVsync = mozilla::TimeStamp::FromSystemTime(nextVsyncTimestamp);
+ 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);
+ return kCVReturnSuccess;
+}
+
+already_AddRefed<mozilla::gfx::VsyncSource>
+gfxPlatformMac::CreateHardwareVsyncSource()
+{
+ RefPtr<VsyncSource> 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();
+}
+
+void
+gfxPlatformMac::GetPlatformCMSOutputProfile(void* &mem, size_t &size)
+{
+ mem = nullptr;
+ size = 0;
+
+ CGColorSpaceRef cspace = ::CGDisplayCopyColorSpace(::CGMainDisplayID());
+ if (!cspace) {
+ cspace = ::CGColorSpaceCreateDeviceRGB();
+ }
+ if (!cspace) {
+ return;
+ }
+
+ CFDataRef iccp = ::CGColorSpaceCopyICCProfile(cspace);
+
+ ::CFRelease(cspace);
+
+ if (!iccp) {
+ return;
+ }
+
+ // copy to external buffer
+ size = static_cast<size_t>(::CFDataGetLength(iccp));
+ if (size > 0) {
+ void *data = malloc(size);
+ if (data) {
+ memcpy(data, ::CFDataGetBytePtr(iccp), size);
+ mem = data;
+ } else {
+ size = 0;
+ }
+ }
+
+ ::CFRelease(iccp);
+}
diff --git a/gfx/thebes/gfxPlatformMac.h b/gfx/thebes/gfxPlatformMac.h
new file mode 100644
index 0000000000..ea4c1a1011
--- /dev/null
+++ b/gfx/thebes/gfxPlatformMac.h
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef GFX_PLATFORM_MAC_H
+#define GFX_PLATFORM_MAC_H
+
+#include "nsTArrayForwardDeclare.h"
+#include "gfxPlatform.h"
+#include "mozilla/LookAndFeel.h"
+
+namespace mozilla {
+namespace gfx {
+class DrawTarget;
+class VsyncSource;
+} // namespace gfx
+} // namespace mozilla
+
+class gfxPlatformMac : public gfxPlatform {
+public:
+ gfxPlatformMac();
+ virtual ~gfxPlatformMac();
+
+ static gfxPlatformMac *GetPlatform() {
+ return (gfxPlatformMac*) gfxPlatform::GetPlatform();
+ }
+
+ virtual already_AddRefed<gfxASurface>
+ CreateOffscreenSurface(const IntSize& aSize,
+ gfxImageFormat aFormat) override;
+
+ already_AddRefed<mozilla::gfx::ScaledFont>
+ GetScaledFontForFont(mozilla::gfx::DrawTarget* aTarget, gfxFont *aFont) override;
+
+ gfxFontGroup*
+ CreateFontGroup(const mozilla::FontFamilyList& aFontFamilyList,
+ const gfxFontStyle *aStyle,
+ gfxTextPerfMetrics* aTextPerf,
+ gfxUserFontSet *aUserFontSet,
+ gfxFloat aDevToCssSize) override;
+
+ virtual gfxPlatformFontList* CreatePlatformFontList() override;
+
+ bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) override;
+
+ virtual void GetCommonFallbackFonts(uint32_t aCh, uint32_t aNextCh,
+ Script aRunScript,
+ nsTArray<const char*>& aFontList) override;
+
+ // lookup the system font for a particular system font type and set
+ // the name and style characteristics
+ static void
+ LookupSystemFont(mozilla::LookAndFeel::FontID aSystemFontID,
+ nsAString& aSystemFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel);
+
+ virtual bool CanRenderContentToDataSurface() const override {
+ return true;
+ }
+
+ virtual bool SupportsApzWheelInput() const override {
+ return true;
+ }
+
+ bool RespectsFontStyleSmoothing() const override {
+ // gfxMacFont respects the font smoothing hint.
+ return true;
+ }
+
+ bool RequiresAcceleratedGLContextForCompositorOGL() const override {
+ // On OS X in a VM, unaccelerated CompositorOGL shows black flashes, so we
+ // require accelerated GL for CompositorOGL but allow unaccelerated GL for
+ // BasicCompositor.
+ return true;
+ }
+
+ virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
+
+ // lower threshold on font anti-aliasing
+ uint32_t GetAntiAliasingThreshold() { return mFontAntiAliasingThreshold; }
+
+private:
+ virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size) override;
+
+ // read in the pref value for the lower threshold on font anti-aliasing
+ static uint32_t ReadAntiAliasingThreshold();
+
+ uint32_t mFontAntiAliasingThreshold;
+};
+
+#endif /* GFX_PLATFORM_MAC_H */
diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h
index b02638e5a6..ac5bdd45a2 100644
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -424,6 +424,9 @@ private:
DECL_GFX_PREF(Live, "gl.ignore-dx-interop2-blacklist", IgnoreDXInterop2Blacklist, bool, false);
DECL_GFX_PREF(Live, "gl.msaa-level", MSAALevel, uint32_t, 2);
+#if defined(XP_MACOSX)
+ DECL_GFX_PREF(Live, "gl.multithreaded", GLMultithreaded, bool, false);
+#endif
DECL_GFX_PREF(Live, "gl.require-hardware", RequireHardwareGL, bool, false);
DECL_GFX_PREF(Once, "image.cache.size", ImageCacheSize, int32_t, 5*1024*1024);
diff --git a/gfx/thebes/gfxQuartzNativeDrawing.cpp b/gfx/thebes/gfxQuartzNativeDrawing.cpp
new file mode 100644
index 0000000000..f14b71d779
--- /dev/null
+++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxQuartzNativeDrawing.h"
+#include "gfxPlatform.h"
+#include "mozilla/gfx/Helpers.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla;
+
+gfxQuartzNativeDrawing::gfxQuartzNativeDrawing(DrawTarget& aDrawTarget,
+ const Rect& nativeRect)
+ : mDrawTarget(&aDrawTarget)
+ , mNativeRect(nativeRect)
+ , mCGContext(nullptr)
+{
+}
+
+CGContextRef
+gfxQuartzNativeDrawing::BeginNativeDrawing()
+{
+ NS_ASSERTION(!mCGContext, "BeginNativeDrawing called when drawing already in progress");
+
+ DrawTarget *dt = mDrawTarget;
+ if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget() ||
+ dt->GetBackendType() != BackendType::SKIA) {
+ // We need a DrawTarget that we can get a CGContextRef from:
+ Matrix transform = dt->GetTransform();
+
+ mNativeRect = transform.TransformBounds(mNativeRect);
+ mNativeRect.RoundOut();
+ // Quartz theme drawing often adjusts drawing rects, so make
+ // sure our surface is big enough for that.
+ mNativeRect.Inflate(5);
+ if (mNativeRect.IsEmpty()) {
+ return nullptr;
+ }
+
+ mTempDrawTarget =
+ Factory::CreateDrawTarget(BackendType::SKIA,
+ IntSize::Truncate(mNativeRect.width, mNativeRect.height),
+ SurfaceFormat::B8G8R8A8);
+
+ if (mTempDrawTarget) {
+ transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
+ mTempDrawTarget->SetTransform(transform);
+ }
+ dt = mTempDrawTarget;
+ }
+ if (dt) {
+ MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
+ mCGContext = mBorrowedContext.Init(dt);
+ MOZ_ASSERT(mCGContext);
+ }
+ return mCGContext;
+}
+
+void
+gfxQuartzNativeDrawing::EndNativeDrawing()
+{
+ NS_ASSERTION(mCGContext, "EndNativeDrawing called without BeginNativeDrawing");
+
+ mBorrowedContext.Finish();
+ if (mTempDrawTarget) {
+ RefPtr<SourceSurface> source = mTempDrawTarget->Snapshot();
+
+ AutoRestoreTransform autoRestore(mDrawTarget);
+ mDrawTarget->SetTransform(Matrix());
+ mDrawTarget->DrawSurface(source, mNativeRect,
+ Rect(0, 0, mNativeRect.width, mNativeRect.height));
+ }
+}
diff --git a/gfx/thebes/gfxQuartzNativeDrawing.h b/gfx/thebes/gfxQuartzNativeDrawing.h
new file mode 100644
index 0000000000..736f9ce836
--- /dev/null
+++ b/gfx/thebes/gfxQuartzNativeDrawing.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef _GFXQUARTZNATIVEDRAWING_H_
+#define _GFXQUARTZNATIVEDRAWING_H_
+
+#include "mozilla/Attributes.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/RefPtr.h"
+
+class gfxQuartzNativeDrawing {
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::Rect Rect;
+public:
+
+ /* Create native Quartz drawing for a rectangle bounded by
+ * nativeRect.
+ *
+ * Typical usage looks like:
+ *
+ * gfxQuartzNativeDrawing nativeDraw(ctx, nativeRect);
+ * CGContextRef cgContext = nativeDraw.BeginNativeDrawing();
+ * if (!cgContext)
+ * return NS_ERROR_FAILURE;
+ *
+ * ... call Quartz operations on CGContextRef to draw to nativeRect ...
+ *
+ * nativeDraw.EndNativeDrawing();
+ *
+ * aNativeRect is the size of the surface (in Quartz/Cocoa points) that
+ * will be created _if_ the gfxQuartzNativeDrawing decides to create a new
+ * surface and CGContext for its drawing operations, which it then
+ * composites into the target DrawTarget.
+ *
+ * (Note that aNativeRect will be ignored if the gfxQuartzNativeDrawing
+ * uses the target DrawTarget directly.)
+ *
+ * The optional aBackingScale parameter is a scaling factor that will be
+ * applied when creating and rendering into such a temporary surface.
+ */
+ gfxQuartzNativeDrawing(DrawTarget& aDrawTarget,
+ const Rect& aNativeRect);
+
+ /* Returns a CGContextRef which may be used for native drawing. This
+ * CGContextRef is valid until EndNativeDrawing is called; if it is used
+ * for drawing after that time, the result is undefined. */
+ CGContextRef BeginNativeDrawing();
+
+ /* Marks the end of native drawing */
+ void EndNativeDrawing();
+
+private:
+ // don't allow copying via construction or assignment
+ gfxQuartzNativeDrawing(const gfxQuartzNativeDrawing&) = delete;
+ const gfxQuartzNativeDrawing& operator=(const gfxQuartzNativeDrawing&) = delete;
+
+ // Final destination context
+ RefPtr<DrawTarget> mDrawTarget;
+ RefPtr<DrawTarget> mTempDrawTarget;
+ mozilla::gfx::BorrowedCGContext mBorrowedContext;
+ mozilla::gfx::Rect mNativeRect;
+
+ // saved state
+ CGContextRef mCGContext;
+};
+
+#endif
diff --git a/gfx/thebes/gfxQuartzSurface.cpp b/gfx/thebes/gfxQuartzSurface.cpp
new file mode 100644
index 0000000000..99553e0c07
--- /dev/null
+++ b/gfx/thebes/gfxQuartzSurface.cpp
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 "gfxQuartzSurface.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+
+#include "cairo-quartz.h"
+
+void
+gfxQuartzSurface::MakeInvalid()
+{
+ mSize = mozilla::gfx::IntSize(-1, -1);
+}
+
+gfxQuartzSurface::gfxQuartzSurface(const mozilla::gfx::IntSize& desiredSize,
+ gfxImageFormat format)
+ : mCGContext(nullptr), mSize(desiredSize)
+{
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(desiredSize))
+ MakeInvalid();
+
+ unsigned int width = static_cast<unsigned int>(mSize.width);
+ unsigned int height = static_cast<unsigned int>(mSize.height);
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(format);
+ cairo_surface_t *surf = cairo_quartz_surface_create(cformat, width, height);
+
+ mCGContext = cairo_quartz_surface_get_cg_context (surf);
+
+ CGContextRetain(mCGContext);
+
+ Init(surf);
+ if (mSurfaceValid) {
+ RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
+ }
+}
+
+gfxQuartzSurface::gfxQuartzSurface(CGContextRef context,
+ const mozilla::gfx::IntSize& size)
+ : mCGContext(context), mSize(size)
+{
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(size))
+ MakeInvalid();
+
+ unsigned int width = static_cast<unsigned int>(mSize.width);
+ unsigned int height = static_cast<unsigned int>(mSize.height);
+
+ cairo_surface_t *surf =
+ cairo_quartz_surface_create_for_cg_context(context,
+ width, height);
+
+ CGContextRetain(mCGContext);
+
+ Init(surf);
+ if (mSurfaceValid) {
+ RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
+ }
+}
+
+gfxQuartzSurface::gfxQuartzSurface(cairo_surface_t *csurf,
+ const mozilla::gfx::IntSize& aSize) :
+ mSize(aSize)
+{
+ mCGContext = cairo_quartz_surface_get_cg_context (csurf);
+ CGContextRetain (mCGContext);
+
+ Init(csurf, true);
+}
+
+gfxQuartzSurface::gfxQuartzSurface(unsigned char *data,
+ const mozilla::gfx::IntSize& aSize,
+ long stride,
+ gfxImageFormat format)
+ : mCGContext(nullptr), mSize(aSize.width, aSize.height)
+{
+ if (!mozilla::gfx::Factory::CheckSurfaceSize(aSize))
+ MakeInvalid();
+
+ cairo_format_t cformat = GfxFormatToCairoFormat(format);
+ cairo_surface_t *surf = cairo_quartz_surface_create_for_data
+ (data, cformat, aSize.width, aSize.height, stride);
+
+ mCGContext = cairo_quartz_surface_get_cg_context (surf);
+
+ CGContextRetain(mCGContext);
+
+ Init(surf);
+ if (mSurfaceValid) {
+ RecordMemoryUsed(mSize.height * stride + sizeof(gfxQuartzSurface));
+ }
+}
+
+already_AddRefed<gfxASurface>
+gfxQuartzSurface::CreateSimilarSurface(gfxContentType aType,
+ const mozilla::gfx::IntSize& aSize)
+{
+ cairo_surface_t *surface =
+ cairo_quartz_surface_create_cg_layer(mSurface, (cairo_content_t)aType,
+ aSize.width, aSize.height);
+ if (cairo_surface_status(surface)) {
+ cairo_surface_destroy(surface);
+ return nullptr;
+ }
+
+ RefPtr<gfxASurface> result = Wrap(surface, aSize);
+ cairo_surface_destroy(surface);
+ return result.forget();
+}
+
+already_AddRefed<gfxImageSurface> gfxQuartzSurface::GetAsImageSurface()
+{
+ cairo_surface_t *surface = cairo_quartz_surface_get_image(mSurface);
+ if (!surface || cairo_surface_status(surface))
+ return nullptr;
+
+ RefPtr<gfxASurface> img = Wrap(surface);
+
+ // cairo_quartz_surface_get_image returns a referenced image, and thebes
+ // shares the refcounts of Cairo surfaces. However, Wrap also adds a
+ // reference to the image. We need to remove one of these references
+ // explicitly so we don't leak.
+ img.get()->Release();
+
+ img->SetOpaqueRect(GetOpaqueRect());
+
+ return img.forget().downcast<gfxImageSurface>();
+}
+
+gfxQuartzSurface::~gfxQuartzSurface()
+{
+ CGContextRelease(mCGContext);
+}
diff --git a/gfx/thebes/gfxQuartzSurface.h b/gfx/thebes/gfxQuartzSurface.h
new file mode 100644
index 0000000000..50e2bfb2c3
--- /dev/null
+++ b/gfx/thebes/gfxQuartzSurface.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef GFX_QUARTZSURFACE_H
+#define GFX_QUARTZSURFACE_H
+
+#include "gfxASurface.h"
+#include "nsSize.h"
+#include "gfxPoint.h"
+
+#include <Carbon/Carbon.h>
+
+class gfxContext;
+class gfxImageSurface;
+
+class gfxQuartzSurface : public gfxASurface {
+public:
+ gfxQuartzSurface(const mozilla::gfx::IntSize&, gfxImageFormat format);
+ gfxQuartzSurface(CGContextRef context, const mozilla::gfx::IntSize& size);
+ gfxQuartzSurface(cairo_surface_t *csurf, const mozilla::gfx::IntSize& aSize);
+ gfxQuartzSurface(unsigned char *data, const mozilla::gfx::IntSize& size, long stride, gfxImageFormat format);
+
+ virtual ~gfxQuartzSurface();
+
+ virtual already_AddRefed<gfxASurface> CreateSimilarSurface(gfxContentType aType,
+ const mozilla::gfx::IntSize& aSize);
+
+ virtual const mozilla::gfx::IntSize GetSize() const { return mozilla::gfx::IntSize(mSize.width, mSize.height); }
+
+ CGContextRef GetCGContext() { return mCGContext; }
+
+ already_AddRefed<gfxImageSurface> GetAsImageSurface();
+
+protected:
+ void MakeInvalid();
+
+ CGContextRef mCGContext;
+ mozilla::gfx::IntSize mSize;
+};
+
+#endif /* GFX_QUARTZSURFACE_H */
diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp
index ae746b8c5b..db9fc346b4 100644
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -518,6 +518,11 @@ HasSyntheticBoldOrColor(const gfxTextRun *aRun, gfxTextRun::Range aRange)
if (fe->TryGetSVGData(font) || fe->TryGetColorGlyphs()) {
return true;
}
+#if defined(XP_MACOSX) // sbix fonts only supported via Core Text
+ if (fe->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
+ return true;
+ }
+#endif
}
}
return false;
diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build
index be28f4e51c..1d3cf3bc93 100644
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -57,7 +57,24 @@ EXPORTS.mozilla.gfx += [
'PrintTargetThebes.h',
]
-if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ EXPORTS += [
+ 'gfxPlatformMac.h',
+ 'gfxQuartzNativeDrawing.h',
+ 'gfxQuartzSurface.h',
+ ]
+ EXPORTS.mozilla.gfx += [
+ 'PrintTargetCG.h',
+ ]
+ SOURCES += [
+ 'gfxCoreTextShaper.cpp',
+ 'gfxMacFont.cpp',
+ 'gfxPlatformMac.cpp',
+ 'gfxQuartzNativeDrawing.cpp',
+ 'gfxQuartzSurface.cpp',
+ 'PrintTargetCG.cpp',
+ ]
+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
EXPORTS += [
'gfxFontconfigFonts.h',
'gfxFT2FontBase.h',
@@ -165,7 +182,11 @@ SOURCES += [
'VsyncSource.cpp',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'gfxMacPlatformFontList.mm',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
SOURCES += [
'D3D11Checks.cpp',
'DeviceManagerDx.cpp',
diff --git a/gfx/ycbcr/convert.patch.outdated b/gfx/ycbcr/convert.patch
index e39f923b3c..e39f923b3c 100644
--- a/gfx/ycbcr/convert.patch.outdated
+++ b/gfx/ycbcr/convert.patch
diff --git a/gfx/ycbcr/yuv_row_posix.cpp b/gfx/ycbcr/yuv_row_posix.cpp
index 152bfc7788..a84792d966 100644
--- a/gfx/ycbcr/yuv_row_posix.cpp
+++ b/gfx/ycbcr/yuv_row_posix.cpp
@@ -1,5 +1,4 @@
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
-// Copyright (c) 2021 Moonchild Productions.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -320,7 +319,9 @@ void FastConvertYUVToRGB32Row_SSE(const uint8* y_buf,
"2:"
"popa\n"
"ret\n"
+#if !defined(XP_MACOSX)
".previous\n"
+#endif
);
void FastConvertYUVToRGB32Row(const uint8* y_buf,
@@ -411,7 +412,9 @@ void ScaleYUVToRGB32Row_SSE(const uint8* y_buf,
"2:"
"popa\n"
"ret\n"
+#if !defined(XP_MACOSX)
".previous\n"
+#endif
);
void ScaleYUVToRGB32Row(const uint8* y_buf,
@@ -541,7 +544,9 @@ void LinearScaleYUVToRGB32Row_SSE(const uint8* y_buf,
"movd %mm1, (%ebp)\n"
"popa\n"
"ret\n"
+#if !defined(XP_MACOSX)
".previous\n"
+#endif
);
void LinearScaleYUVToRGB32Row(const uint8* y_buf,
@@ -572,7 +577,11 @@ void PICConvertYUVToRGB32Row_SSE(const uint8* y_buf,
asm(
".text\n"
- "PICConvertYUVToRGB32Row_SSE:\n"
+#if defined(XP_MACOSX)
+"_PICConvertYUVToRGB32Row_SSE:\n"
+#else
+"PICConvertYUVToRGB32Row_SSE:\n"
+#endif
"pusha\n"
"mov 0x24(%esp),%edx\n"
"mov 0x28(%esp),%edi\n"
@@ -621,7 +630,9 @@ void PICConvertYUVToRGB32Row_SSE(const uint8* y_buf,
"2:"
"popa\n"
"ret\n"
+#if !defined(XP_MACOSX)
".previous\n"
+#endif
);
void FastConvertYUVToRGB32Row(const uint8* y_buf,
@@ -649,7 +660,11 @@ void PICScaleYUVToRGB32Row_SSE(const uint8* y_buf,
asm(
".text\n"
- "PICScaleYUVToRGB32Row_SSE:\n"
+#if defined(XP_MACOSX)
+"_PICScaleYUVToRGB32Row_SSE:\n"
+#else
+"PICScaleYUVToRGB32Row_SSE:\n"
+#endif
"pusha\n"
"mov 0x24(%esp),%edx\n"
"mov 0x28(%esp),%edi\n"
@@ -712,7 +727,9 @@ void PICScaleYUVToRGB32Row_SSE(const uint8* y_buf,
"2:"
"popa\n"
"ret\n"
+#if !defined(XP_MACOSX)
".previous\n"
+#endif
);
void ScaleYUVToRGB32Row(const uint8* y_buf,
@@ -741,7 +758,11 @@ void PICLinearScaleYUVToRGB32Row_SSE(const uint8* y_buf,
asm(
".text\n"
- "PICLinearScaleYUVToRGB32Row_SSE:\n"
+#if defined(XP_MACOSX)
+"_PICLinearScaleYUVToRGB32Row_SSE:\n"
+#else
+"PICLinearScaleYUVToRGB32Row_SSE:\n"
+#endif
"pusha\n"
"mov 0x24(%esp),%edx\n"
"mov 0x30(%esp),%ebp\n"
@@ -844,7 +865,9 @@ void PICLinearScaleYUVToRGB32Row_SSE(const uint8* y_buf,
"movd %mm1, (%ebp)\n"
"popa\n"
"ret\n"
+#if !defined(XP_MACOSX)
".previous\n"
+#endif
);
diff --git a/hal/cocoa/CocoaSensor.mm b/hal/cocoa/CocoaSensor.mm
new file mode 100644
index 0000000000..ad203073c3
--- /dev/null
+++ b/hal/cocoa/CocoaSensor.mm
@@ -0,0 +1,149 @@
+/* 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 "Hal.h"
+#include "nsITimer.h"
+#include "smslib.h"
+#include "nsComponentManagerUtils.h"
+
+#include <mach/mach.h>
+#include <cmath>
+#import <IOKit/IOKitLib.h>
+
+#define MEAN_GRAVITY 9.80665
+#define DEFAULT_SENSOR_POLL 100
+using namespace mozilla::hal;
+namespace mozilla {
+namespace hal_impl {
+static nsITimer* sUpdateTimer = nullptr;
+static bool sActiveSensors[NUM_SENSOR_TYPE];
+static io_connect_t sDataPort = IO_OBJECT_NULL;
+static uint64_t sLastMean = -1;
+static float
+LMUvalueToLux(uint64_t aValue)
+{
+ //Conversion formula from regression. See Bug 793728.
+ // -3*(10^-27)*x^4 + 2.6*(10^-19)*x^3 + -3.4*(10^-12)*x^2 + 3.9*(10^-5)*x - 0.19
+ long double powerC4 = 1/pow((long double)10,27);
+ long double powerC3 = 1/pow((long double)10,19);
+ long double powerC2 = 1/pow((long double)10,12);
+ long double powerC1 = 1/pow((long double)10,5);
+
+ long double term4 = -3.0 * powerC4 * pow(aValue,4);
+ long double term3 = 2.6 * powerC3 * pow(aValue,3);
+ long double term2 = -3.4 * powerC2 * pow(aValue,2);
+ long double term1 = 3.9 * powerC1 * aValue;
+
+ float lux = ceil(static_cast<float>(term4 + term3 + term2 + term1 - 0.19));
+ return lux > 0 ? lux : 0;
+}
+void
+UpdateHandler(nsITimer *aTimer, void *aClosure)
+{
+ for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
+ if (!sActiveSensors[i]) {
+ continue;
+ }
+ SensorType sensor = static_cast<SensorType>(i);
+ InfallibleTArray<float> values;
+ if (sensor == SENSOR_ACCELERATION) {
+ sms_acceleration accel;
+ smsGetData(&accel);
+
+ values.AppendElement(accel.x * MEAN_GRAVITY);
+ values.AppendElement(accel.y * MEAN_GRAVITY);
+ values.AppendElement(accel.z * MEAN_GRAVITY);
+ } else if (sensor == SENSOR_LIGHT && sDataPort != IO_OBJECT_NULL) {
+ kern_return_t kr;
+ uint32_t outputs = 2;
+ uint64_t lightLMU[outputs];
+
+ kr = IOConnectCallMethod(sDataPort, 0, nil, 0, nil, 0, lightLMU, &outputs, nil, 0);
+ if (kr == KERN_SUCCESS) {
+ uint64_t mean = (lightLMU[0] + lightLMU[1]) / 2;
+ if (mean == sLastMean) {
+ continue;
+ }
+ sLastMean = mean;
+ values.AppendElement(LMUvalueToLux(mean));
+ } else if (kr == kIOReturnBusy) {
+ continue;
+ }
+ }
+
+ hal::SensorData sdata(sensor,
+ PR_Now(),
+ values,
+ hal::SENSOR_ACCURACY_UNKNOWN);
+ hal::NotifySensorChange(sdata);
+ }
+}
+void
+EnableSensorNotifications(SensorType aSensor)
+{
+ if (aSensor == SENSOR_ACCELERATION) {
+ int result = smsStartup(nil, nil);
+
+ if (result != SMS_SUCCESS) {
+ return;
+ }
+
+ if (!smsLoadCalibration()) {
+ return;
+ }
+ } else if (aSensor == SENSOR_LIGHT) {
+ io_service_t serviceObject;
+ serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault,
+ IOServiceMatching("AppleLMUController"));
+ if (!serviceObject) {
+ return;
+ }
+ kern_return_t kr;
+ kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &sDataPort);
+ IOObjectRelease(serviceObject);
+ if (kr != KERN_SUCCESS) {
+ return;
+ }
+ } else {
+ NS_WARNING("EnableSensorNotifications called on an unknown sensor type");
+ return;
+ }
+ sActiveSensors[aSensor] = true;
+
+ if (!sUpdateTimer) {
+ CallCreateInstance("@mozilla.org/timer;1", &sUpdateTimer);
+ if (sUpdateTimer) {
+ sUpdateTimer->InitWithFuncCallback(UpdateHandler,
+ nullptr,
+ DEFAULT_SENSOR_POLL,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+ }
+}
+void
+DisableSensorNotifications(SensorType aSensor)
+{
+ if (!sActiveSensors[aSensor] || (aSensor != SENSOR_ACCELERATION && aSensor != SENSOR_LIGHT)) {
+ return;
+ }
+
+ sActiveSensors[aSensor] = false;
+
+ if (aSensor == SENSOR_ACCELERATION) {
+ smsShutdown();
+ } else if (aSensor == SENSOR_LIGHT) {
+ IOServiceClose(sDataPort);
+ }
+ // If all sensors are disabled, cancel the update timer.
+ if (sUpdateTimer) {
+ for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
+ if (sActiveSensors[i]) {
+ return;
+ }
+ }
+ sUpdateTimer->Cancel();
+ NS_RELEASE(sUpdateTimer);
+ }
+}
+} // namespace hal_impl
+} // namespace mozilla
diff --git a/hal/cocoa/smslib.h b/hal/cocoa/smslib.h
new file mode 100644
index 0000000000..2f0b2664e4
--- /dev/null
+++ b/hal/cocoa/smslib.h
@@ -0,0 +1,159 @@
+/*
+ * smslib.h
+ *
+ * SMSLib Sudden Motion Sensor Access Library
+ * Copyright (c) 2010 Suitable Systems
+ * All rights reserved.
+ *
+ * Developed by: Daniel Griscom
+ * Suitable Systems
+ * http://www.suitable.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal with the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimers.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimers in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the names of Suitable Systems nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this Software without specific prior written permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
+ *
+ * For more information about SMSLib, see
+ * <http://www.suitable.com/tools/smslib.html>
+ * or contact
+ * Daniel Griscom
+ * Suitable Systems
+ * 1 Centre Street, Suite 204
+ * Wakefield, MA 01880
+ * (781) 665-0053
+ *
+ */
+
+#import <Foundation/Foundation.h>
+
+#define SMSLIB_VERSION "1.8"
+
+#pragma mark Structure definitions
+
+// Structure for specifying a 3-axis acceleration. 0.0 means "zero gravities",
+// 1.0 means "one gravity".
+typedef struct sms_acceleration {
+ float x; // Right-left acceleration (positive is rightwards)
+ float y; // Front-rear acceleration (positive is rearwards)
+ float z; // Up-down acceleration (positive is upwards)
+} sms_acceleration;
+
+// Structure for specifying a calibration.
+typedef struct sms_calibration {
+ float zeros[3]; // Zero points for three axes (X, Y, Z)
+ float onegs[3]; // One gravity values for three axes
+} sms_calibration;
+
+#pragma mark Return value definitions
+
+// These are the return values for accelStartup(), giving the
+// various stages where the most successful attempt at accessing
+// the accelerometer failed. The higher the value, the further along the
+// software progressed before failing. The options are:
+// - Didn't match model name
+#define SMS_FAIL_MODEL (-7)
+// - Failure getting dictionary matching desired services
+#define SMS_FAIL_DICTIONARY (-6)
+// - Failure getting list of services
+#define SMS_FAIL_LIST_SERVICES (-5)
+// - Failure if list of services is empty. The process generally fails
+// here if run on a machine without a Sudden Motion Sensor.
+#define SMS_FAIL_NO_SERVICES (-4)
+// - Failure if error opening device.
+#define SMS_FAIL_OPENING (-3)
+// - Failure if opened, but didn't get a connection
+#define SMS_FAIL_CONNECTION (-2)
+// - Failure if couldn't access connction using given function and size. This
+// is where the process would probably fail with a change in Apple's API.
+// Driver problems often also cause failures here.
+#define SMS_FAIL_ACCESS (-1)
+// - Success!
+#define SMS_SUCCESS (0)
+
+#pragma mark Function declarations
+
+// This starts up the accelerometer code, trying each possible sensor
+// specification. Note that for logging purposes it
+// takes an object and a selector; the object's selector is then invoked
+// with a single NSString as argument giving progress messages. Example
+// logging method:
+// - (void)logMessage: (NSString *)theString
+// which would be used in accelStartup's invocation thusly:
+// result = accelStartup(self, @selector(logMessage:));
+// If the object is nil, then no logging is done. Sets calibation from built-in
+// value table. Returns ACCEL_SUCCESS for success, and other (negative)
+// values for various failures (returns value indicating result of
+// most successful trial).
+int smsStartup(id logObject, SEL logSelector);
+
+// This starts up the library in debug mode, ignoring the actual hardware.
+// Returned data is in the form of 1Hz sine waves, with the X, Y and Z
+// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
+// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
+// Z axes centered on 1 (calibrated) or 256 (uncalibrated).
+// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
+int smsDebugStartup(id logObject, SEL logSelector);
+
+// Returns the current calibration values.
+void smsGetCalibration(sms_calibration *calibrationRecord);
+
+// Sets the calibration, but does NOT store it as a preference. If the argument
+// is nil then the current calibration is set from the built-in value table.
+void smsSetCalibration(sms_calibration *calibrationRecord);
+
+// Stores the current calibration values as a stored preference.
+void smsStoreCalibration(void);
+
+// Loads the stored preference values into the current calibration.
+// Returns YES if successful.
+BOOL smsLoadCalibration(void);
+
+// Deletes any stored calibration, and then takes the current calibration values
+// from the built-in value table.
+void smsDeleteCalibration(void);
+
+// Fills in the accel record with calibrated acceleration data. Takes
+// 1-2ms to return a value. Returns 0 if success, error number if failure.
+int smsGetData(sms_acceleration *accel);
+
+// Fills in the accel record with uncalibrated acceleration data.
+// Returns 0 if success, error number if failure.
+int smsGetUncalibratedData(sms_acceleration *accel);
+
+// Returns the length of a raw block of data for the current type of sensor.
+int smsGetBufferLength(void);
+
+// Takes a pointer to accelGetRawLength() bytes; sets those bytes
+// to return value from sensor. Make darn sure the buffer length is right!
+void smsGetBufferData(char *buffer);
+
+// This returns an NSString describing the current calibration in
+// human-readable form. Also include a description of the machine.
+NSString *smsGetCalibrationDescription(void);
+
+// Shuts down the accelerometer.
+void smsShutdown(void);
+
diff --git a/hal/cocoa/smslib.mm b/hal/cocoa/smslib.mm
new file mode 100644
index 0000000000..c11c1e4d60
--- /dev/null
+++ b/hal/cocoa/smslib.mm
@@ -0,0 +1,938 @@
+/*
+ * smslib.m
+ *
+ * SMSLib Sudden Motion Sensor Access Library
+ * Copyright (c) 2010 Suitable Systems
+ * All rights reserved.
+ *
+ * Developed by: Daniel Griscom
+ * Suitable Systems
+ * http://www.suitable.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal with the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimers.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimers in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the names of Suitable Systems nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this Software without specific prior written permission.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
+ *
+ * For more information about SMSLib, see
+ * <http://www.suitable.com/tools/smslib.html>
+ * or contact
+ * Daniel Griscom
+ * Suitable Systems
+ * 1 Centre Street, Suite 204
+ * Wakefield, MA 01880
+ * (781) 665-0053
+ *
+ */
+
+#import <IOKit/IOKitLib.h>
+#import <sys/sysctl.h>
+#import <math.h>
+#import "smslib.h"
+
+#pragma mark Internal structures
+
+// Represents a single axis of a type of sensor.
+typedef struct axisStruct {
+ int enabled; // Non-zero if axis is valid in this sensor
+ int index; // Location in struct of first byte
+ int size; // Number of bytes
+ float zerog; // Value meaning "zero g"
+ float oneg; // Change in value meaning "increase of one g"
+ // (can be negative if axis sensor reversed)
+} axisStruct;
+
+// Represents the configuration of a type of sensor.
+typedef struct sensorSpec {
+ const char *model; // Prefix of model to be tested
+ const char *name; // Name of device to be read
+ unsigned int function; // Kernel function index
+ int recordSize; // Size of record to be sent/received
+ axisStruct axes[3]; // Description of three axes (X, Y, Z)
+} sensorSpec;
+
+// Configuration of all known types of sensors. The configurations are
+// tried in order until one succeeds in returning data.
+// All default values are set here, but each axis' zerog and oneg values
+// may be changed to saved (calibrated) values.
+//
+// These values came from SeisMaCalibrate calibration reports. In general I've
+// found the following:
+// - All Intel-based SMSs have 250 counts per g, centered on 0, but the signs
+// are different (and in one case two axes are swapped)
+// - PowerBooks and iBooks all have sensors centered on 0, and reading
+// 50-53 steps per gravity (but with differing polarities!)
+// - PowerBooks and iBooks of the same model all have the same axis polarities
+// - PowerBook and iBook access methods are model- and OS version-specific
+//
+// So, the sequence of tests is:
+// - Try model-specific access methods. Note that the test is for a match to the
+// beginning of the model name, e.g. the record with model name "MacBook"
+// matches computer models "MacBookPro1,2" and "MacBook1,1" (and ""
+// matches any model).
+// - If no model-specific record's access fails, then try each model-independent
+// access method in order, stopping when one works.
+static const sensorSpec sensors[] = {
+ // ****** Model-dependent methods ******
+ // The PowerBook5,6 is one of the G4 models that seems to lose
+ // SMS access until the next reboot.
+ {"PowerBook5,6", "IOI2CMotionSensor", 21, 60, {
+ {1, 0, 1, 0, 51.5},
+ {1, 1, 1, 0, -51.5},
+ {1, 2, 1, 0, -51.5}
+ }
+ },
+ // The PowerBook5,7 is one of the G4 models that seems to lose
+ // SMS access until the next reboot.
+ {"PowerBook5,7", "IOI2CMotionSensor", 21, 60, {
+ {1, 0, 1, 0, 51.5},
+ {1, 1, 1, 0, 51.5},
+ {1, 2, 1, 0, 51.5}
+ }
+ },
+ // Access seems to be reliable on the PowerBook5,8
+ {"PowerBook5,8", "PMUMotionSensor", 21, 60, {
+ {1, 0, 1, 0, -51.5},
+ {1, 1, 1, 0, 51.5},
+ {1, 2, 1, 0, -51.5}
+ }
+ },
+ // Access seems to be reliable on the PowerBook5,9
+ {"PowerBook5,9", "PMUMotionSensor", 21, 60, {
+ {1, 0, 1, 0, 51.5},
+ {1, 1, 1, 0, -51.5},
+ {1, 2, 1, 0, -51.5}
+ }
+ },
+ // The PowerBook6,7 is one of the G4 models that seems to lose
+ // SMS access until the next reboot.
+ {"PowerBook6,7", "IOI2CMotionSensor", 21, 60, {
+ {1, 0, 1, 0, 51.5},
+ {1, 1, 1, 0, 51.5},
+ {1, 2, 1, 0, 51.5}
+ }
+ },
+ // The PowerBook6,8 is one of the G4 models that seems to lose
+ // SMS access until the next reboot.
+ {"PowerBook6,8", "IOI2CMotionSensor", 21, 60, {
+ {1, 0, 1, 0, 51.5},
+ {1, 1, 1, 0, 51.5},
+ {1, 2, 1, 0, 51.5}
+ }
+ },
+ // MacBook Pro Core 2 Duo 17". Note the reversed Y and Z axes.
+ {"MacBookPro2,1", "SMCMotionSensor", 5, 40, {
+ {1, 0, 2, 0, 251},
+ {1, 2, 2, 0, -251},
+ {1, 4, 2, 0, -251}
+ }
+ },
+ // MacBook Pro Core 2 Duo 15" AND 17" with LED backlight, introduced June '07.
+ // NOTE! The 17" machines have the signs of their X and Y axes reversed
+ // from this calibration, but there's no clear way to discriminate between
+ // the two machines.
+ {"MacBookPro3,1", "SMCMotionSensor", 5, 40, {
+ {1, 0, 2, 0, -251},
+ {1, 2, 2, 0, 251},
+ {1, 4, 2, 0, -251}
+ }
+ },
+ // ... specs?
+ {"MacBook5,2", "SMCMotionSensor", 5, 40, {
+ {1, 0, 2, 0, -251},
+ {1, 2, 2, 0, 251},
+ {1, 4, 2, 0, -251}
+ }
+ },
+ // ... specs?
+ {"MacBookPro5,1", "SMCMotionSensor", 5, 40, {
+ {1, 0, 2, 0, -251},
+ {1, 2, 2, 0, -251},
+ {1, 4, 2, 0, 251}
+ }
+ },
+ // ... specs?
+ {"MacBookPro5,2", "SMCMotionSensor", 5, 40, {
+ {1, 0, 2, 0, -251},
+ {1, 2, 2, 0, -251},
+ {1, 4, 2, 0, 251}
+ }
+ },
+ // This is speculative, based on a single user's report. Looks like the X and Y axes
+ // are swapped. This is true for no other known Appple laptop.
+ {"MacBookPro5,3", "SMCMotionSensor", 5, 40, {
+ {1, 2, 2, 0, -251},
+ {1, 0, 2, 0, -251},
+ {1, 4, 2, 0, -251}
+ }
+ },
+ // ... specs?
+ {"MacBookPro5,4", "SMCMotionSensor", 5, 40, {
+ {1, 0, 2, 0, -251},
+ {1, 2, 2, 0, -251},
+ {1, 4, 2, 0, 251}
+ }
+ },
+ // ****** Model-independent methods ******
+ // Seen once with PowerBook6,8 under system 10.3.9; I suspect
+ // other G4-based 10.3.* systems might use this
+ {"", "IOI2CMotionSensor", 24, 60, {
+ {1, 0, 1, 0, 51.5},
+ {1, 1, 1, 0, 51.5},
+ {1, 2, 1, 0, 51.5}
+ }
+ },
+ // PowerBook5,6 , PowerBook5,7 , PowerBook6,7 , PowerBook6,8
+ // under OS X 10.4.*
+ {"", "IOI2CMotionSensor", 21, 60, {
+ {1, 0, 1, 0, 51.5},
+ {1, 1, 1, 0, 51.5},
+ {1, 2, 1, 0, 51.5}
+ }
+ },
+ // PowerBook5,8 , PowerBook5,9 under OS X 10.4.*
+ {"", "PMUMotionSensor", 21, 60, {
+ // Each has two out of three gains negative, but it's different
+ // for the different models. So, this will be right in two out
+ // of three axis for either model.
+ {1, 0, 1, 0, -51.5},
+ {1, 1, 1, -6, -51.5},
+ {1, 2, 1, 0, -51.5}
+ }
+ },
+ // All MacBook, MacBookPro models. Hardware (at least on early MacBookPro 15")
+ // is Kionix KXM52-1050 three-axis accelerometer chip. Data is at
+ // http://kionix.com/Product-Index/product-index.htm. Specific MB and MBP models
+ // that use this are:
+ // MacBook1,1
+ // MacBook2,1
+ // MacBook3,1
+ // MacBook4,1
+ // MacBook5,1
+ // MacBook6,1
+ // MacBookAir1,1
+ // MacBookPro1,1
+ // MacBookPro1,2
+ // MacBookPro4,1
+ // MacBookPro5,5
+ {"", "SMCMotionSensor", 5, 40, {
+ {1, 0, 2, 0, 251},
+ {1, 2, 2, 0, 251},
+ {1, 4, 2, 0, 251}
+ }
+ }
+};
+
+#define SENSOR_COUNT (sizeof(sensors)/sizeof(sensorSpec))
+
+#pragma mark Internal prototypes
+
+static int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector);
+static float getAxis(int which, int calibrated);
+static int signExtend(int value, int size);
+static NSString *getModelName(void);
+static NSString *getOSVersion(void);
+static BOOL loadCalibration(void);
+static void storeCalibration(void);
+static void defaultCalibration(void);
+static void deleteCalibration(void);
+static int prefIntRead(NSString *prefName, BOOL *success);
+static void prefIntWrite(NSString *prefName, int prefValue);
+static float prefFloatRead(NSString *prefName, BOOL *success);
+static void prefFloatWrite(NSString *prefName, float prefValue);
+static void prefDelete(NSString *prefName);
+static void prefSynchronize(void);
+// static long getMicroseconds(void);
+float fakeData(NSTimeInterval time);
+
+#pragma mark Static variables
+
+static int debugging = NO; // True if debugging (synthetic data)
+static io_connect_t connection; // Connection for reading accel values
+static int running = NO; // True if we successfully started
+static unsigned int sensorNum = 0; // The current index into sensors[]
+static const char *serviceName; // The name of the current service
+static char *iRecord, *oRecord; // Pointers to read/write records for sensor
+static int recordSize; // Size of read/write records
+static unsigned int function; // Which kernel function should be used
+static float zeros[3]; // X, Y and Z zero calibration values
+static float onegs[3]; // X, Y and Z one-g calibration values
+
+#pragma mark Defines
+
+// Pattern for building axis letter from axis number
+#define INT_TO_AXIS(a) (a == 0 ? @"X" : a == 1 ? @"Y" : @"Z")
+// Name of configuration for given axis' zero (axis specified by integer)
+#define ZERO_NAME(a) [NSString stringWithFormat:@"%@-Axis-Zero", INT_TO_AXIS(a)]
+// Name of configuration for given axis' oneg (axis specified by integer)
+#define ONEG_NAME(a) [NSString stringWithFormat:@"%@-Axis-One-g", INT_TO_AXIS(a)]
+// Name of "Is calibrated" preference
+#define CALIBRATED_NAME (@"Calibrated")
+// Application domain for SeisMac library
+#define APP_ID ((CFStringRef)@"com.suitable.SeisMacLib")
+
+// These #defines make the accelStartup code a LOT easier to read.
+#undef LOG
+#define LOG(message) \
+ if (logObject) { \
+ [logObject performSelector:logSelector withObject:message]; \
+ }
+#define LOG_ARG(format, var1) \
+ if (logObject) { \
+ [logObject performSelector:logSelector \
+ withObject:[NSString stringWithFormat:format, var1]]; \
+ }
+#define LOG_2ARG(format, var1, var2) \
+ if (logObject) { \
+ [logObject performSelector:logSelector \
+ withObject:[NSString stringWithFormat:format, var1, var2]]; \
+ }
+#define LOG_3ARG(format, var1, var2, var3) \
+ if (logObject) { \
+ [logObject performSelector:logSelector \
+ withObject:[NSString stringWithFormat:format, var1, var2, var3]]; \
+ }
+
+#pragma mark Function definitions
+
+// This starts up the accelerometer code, trying each possible sensor
+// specification. Note that for logging purposes it
+// takes an object and a selector; the object's selector is then invoked
+// with a single NSString as argument giving progress messages. Example
+// logging method:
+// - (void)logMessage: (NSString *)theString
+// which would be used in accelStartup's invocation thusly:
+// result = accelStartup(self, @selector(logMessage:));
+// If the object is nil, then no logging is done. Sets calibation from built-in
+// value table. Returns ACCEL_SUCCESS for success, and other (negative)
+// values for various failures (returns value indicating result of
+// most successful trial).
+int smsStartup(id logObject, SEL logSelector) {
+ io_iterator_t iterator;
+ io_object_t device;
+ kern_return_t result;
+ sms_acceleration accel;
+ int failure_result = SMS_FAIL_MODEL;
+
+ running = NO;
+ debugging = NO;
+
+ NSString *modelName = getModelName();
+
+ LOG_ARG(@"Machine model: %@\n", modelName);
+ LOG_ARG(@"OS X version: %@\n", getOSVersion());
+ LOG_ARG(@"Accelerometer library version: %s\n", SMSLIB_VERSION);
+
+ for (sensorNum = 0; sensorNum < SENSOR_COUNT; sensorNum++) {
+
+ // Set up all specs for this type of sensor
+ serviceName = sensors[sensorNum].name;
+ recordSize = sensors[sensorNum].recordSize;
+ function = sensors[sensorNum].function;
+
+ LOG_3ARG(@"Trying service \"%s\" with selector %d and %d byte record:\n",
+ serviceName, function, recordSize);
+
+ NSString *targetName = [NSString stringWithCString:sensors[sensorNum].model
+ encoding:NSMacOSRomanStringEncoding];
+ LOG_ARG(@" Comparing model name to target \"%@\": ", targetName);
+ if ([targetName length] == 0 || [modelName hasPrefix:targetName]) {
+ LOG(@"success.\n");
+ } else {
+ LOG(@"failure.\n");
+ // Don't need to increment failure_result.
+ continue;
+ }
+
+ LOG(@" Fetching dictionary for service: ");
+ CFMutableDictionaryRef dict = IOServiceMatching(serviceName);
+
+ if (dict) {
+ LOG(@"success.\n");
+ } else {
+ LOG(@"failure.\n");
+ if (failure_result < SMS_FAIL_DICTIONARY) {
+ failure_result = SMS_FAIL_DICTIONARY;
+ }
+ continue;
+ }
+
+ LOG(@" Getting list of matching services: ");
+ result = IOServiceGetMatchingServices(kIOMasterPortDefault,
+ dict,
+ &iterator);
+
+ if (result == KERN_SUCCESS) {
+ LOG(@"success.\n");
+ } else {
+ LOG_ARG(@"failure, with return value 0x%x.\n", result);
+ if (failure_result < SMS_FAIL_LIST_SERVICES) {
+ failure_result = SMS_FAIL_LIST_SERVICES;
+ }
+ continue;
+ }
+
+ LOG(@" Getting first device in list: ");
+ device = IOIteratorNext(iterator);
+
+ if (device == 0) {
+ LOG(@"failure.\n");
+ if (failure_result < SMS_FAIL_NO_SERVICES) {
+ failure_result = SMS_FAIL_NO_SERVICES;
+ }
+ continue;
+ } else {
+ LOG(@"success.\n");
+ LOG(@" Opening device: ");
+ }
+
+ result = IOServiceOpen(device, mach_task_self(), 0, &connection);
+
+ if (result != KERN_SUCCESS) {
+ LOG_ARG(@"failure, with return value 0x%x.\n", result);
+ IOObjectRelease(device);
+ if (failure_result < SMS_FAIL_OPENING) {
+ failure_result = SMS_FAIL_OPENING;
+ }
+ continue;
+ } else if (connection == 0) {
+ LOG_ARG(@"'success', but didn't get a connection (return value was: 0x%x).\n", result);
+ IOObjectRelease(device);
+ if (failure_result < SMS_FAIL_CONNECTION) {
+ failure_result = SMS_FAIL_CONNECTION;
+ }
+ continue;
+ } else {
+ IOObjectRelease(device);
+ LOG(@"success.\n");
+ }
+ LOG(@" Testing device.\n");
+
+ defaultCalibration();
+
+ iRecord = (char*) malloc(recordSize);
+ oRecord = (char*) malloc(recordSize);
+
+ running = YES;
+ result = getData(&accel, true, logObject, logSelector);
+ running = NO;
+
+ if (result) {
+ LOG_ARG(@" Failure testing device, with result 0x%x.\n", result);
+ free(iRecord);
+ iRecord = 0;
+ free(oRecord);
+ oRecord = 0;
+ if (failure_result < SMS_FAIL_ACCESS) {
+ failure_result = SMS_FAIL_ACCESS;
+ }
+ continue;
+ } else {
+ LOG(@" Success testing device!\n");
+ running = YES;
+ return SMS_SUCCESS;
+ }
+ }
+ return failure_result;
+}
+
+// This starts up the library in debug mode, ignoring the actual hardware.
+// Returned data is in the form of 1Hz sine waves, with the X, Y and Z
+// axes 120 degrees out of phase; "calibrated" data has range +/- (1.0/5);
+// "uncalibrated" data has range +/- (256/5). X and Y axes centered on 0.0,
+// Z axes centered on 1 (calibrated) or 256 (uncalibrated).
+// Don't use smsGetBufferLength or smsGetBufferData. Always returns SMS_SUCCESS.
+int smsDebugStartup(id logObject, SEL logSelector) {
+ LOG(@"Starting up in debug mode\n");
+ debugging = YES;
+ return SMS_SUCCESS;
+}
+
+// Returns the current calibration values.
+void smsGetCalibration(sms_calibration *calibrationRecord) {
+ int x;
+
+ for (x = 0; x < 3; x++) {
+ calibrationRecord->zeros[x] = (debugging ? 0 : zeros[x]);
+ calibrationRecord->onegs[x] = (debugging ? 256 : onegs[x]);
+ }
+}
+
+// Sets the calibration, but does NOT store it as a preference. If the argument
+// is nil then the current calibration is set from the built-in value table.
+void smsSetCalibration(sms_calibration *calibrationRecord) {
+ int x;
+
+ if (!debugging) {
+ if (calibrationRecord) {
+ for (x = 0; x < 3; x++) {
+ zeros[x] = calibrationRecord->zeros[x];
+ onegs[x] = calibrationRecord->onegs[x];
+ }
+ } else {
+ defaultCalibration();
+ }
+ }
+}
+
+// Stores the current calibration values as a stored preference.
+void smsStoreCalibration(void) {
+ if (!debugging)
+ storeCalibration();
+}
+
+// Loads the stored preference values into the current calibration.
+// Returns YES if successful.
+BOOL smsLoadCalibration(void) {
+ if (debugging) {
+ return YES;
+ } else if (loadCalibration()) {
+ return YES;
+ } else {
+ defaultCalibration();
+ return NO;
+ }
+}
+
+// Deletes any stored calibration, and then takes the current calibration values
+// from the built-in value table.
+void smsDeleteCalibration(void) {
+ if (!debugging) {
+ deleteCalibration();
+ defaultCalibration();
+ }
+}
+
+// Fills in the accel record with calibrated acceleration data. Takes
+// 1-2ms to return a value. Returns 0 if success, error number if failure.
+int smsGetData(sms_acceleration *accel) {
+ NSTimeInterval time;
+ if (debugging) {
+ usleep(1500); // Usually takes 1-2 milliseconds
+ time = [NSDate timeIntervalSinceReferenceDate];
+ accel->x = fakeData(time)/5;
+ accel->y = fakeData(time - 1)/5;
+ accel->z = fakeData(time - 2)/5 + 1.0;
+ return true;
+ } else {
+ return getData(accel, true, nil, nil);
+ }
+}
+
+// Fills in the accel record with uncalibrated acceleration data.
+// Returns 0 if success, error number if failure.
+int smsGetUncalibratedData(sms_acceleration *accel) {
+ NSTimeInterval time;
+ if (debugging) {
+ usleep(1500); // Usually takes 1-2 milliseconds
+ time = [NSDate timeIntervalSinceReferenceDate];
+ accel->x = fakeData(time) * 256 / 5;
+ accel->y = fakeData(time - 1) * 256 / 5;
+ accel->z = fakeData(time - 2) * 256 / 5 + 256;
+ return true;
+ } else {
+ return getData(accel, false, nil, nil);
+ }
+}
+
+// Returns the length of a raw block of data for the current type of sensor.
+int smsGetBufferLength(void) {
+ if (debugging) {
+ return 0;
+ } else if (running) {
+ return sensors[sensorNum].recordSize;
+ } else {
+ return 0;
+ }
+}
+
+// Takes a pointer to accelGetRawLength() bytes; sets those bytes
+// to return value from sensor. Make darn sure the buffer length is right!
+void smsGetBufferData(char *buffer) {
+ IOItemCount iSize = recordSize;
+ IOByteCount oSize = recordSize;
+ kern_return_t result;
+
+ if (debugging || running == NO) {
+ return;
+ }
+
+ memset(iRecord, 1, iSize);
+ memset(buffer, 0, oSize);
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
+ const size_t InStructSize = recordSize;
+ size_t OutStructSize = recordSize;
+ result = IOConnectCallStructMethod(connection,
+ function, // magic kernel function number
+ (const void *)iRecord,
+ InStructSize,
+ (void *)buffer,
+ &OutStructSize
+ );
+#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
+ result = IOConnectMethodStructureIStructureO(connection,
+ function, // magic kernel function number
+ iSize,
+ &oSize,
+ iRecord,
+ buffer
+ );
+#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
+
+ if (result != KERN_SUCCESS) {
+ running = NO;
+ }
+}
+
+// This returns an NSString describing the current calibration in
+// human-readable form. Also include a description of the machine.
+NSString *smsGetCalibrationDescription(void) {
+ BOOL success;
+ NSMutableString *s = [[NSMutableString alloc] init];
+
+ if (debugging) {
+ [s release];
+ return @"Debugging!";
+ }
+
+ [s appendString:@"---- SeisMac Calibration Record ----\n \n"];
+ [s appendFormat:@"Machine model: %@\n",
+ getModelName()];
+ [s appendFormat:@"OS X build: %@\n",
+ getOSVersion()];
+ [s appendFormat:@"SeisMacLib version %s, record %d\n \n",
+ SMSLIB_VERSION, sensorNum];
+ [s appendFormat:@"Using service \"%s\", function index %d, size %d\n \n",
+ serviceName, function, recordSize];
+ if (prefIntRead(CALIBRATED_NAME, &success) && success) {
+ [s appendString:@"Calibration values (from calibration):\n"];
+ } else {
+ [s appendString:@"Calibration values (from defaults):\n"];
+ }
+ [s appendFormat:@" X-Axis-Zero = %.2f\n", zeros[0]];
+ [s appendFormat:@" X-Axis-One-g = %.2f\n", onegs[0]];
+ [s appendFormat:@" Y-Axis-Zero = %.2f\n", zeros[1]];
+ [s appendFormat:@" Y-Axis-One-g = %.2f\n", onegs[1]];
+ [s appendFormat:@" Z-Axis-Zero = %.2f\n", zeros[2]];
+ [s appendFormat:@" Z-Axis-One-g = %.2f\n \n", onegs[2]];
+ [s appendString:@"---- End Record ----\n"];
+ return s;
+}
+
+// Shuts down the accelerometer.
+void smsShutdown(void) {
+ if (!debugging) {
+ running = NO;
+ if (iRecord) free(iRecord);
+ if (oRecord) free(oRecord);
+ IOServiceClose(connection);
+ }
+}
+
+#pragma mark Internal functions
+
+// Loads the current calibration from the stored preferences.
+// Returns true iff successful.
+BOOL loadCalibration(void) {
+ BOOL thisSuccess, allSuccess;
+ int x;
+
+ prefSynchronize();
+
+ if (prefIntRead(CALIBRATED_NAME, &thisSuccess) && thisSuccess) {
+ // Calibrated. Set all values from saved values.
+ allSuccess = YES;
+ for (x = 0; x < 3; x++) {
+ zeros[x] = prefFloatRead(ZERO_NAME(x), &thisSuccess);
+ allSuccess &= thisSuccess;
+ onegs[x] = prefFloatRead(ONEG_NAME(x), &thisSuccess);
+ allSuccess &= thisSuccess;
+ }
+ return allSuccess;
+ }
+
+ return NO;
+}
+
+// Stores the current calibration into the stored preferences.
+static void storeCalibration(void) {
+ int x;
+ prefIntWrite(CALIBRATED_NAME, 1);
+ for (x = 0; x < 3; x++) {
+ prefFloatWrite(ZERO_NAME(x), zeros[x]);
+ prefFloatWrite(ONEG_NAME(x), onegs[x]);
+ }
+ prefSynchronize();
+}
+
+
+// Sets the calibration to its default values.
+void defaultCalibration(void) {
+ int x;
+ for (x = 0; x < 3; x++) {
+ zeros[x] = sensors[sensorNum].axes[x].zerog;
+ onegs[x] = sensors[sensorNum].axes[x].oneg;
+ }
+}
+
+// Deletes the stored preferences.
+static void deleteCalibration(void) {
+ int x;
+
+ prefDelete(CALIBRATED_NAME);
+ for (x = 0; x < 3; x++) {
+ prefDelete(ZERO_NAME(x));
+ prefDelete(ONEG_NAME(x));
+ }
+ prefSynchronize();
+}
+
+// Read a named floating point value from the stored preferences. Sets
+// the success boolean based on, you guessed it, whether it succeeds.
+static float prefFloatRead(NSString *prefName, BOOL *success) {
+ float result = 0.0f;
+
+ CFPropertyListRef ref = CFPreferencesCopyAppValue((CFStringRef)prefName,
+ APP_ID);
+ // If there isn't such a preference, fail
+ if (ref == NULL) {
+ *success = NO;
+ return result;
+ }
+ CFTypeID typeID = CFGetTypeID(ref);
+ // Is it a number?
+ if (typeID == CFNumberGetTypeID()) {
+ // Is it a floating point number?
+ if (CFNumberIsFloatType((CFNumberRef)ref)) {
+ // Yup: grab it.
+ *success = CFNumberGetValue((__CFNumber*)ref, kCFNumberFloat32Type, &result);
+ } else {
+ // Nope: grab as an integer, and convert to a float.
+ long num;
+ if (CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &num)) {
+ result = num;
+ *success = YES;
+ } else {
+ *success = NO;
+ }
+ }
+ // Or is it a string (e.g. set by the command line "defaults" command)?
+ } else if (typeID == CFStringGetTypeID()) {
+ result = (float)CFStringGetDoubleValue((CFStringRef)ref);
+ *success = YES;
+ } else {
+ // Can't convert to a number: fail.
+ *success = NO;
+ }
+ CFRelease(ref);
+ return result;
+}
+
+// Writes a named floating point value to the stored preferences.
+static void prefFloatWrite(NSString *prefName, float prefValue) {
+ CFNumberRef cfFloat = CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberFloatType,
+ &prefValue);
+ CFPreferencesSetAppValue((CFStringRef)prefName,
+ cfFloat,
+ APP_ID);
+ CFRelease(cfFloat);
+}
+
+// Reads a named integer value from the stored preferences.
+static int prefIntRead(NSString *prefName, BOOL *success) {
+ Boolean internalSuccess;
+ CFIndex result = CFPreferencesGetAppIntegerValue((CFStringRef)prefName,
+ APP_ID,
+ &internalSuccess);
+ *success = internalSuccess;
+
+ return result;
+}
+
+// Writes a named integer value to the stored preferences.
+static void prefIntWrite(NSString *prefName, int prefValue) {
+ CFPreferencesSetAppValue((CFStringRef)prefName,
+ (CFNumberRef)[NSNumber numberWithInt:prefValue],
+ APP_ID);
+}
+
+// Deletes the named preference values.
+static void prefDelete(NSString *prefName) {
+ CFPreferencesSetAppValue((CFStringRef)prefName,
+ NULL,
+ APP_ID);
+}
+
+// Synchronizes the local preferences with the stored preferences.
+static void prefSynchronize(void) {
+ CFPreferencesAppSynchronize(APP_ID);
+}
+
+// Internal version of accelGetData, with logging
+int getData(sms_acceleration *accel, int calibrated, id logObject, SEL logSelector) {
+ IOItemCount iSize = recordSize;
+ IOByteCount oSize = recordSize;
+ kern_return_t result;
+
+ if (running == NO) {
+ return -1;
+ }
+
+ memset(iRecord, 1, iSize);
+ memset(oRecord, 0, oSize);
+
+ LOG_2ARG(@" Querying device (%u, %d): ",
+ sensors[sensorNum].function, sensors[sensorNum].recordSize);
+
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
+ const size_t InStructSize = recordSize;
+ size_t OutStructSize = recordSize;
+ result = IOConnectCallStructMethod(connection,
+ function, // magic kernel function number
+ (const void *)iRecord,
+ InStructSize,
+ (void *)oRecord,
+ &OutStructSize
+ );
+#else // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
+ result = IOConnectMethodStructureIStructureO(connection,
+ function, // magic kernel function number
+ iSize,
+ &oSize,
+ iRecord,
+ oRecord
+ );
+#endif // __MAC_OS_X_VERSION_MIN_REQUIRED 1050
+
+ if (result != KERN_SUCCESS) {
+ LOG(@"failed.\n");
+ running = NO;
+ return result;
+ } else {
+ LOG(@"succeeded.\n");
+
+ accel->x = getAxis(0, calibrated);
+ accel->y = getAxis(1, calibrated);
+ accel->z = getAxis(2, calibrated);
+ return 0;
+ }
+}
+
+// Given the returned record, extracts the value of the given axis. If
+// calibrated, then zero G is 0.0, and one G is 1.0.
+float getAxis(int which, int calibrated) {
+ // Get various values (to make code cleaner)
+ int indx = sensors[sensorNum].axes[which].index;
+ int size = sensors[sensorNum].axes[which].size;
+ float zerog = zeros[which];
+ float oneg = onegs[which];
+ // Storage for value to be returned
+ int value = 0;
+
+ // Although the values in the returned record should have the proper
+ // endianness, we still have to get it into the proper end of value.
+#if (BYTE_ORDER == BIG_ENDIAN)
+ // On PowerPC processors
+ memcpy(((char *)&value) + (sizeof(int) - size), &oRecord[indx], size);
+#endif
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+ // On Intel processors
+ memcpy(&value, &oRecord[indx], size);
+#endif
+
+ value = signExtend(value, size);
+
+ if (calibrated) {
+ // Scale and shift for zero.
+ return ((float)(value - zerog)) / oneg;
+ } else {
+ return value;
+ }
+}
+
+// Extends the sign, given the length of the value.
+int signExtend(int value, int size) {
+ // Extend sign
+ switch (size) {
+ case 1:
+ if (value & 0x00000080)
+ value |= 0xffffff00;
+ break;
+ case 2:
+ if (value & 0x00008000)
+ value |= 0xffff0000;
+ break;
+ case 3:
+ if (value & 0x00800000)
+ value |= 0xff000000;
+ break;
+ }
+ return value;
+}
+
+// Returns the model name of the computer (e.g. "MacBookPro1,1")
+NSString *getModelName(void) {
+ char model[32];
+ size_t len = sizeof(model);
+ int name[2] = {CTL_HW, HW_MODEL};
+ NSString *result;
+
+ if (sysctl(name, 2, &model, &len, NULL, 0) == 0) {
+ result = [NSString stringWithFormat:@"%s", model];
+ } else {
+ result = @"";
+ }
+
+ return result;
+}
+
+// Returns the current OS X version and build (e.g. "10.4.7 (build 8J2135a)")
+NSString *getOSVersion(void) {
+ NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:
+ @"/System/Library/CoreServices/SystemVersion.plist"];
+ NSString *versionString = [dict objectForKey:@"ProductVersion"];
+ NSString *buildString = [dict objectForKey:@"ProductBuildVersion"];
+ NSString *wholeString = [NSString stringWithFormat:@"%@ (build %@)",
+ versionString, buildString];
+ return wholeString;
+}
+
+// Returns time within the current second in microseconds.
+// long getMicroseconds() {
+// struct timeval t;
+// gettimeofday(&t, 0);
+// return t.tv_usec;
+//}
+
+// Returns fake data given the time. Range is +/-1.
+float fakeData(NSTimeInterval time) {
+ long secs = lround(floor(time));
+ int secsMod3 = secs % 3;
+ double angle = time * 10 * M_PI * 2;
+ double mag = exp(-(time - (secs - secsMod3)) * 2);
+ return sin(angle) * mag;
+}
+
diff --git a/hal/moz.build b/hal/moz.build
index 1aecdb750e..fefd56fcf1 100644
--- a/hal/moz.build
+++ b/hal/moz.build
@@ -40,6 +40,13 @@ elif CONFIG['OS_TARGET'] == 'WINNT':
'fallback/FallbackScreenConfiguration.cpp',
'windows/WindowsSensor.cpp',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'fallback/FallbackAlarm.cpp',
+ 'fallback/FallbackMemory.cpp',
+ 'fallback/FallbackPower.cpp',
+ 'fallback/FallbackScreenConfiguration.cpp',
+ ]
elif CONFIG['OS_TARGET'] in ('OpenBSD', 'NetBSD', 'FreeBSD', 'DragonFly'):
UNIFIED_SOURCES += [
'fallback/FallbackAlarm.cpp',
@@ -72,6 +79,12 @@ UNIFIED_SOURCES += [
'fallback/FallbackNetwork.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'cocoa/CocoaSensor.mm',
+ 'cocoa/smslib.mm',
+ ]
+
IPDL_SOURCES = [
'sandbox/PHal.ipdl',
]
diff --git a/image/decoders/icon/mac/moz.build b/image/decoders/icon/mac/moz.build
new file mode 100644
index 0000000000..f36d6ca53d
--- /dev/null
+++ b/image/decoders/icon/mac/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+ 'nsIconChannelCocoa.mm',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/image/decoders/icon/mac/nsIconChannel.h b/image/decoders/icon/mac/nsIconChannel.h
new file mode 100644
index 0000000000..9fef171193
--- /dev/null
+++ b/image/decoders/icon/mac/nsIconChannel.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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/. */
+
+#ifndef mozilla_image_encoders_icon_mac_nsIconChannel_h
+#define mozilla_image_encoders_icon_mac_nsIconChannel_h
+
+#include "mozilla/Attributes.h"
+
+#include "nsCOMPtr.h"
+#include "nsXPIDLString.h"
+#include "nsIChannel.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamListener.h"
+#include "nsIURI.h"
+
+class nsIFile;
+
+class nsIconChannel final : public nsIChannel, public nsIStreamListener
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsIconChannel();
+
+ nsresult Init(nsIURI* uri);
+
+protected:
+ virtual ~nsIconChannel();
+
+ nsCOMPtr<nsIURI> mUrl;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsILoadInfo> mLoadInfo;
+
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIStreamListener> mListener;
+
+ nsresult MakeInputStream(nsIInputStream** _retval, bool nonBlocking);
+
+ nsresult ExtractIconInfoFromUrl(nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize,
+ nsACString& aContentType,
+ nsACString& aFileExtension);
+};
+
+#endif // mozilla_image_encoders_icon_mac_nsIconChannel_h
diff --git a/image/decoders/icon/mac/nsIconChannelCocoa.mm b/image/decoders/icon/mac/nsIconChannelCocoa.mm
new file mode 100644
index 0000000000..9c2686cdd2
--- /dev/null
+++ b/image/decoders/icon/mac/nsIconChannelCocoa.mm
@@ -0,0 +1,565 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "nsContentUtils.h"
+#include "nsIconChannel.h"
+#include "mozilla/EndianUtils.h"
+#include "nsIIconURI.h"
+#include "nsIServiceManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsXPIDLString.h"
+#include "nsMimeTypes.h"
+#include "nsMemory.h"
+#include "nsIStringStream.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIPipe.h"
+#include "nsIOutputStream.h"
+#include "nsIMIMEService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsILocalFileMac.h"
+#include "nsIFileURL.h"
+#include "nsTArray.h"
+#include "nsObjCExceptions.h"
+#include "nsProxyRelease.h"
+#include "nsContentSecurityManager.h"
+
+#include <Cocoa/Cocoa.h>
+
+// nsIconChannel methods
+nsIconChannel::nsIconChannel()
+{
+}
+
+nsIconChannel::~nsIconChannel()
+{
+ if (mLoadInfo) {
+ NS_ReleaseOnMainThread(mLoadInfo.forget());
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsIconChannel,
+ nsIChannel,
+ nsIRequest,
+ nsIRequestObserver,
+ nsIStreamListener)
+
+nsresult
+nsIconChannel::Init(nsIURI* uri)
+{
+ NS_ASSERTION(uri, "no uri");
+ mUrl = uri;
+ mOriginalURI = uri;
+ nsresult rv;
+ mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIRequest methods:
+
+NS_IMETHODIMP
+nsIconChannel::GetName(nsACString& result)
+{
+ return mUrl->GetSpec(result);
+}
+
+NS_IMETHODIMP
+nsIconChannel::IsPending(bool* result)
+{
+ return mPump->IsPending(result);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetStatus(nsresult* status)
+{
+ return mPump->GetStatus(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Cancel(nsresult status)
+{
+ return mPump->Cancel(status);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Suspend(void)
+{
+ return mPump->Suspend();
+}
+
+NS_IMETHODIMP
+nsIconChannel::Resume(void)
+{
+ return mPump->Resume();
+}
+
+// nsIRequestObserver methods
+NS_IMETHODIMP
+nsIconChannel::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ if (mListener) {
+ return mListener->OnStartRequest(this, aContext);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ if (mListener) {
+ mListener->OnStopRequest(this, aContext, aStatus);
+ mListener = nullptr;
+ }
+
+ // Remove from load group
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, aStatus);
+ }
+
+ return NS_OK;
+}
+
+// nsIStreamListener methods
+NS_IMETHODIMP
+nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ if (mListener) {
+ return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIChannel methods:
+
+NS_IMETHODIMP
+nsIconChannel::GetOriginalURI(nsIURI** aURI)
+{
+ *aURI = mOriginalURI;
+ NS_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetOriginalURI(nsIURI* aURI)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ mOriginalURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetURI(nsIURI** aURI)
+{
+ *aURI = mUrl;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::Open(nsIInputStream** _retval)
+{
+ return MakeInputStream(_retval, false);
+}
+
+NS_IMETHODIMP
+nsIconChannel::Open2(nsIInputStream** aStream)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(aStream);
+}
+
+nsresult
+nsIconChannel::ExtractIconInfoFromUrl(nsIFile** aLocalFile,
+ uint32_t* aDesiredImageSize,
+ nsACString& aContentType,
+ nsACString& aFileExtension)
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ iconURI->GetImageSize(aDesiredImageSize);
+ iconURI->GetContentType(aContentType);
+ iconURI->GetFileExtension(aFileExtension);
+
+ nsCOMPtr<nsIURL> url;
+ rv = iconURI->GetIconURL(getter_AddRefs(url));
+ if (NS_FAILED(rv) || !url) return NS_OK;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv);
+ if (NS_FAILED(rv) || !fileURL) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_FAILED(rv) || !file) return NS_OK;
+
+ nsCOMPtr<nsILocalFileMac> localFileMac (do_QueryInterface(file, &rv));
+ if (NS_FAILED(rv) || !localFileMac) return NS_OK;
+
+ *aLocalFile = file;
+ NS_IF_ADDREF(*aLocalFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::AsyncOpen(nsIStreamListener* aListener,
+ nsISupports* ctxt)
+{
+ MOZ_ASSERT(!mLoadInfo ||
+ mLoadInfo->GetSecurityMode() == 0 ||
+ mLoadInfo->GetInitialSecurityCheckDone() ||
+ (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
+ "security flags in loadInfo but asyncOpen2() not called");
+
+ nsCOMPtr<nsIInputStream> inStream;
+ nsresult rv = MakeInputStream(getter_AddRefs(inStream), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Init our stream pump
+ rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mPump->AsyncRead(this, ctxt);
+ if (NS_SUCCEEDED(rv)) {
+ // Store our real listener
+ mListener = aListener;
+ // Add ourself to the load group, if available
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsIconChannel::AsyncOpen2(nsIStreamListener* aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult
+nsIconChannel::MakeInputStream(nsIInputStream** _retval,
+ bool aNonBlocking)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsXPIDLCString contentType;
+ nsAutoCString fileExt;
+ nsCOMPtr<nsIFile> fileloc; // file we want an icon for
+ uint32_t desiredImageSize;
+ nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc),
+ &desiredImageSize, contentType, fileExt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool fileExists = false;
+ if (fileloc) {
+ // ensure that we DO NOT resolve aliases, very important for file views
+ fileloc->SetFollowLinks(false);
+ fileloc->Exists(&fileExists);
+ }
+
+ NSImage* iconImage = nil;
+
+ // first try to get the icon from the file if it exists
+ if (fileExists) {
+ nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(fileloc, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CFURLRef macURL;
+ if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) {
+ iconImage = [[NSWorkspace sharedWorkspace]
+ iconForFile:[(NSURL*)macURL path]];
+ ::CFRelease(macURL);
+ }
+ }
+
+ // if we don't have an icon yet try to get one by extension
+ if (!iconImage && !fileExt.IsEmpty()) {
+ NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()];
+ iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension];
+ }
+
+ // If we still don't have an icon, get the generic document icon.
+ if (!iconImage) {
+ iconImage = [[NSWorkspace sharedWorkspace]
+ iconForFileType:NSFileTypeUnknown];
+ }
+
+ if (!iconImage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // we have an icon now, size it
+ NSRect desiredSizeRect = NSMakeRect(0, 0, desiredImageSize, desiredImageSize);
+ [iconImage setSize:desiredSizeRect.size];
+
+ [iconImage lockFocus];
+ NSBitmapImageRep* bitmapRep = [[[NSBitmapImageRep alloc]
+ initWithFocusedViewRect:desiredSizeRect]
+ autorelease];
+ [iconImage unlockFocus];
+
+ // we expect the following things to be true about our bitmapRep
+ NS_ENSURE_TRUE(![bitmapRep isPlanar] &&
+ // Not necessarily: on a HiDPI-capable system, we'll get
+ // a 2x bitmap
+ // (unsigned int)[bitmapRep bytesPerPlane] ==
+ // desiredImageSize * desiredImageSize * 4 &&
+ [bitmapRep bitsPerPixel] == 32 &&
+ [bitmapRep samplesPerPixel] == 4 &&
+ [bitmapRep hasAlpha] == YES,
+ NS_ERROR_UNEXPECTED);
+
+ // check what size we actually got, and ensure it isn't too big to return
+ uint32_t actualImageSize = [bitmapRep bytesPerRow] / 4;
+ NS_ENSURE_TRUE(actualImageSize < 256, NS_ERROR_UNEXPECTED);
+
+ // now we can validate the amount of data
+ NS_ENSURE_TRUE((unsigned int)[bitmapRep bytesPerPlane] ==
+ actualImageSize * actualImageSize * 4,
+ NS_ERROR_UNEXPECTED);
+
+ // rgba, pre-multiplied data
+ uint8_t* bitmapRepData = (uint8_t*)[bitmapRep bitmapData];
+
+ // create our buffer
+ int32_t bufferCapacity = 2 + [bitmapRep bytesPerPlane];
+ AutoTArray<uint8_t, 3 + 16 * 16 * 5> iconBuffer; // initial size is for
+ // 16x16
+ iconBuffer.SetLength(bufferCapacity);
+
+ uint8_t* iconBufferPtr = iconBuffer.Elements();
+
+ // write header data into buffer
+ *iconBufferPtr++ = actualImageSize;
+ *iconBufferPtr++ = actualImageSize;
+
+ uint32_t dataCount = [bitmapRep bytesPerPlane];
+ uint32_t index = 0;
+ while (index < dataCount) {
+ // get data from the bitmap
+ uint8_t r = bitmapRepData[index++];
+ uint8_t g = bitmapRepData[index++];
+ uint8_t b = bitmapRepData[index++];
+ uint8_t a = bitmapRepData[index++];
+
+ // write data out to our buffer
+ // non-cairo uses native image format, but the A channel is ignored.
+ // cairo uses ARGB (highest to lowest bits)
+#if MOZ_LITTLE_ENDIAN
+ *iconBufferPtr++ = b;
+ *iconBufferPtr++ = g;
+ *iconBufferPtr++ = r;
+ *iconBufferPtr++ = a;
+#else
+ *iconBufferPtr++ = a;
+ *iconBufferPtr++ = r;
+ *iconBufferPtr++ = g;
+ *iconBufferPtr++ = b;
+#endif
+ }
+
+ NS_ASSERTION(iconBufferPtr == iconBuffer.Elements() + bufferCapacity,
+ "buffer size miscalculation");
+
+ // Now, create a pipe and stuff our data into it
+ nsCOMPtr<nsIInputStream> inStream;
+ nsCOMPtr<nsIOutputStream> outStream;
+ rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream),
+ bufferCapacity, bufferCapacity, aNonBlocking);
+
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t written;
+ rv = outStream->Write((char*)iconBuffer.Elements(), bufferCapacity,
+ &written);
+ if (NS_SUCCEEDED(rv)) {
+ NS_IF_ADDREF(*_retval = inStream);
+ }
+ }
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadFlags(uint32_t* aLoadAttributes)
+{
+ return mPump->GetLoadFlags(aLoadAttributes);
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes)
+{
+ return mPump->SetLoadFlags(aLoadAttributes);
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentType(nsACString& aContentType)
+{
+ aContentType.AssignLiteral(IMAGE_ICON_MS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentType(const nsACString& aContentType)
+{
+ //It doesn't make sense to set the content-type on this type
+ // of channel...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentCharset(nsACString& aContentCharset)
+{
+ aContentCharset.AssignLiteral(IMAGE_ICON_MS);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentCharset(const nsACString& aContentCharset)
+{
+ //It doesn't make sense to set the content-type on this type
+ // of channel...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentDisposition(uint32_t* aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetContentDispositionFilename(nsAString& aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetContentDispositionHeader(nsACString& aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetContentLength(int64_t* aContentLength)
+{
+ *aContentLength = 0;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetContentLength(int64_t aContentLength)
+{
+ NS_NOTREACHED("nsIconChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadGroup(nsILoadGroup** aLoadGroup)
+{
+ *aLoadGroup = mLoadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+ mLoadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetOwner(nsISupports** aOwner)
+{
+ *aOwner = mOwner.get();
+ NS_IF_ADDREF(*aOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetOwner(nsISupports* aOwner)
+{
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
+{
+ NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
+{
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ GetNotificationCallbacks(nsIInterfaceRequestor** aNotificationCallbacks)
+{
+ *aNotificationCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::
+ SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIconChannel::GetSecurityInfo(nsISupports** aSecurityInfo)
+{
+ *aSecurityInfo = nullptr;
+ return NS_OK;
+}
+
diff --git a/image/decoders/icon/moz.build b/image/decoders/icon/moz.build
index 56465dd274..5a173d3169 100644
--- a/image/decoders/icon/moz.build
+++ b/image/decoders/icon/moz.build
@@ -21,5 +21,8 @@ if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
if CONFIG['OS_ARCH'] == 'WINNT':
platform = 'win'
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ platform = 'mac'
+
if platform:
LOCAL_INCLUDES += [platform]
diff --git a/image/decoders/moz.build b/image/decoders/moz.build
index 5a07bf765f..907b81bda8 100644
--- a/image/decoders/moz.build
+++ b/image/decoders/moz.build
@@ -13,6 +13,9 @@ if 'gtk' in toolkit:
if CONFIG['OS_ARCH'] == 'WINNT':
DIRS += ['icon/win', 'icon']
+if toolkit == 'cocoa':
+ DIRS += ['icon/mac', 'icon']
+
UNIFIED_SOURCES += [
'EXIF.cpp',
'iccjpeg.c',
diff --git a/image/imgFrame.cpp b/image/imgFrame.cpp
index e2be7673b4..9a0846bce2 100644
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -123,12 +123,20 @@ AllowedImageSize(int32_t aWidth, int32_t aHeight)
return false;
}
- // check to make sure we don't overflow 32-bit size for RGBA
+ // check to make sure we don't overflow a 32-bit
CheckedInt32 requiredBytes = CheckedInt32(aWidth) * CheckedInt32(aHeight) * 4;
if (MOZ_UNLIKELY(!requiredBytes.isValid())) {
NS_WARNING("width or height too large");
return false;
}
+#if defined(XP_MACOSX)
+ // CoreGraphics is limited to images < 32K in *height*, so clamp all surfaces
+ // on the Mac to that height
+ if (MOZ_UNLIKELY(aHeight > SHRT_MAX)) {
+ NS_WARNING("image too big");
+ return false;
+ }
+#endif
return true;
}
diff --git a/intl/locale/mac/moz.build b/intl/locale/mac/moz.build
new file mode 100644
index 0000000000..422fd3e3cd
--- /dev/null
+++ b/intl/locale/mac/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+UNIFIED_SOURCES += [
+ 'nsCollationMacUC.cpp',
+ 'nsDateTimeFormatMac.cpp',
+ 'nsMacCharset.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '..',
+]
diff --git a/intl/locale/mac/nsCollationMacUC.cpp b/intl/locale/mac/nsCollationMacUC.cpp
new file mode 100644
index 0000000000..d230f4d771
--- /dev/null
+++ b/intl/locale/mac/nsCollationMacUC.cpp
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsCollationMacUC.h"
+#include "nsILocaleService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIServiceManager.h"
+#include "prmem.h"
+#include "nsString.h"
+
+NS_IMPL_ISUPPORTS(nsCollationMacUC, nsICollation)
+
+nsCollationMacUC::nsCollationMacUC()
+ : mInit(false)
+ , mHasCollator(false)
+ , mLocaleICU(nullptr)
+ , mLastStrength(-1)
+ , mCollatorICU(nullptr)
+{ }
+
+nsCollationMacUC::~nsCollationMacUC()
+{
+#ifdef DEBUG
+ nsresult res =
+#endif
+ CleanUpCollator();
+ NS_ASSERTION(NS_SUCCEEDED(res), "CleanUpCollator failed");
+ if (mLocaleICU) {
+ free(mLocaleICU);
+ mLocaleICU = nullptr;
+ }
+}
+
+nsresult nsCollationMacUC::ConvertStrength(const int32_t aNSStrength,
+ UCollationStrength* aICUStrength,
+ UColAttributeValue* aCaseLevelOut)
+{
+ NS_ENSURE_ARG_POINTER(aICUStrength);
+ NS_ENSURE_TRUE((aNSStrength < 4), NS_ERROR_FAILURE);
+
+ UCollationStrength strength = UCOL_DEFAULT;
+ UColAttributeValue caseLevel = UCOL_OFF;
+ switch (aNSStrength) {
+ case kCollationCaseInSensitive:
+ strength = UCOL_PRIMARY;
+ break;
+ case kCollationCaseInsensitiveAscii:
+ strength = UCOL_SECONDARY;
+ break;
+ case kCollationAccentInsenstive:
+ caseLevel = UCOL_ON;
+ strength = UCOL_PRIMARY;
+ break;
+ case kCollationCaseSensitive:
+ strength = UCOL_TERTIARY;
+ break;
+ default:
+ NS_WARNING("Bad aNSStrength passed to ConvertStrength.");
+ return NS_ERROR_FAILURE;
+ }
+
+ *aICUStrength = strength;
+ *aCaseLevelOut = caseLevel;
+
+ return NS_OK;
+}
+
+nsresult nsCollationMacUC::ConvertLocaleICU(nsILocale* aNSLocale, char** aICULocale)
+{
+ NS_ENSURE_ARG_POINTER(aNSLocale);
+ NS_ENSURE_ARG_POINTER(aICULocale);
+
+ nsAutoString localeString;
+ nsresult res = aNSLocale->GetCategory(NS_LITERAL_STRING("NSILOCALE_COLLATE"), localeString);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(res) && !localeString.IsEmpty(),
+ NS_ERROR_FAILURE);
+ NS_LossyConvertUTF16toASCII tmp(localeString);
+ tmp.ReplaceChar('-', '_');
+ char* locale = (char*)malloc(tmp.Length() + 1);
+ if (!locale) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ strcpy(locale, tmp.get());
+
+ *aICULocale = locale;
+
+ return NS_OK;
+}
+
+nsresult nsCollationMacUC::EnsureCollator(const int32_t newStrength)
+{
+ NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
+ if (mHasCollator && (mLastStrength == newStrength))
+ return NS_OK;
+
+ nsresult res;
+ res = CleanUpCollator();
+ NS_ENSURE_SUCCESS(res, res);
+
+ NS_ENSURE_TRUE(mLocaleICU, NS_ERROR_NOT_INITIALIZED);
+
+ UErrorCode status;
+ status = U_ZERO_ERROR;
+ mCollatorICU = ucol_open(mLocaleICU, &status);
+ NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
+
+ UCollationStrength strength;
+ UColAttributeValue caseLevel;
+ res = ConvertStrength(newStrength, &strength, &caseLevel);
+ NS_ENSURE_SUCCESS(res, res);
+
+ status = U_ZERO_ERROR;
+ ucol_setAttribute(mCollatorICU, UCOL_STRENGTH, strength, &status);
+ NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
+ ucol_setAttribute(mCollatorICU, UCOL_CASE_LEVEL, caseLevel, &status);
+ NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
+ ucol_setAttribute(mCollatorICU, UCOL_ALTERNATE_HANDLING, UCOL_DEFAULT, &status);
+ NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
+ ucol_setAttribute(mCollatorICU, UCOL_NUMERIC_COLLATION, UCOL_OFF, &status);
+ NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
+ ucol_setAttribute(mCollatorICU, UCOL_NORMALIZATION_MODE, UCOL_ON, &status);
+ NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
+ ucol_setAttribute(mCollatorICU, UCOL_CASE_FIRST, UCOL_DEFAULT, &status);
+ NS_ENSURE_TRUE(U_SUCCESS(status), NS_ERROR_FAILURE);
+
+ mHasCollator = true;
+
+ mLastStrength = newStrength;
+ return NS_OK;
+}
+
+nsresult nsCollationMacUC::CleanUpCollator(void)
+{
+ if (mHasCollator) {
+ ucol_close(mCollatorICU);
+ mHasCollator = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCollationMacUC::Initialize(nsILocale* locale)
+{
+ NS_ENSURE_TRUE((!mInit), NS_ERROR_ALREADY_INITIALIZED);
+ nsCOMPtr<nsILocale> appLocale;
+
+ nsresult rv;
+ if (!locale) {
+ nsCOMPtr<nsILocaleService> localeService = do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
+ NS_ENSURE_SUCCESS(rv, rv);
+ locale = appLocale;
+ }
+
+ rv = ConvertLocaleICU(locale, &mLocaleICU);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mInit = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCollationMacUC::AllocateRawSortKey(int32_t strength, const nsAString& stringIn,
+ uint8_t** key, uint32_t* outLen)
+{
+ NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_ARG_POINTER(key);
+ NS_ENSURE_ARG_POINTER(outLen);
+
+ nsresult res = EnsureCollator(strength);
+ NS_ENSURE_SUCCESS(res, res);
+
+ uint32_t stringInLen = stringIn.Length();
+
+ const UChar* str = (const UChar*)stringIn.BeginReading();
+
+ int32_t keyLength = ucol_getSortKey(mCollatorICU, str, stringInLen, nullptr, 0);
+ NS_ENSURE_TRUE((stringInLen == 0 || keyLength > 0), NS_ERROR_FAILURE);
+
+ // Since key is freed elsewhere with PR_Free, allocate with PR_Malloc.
+ uint8_t* newKey = (uint8_t*)PR_Malloc(keyLength + 1);
+ if (!newKey) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ keyLength = ucol_getSortKey(mCollatorICU, str, stringInLen, newKey, keyLength + 1);
+ NS_ENSURE_TRUE((stringInLen == 0 || keyLength > 0), NS_ERROR_FAILURE);
+
+ *key = newKey;
+ *outLen = keyLength;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCollationMacUC::CompareString(int32_t strength, const nsAString& string1,
+ const nsAString& string2, int32_t* result)
+{
+ NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_ARG_POINTER(result);
+ *result = 0;
+
+ nsresult rv = EnsureCollator(strength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ UCollationResult uresult;
+ uresult = ucol_strcoll(mCollatorICU,
+ (const UChar*)string1.BeginReading(),
+ string1.Length(),
+ (const UChar*)string2.BeginReading(),
+ string2.Length());
+ int32_t res;
+ switch (uresult) {
+ case UCOL_LESS:
+ res = -1;
+ break;
+ case UCOL_EQUAL:
+ res = 0;
+ break;
+ case UCOL_GREATER:
+ res = 1;
+ break;
+ default:
+ MOZ_CRASH("ucol_strcoll returned bad UCollationResult");
+ }
+ *result = res;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCollationMacUC::CompareRawSortKey(const uint8_t* key1, uint32_t len1,
+ const uint8_t* key2, uint32_t len2,
+ int32_t* result)
+{
+ NS_ENSURE_TRUE(mInit, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_ARG_POINTER(key1);
+ NS_ENSURE_ARG_POINTER(key2);
+ NS_ENSURE_ARG_POINTER(result);
+ *result = 0;
+
+ int32_t tmpResult = strcmp((const char*)key1, (const char*)key2);
+ int32_t res;
+ if (tmpResult < 0) {
+ res = -1;
+ } else if (tmpResult > 0) {
+ res = 1;
+ } else {
+ res = 0;
+ }
+ *result = res;
+ return NS_OK;
+}
diff --git a/intl/locale/mac/nsCollationMacUC.h b/intl/locale/mac/nsCollationMacUC.h
new file mode 100644
index 0000000000..46bb0145de
--- /dev/null
+++ b/intl/locale/mac/nsCollationMacUC.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsCollationMacUC_h_
+#define nsCollationMacUC_h_
+
+#include "nsICollation.h"
+#include "nsCollation.h"
+#include "mozilla/Attributes.h"
+
+#include "unicode/ucol.h"
+
+class nsCollationMacUC final : public nsICollation {
+
+public:
+ nsCollationMacUC();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsICollation interface
+ NS_DECL_NSICOLLATION
+
+protected:
+ ~nsCollationMacUC();
+
+ nsresult ConvertLocaleICU(nsILocale* aNSLocale, char** aICULocale);
+ nsresult ConvertStrength(const int32_t aStrength,
+ UCollationStrength* aStrengthOut,
+ UColAttributeValue* aCaseLevelOut);
+ nsresult EnsureCollator(const int32_t newStrength);
+ nsresult CleanUpCollator(void);
+
+private:
+ bool mInit;
+ bool mHasCollator;
+ char* mLocaleICU;
+ int32_t mLastStrength;
+ UCollator* mCollatorICU;
+};
+
+#endif /* nsCollationMacUC_h_ */
diff --git a/intl/locale/mac/nsDateTimeFormatMac.cpp b/intl/locale/mac/nsDateTimeFormatMac.cpp
new file mode 100644
index 0000000000..6ee73292d7
--- /dev/null
+++ b/intl/locale/mac/nsDateTimeFormatMac.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; 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 <CoreFoundation/CoreFoundation.h>
+#include "nsIServiceManager.h"
+#include "nsDateTimeFormatMac.h"
+#include <CoreFoundation/CFDateFormatter.h>
+#include "nsIComponentManager.h"
+#include "nsILocaleService.h"
+#include "nsCRT.h"
+#include "plstr.h"
+#include "nsUnicharUtils.h"
+#include "nsTArray.h"
+
+
+NS_IMPL_ISUPPORTS(nsDateTimeFormatMac, nsIDateTimeFormat)
+
+nsresult nsDateTimeFormatMac::Initialize(nsILocale* locale)
+{
+ nsAutoString localeStr;
+ nsAutoString category(NS_LITERAL_STRING("NSILOCALE_TIME"));
+ nsresult res;
+
+ // use cached info if match with stored locale
+ if (nullptr == locale) {
+ if (!mLocale.IsEmpty() &&
+ mLocale.Equals(mAppLocale, nsCaseInsensitiveStringComparator())) {
+ return NS_OK;
+ }
+ }
+ else {
+ res = locale->GetCategory(category, localeStr);
+ if (NS_SUCCEEDED(res) && !localeStr.IsEmpty()) {
+ if (!mLocale.IsEmpty() &&
+ mLocale.Equals(localeStr,
+ nsCaseInsensitiveStringComparator())) {
+ return NS_OK;
+ }
+ }
+ }
+
+ // get application locale
+ nsCOMPtr<nsILocaleService> localeService =
+ do_GetService(NS_LOCALESERVICE_CONTRACTID, &res);
+ if (NS_SUCCEEDED(res)) {
+ nsCOMPtr<nsILocale> appLocale;
+ res = localeService->GetApplicationLocale(getter_AddRefs(appLocale));
+ if (NS_SUCCEEDED(res)) {
+ res = appLocale->GetCategory(category, localeStr);
+ if (NS_SUCCEEDED(res) && !localeStr.IsEmpty()) {
+ mAppLocale = localeStr; // cache app locale name
+ }
+ }
+ }
+
+ // use app default if no locale specified
+ if (nullptr == locale) {
+ mUseDefaultLocale = true;
+ }
+ else {
+ mUseDefaultLocale = false;
+ res = locale->GetCategory(category, localeStr);
+ }
+
+ if (NS_SUCCEEDED(res) && !localeStr.IsEmpty()) {
+ mLocale.Assign(localeStr); // cache locale name
+ }
+
+ return res;
+}
+
+// performs a locale sensitive date formatting operation on the time_t parameter
+nsresult nsDateTimeFormatMac::FormatTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const time_t timetTime,
+ nsAString& stringOut)
+{
+ struct tm tmTime;
+ return FormatTMTime(locale, dateFormatSelector, timeFormatSelector, localtime_r(&timetTime, &tmTime), stringOut);
+}
+
+// performs a locale sensitive date formatting operation on the struct tm parameter
+nsresult nsDateTimeFormatMac::FormatTMTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const struct tm* tmTime,
+ nsAString& stringOut)
+{
+ nsresult res = NS_OK;
+
+ // set up locale data
+ (void) Initialize(locale);
+
+ // return, nothing to format
+ if (dateFormatSelector == kDateFormatNone && timeFormatSelector == kTimeFormatNone) {
+ stringOut.Truncate();
+ return NS_OK;
+ }
+
+ NS_ASSERTION(tmTime->tm_mon >= 0, "tm is not set correctly");
+ NS_ASSERTION(tmTime->tm_mday >= 1, "tm is not set correctly");
+ NS_ASSERTION(tmTime->tm_hour >= 0, "tm is not set correctly");
+ NS_ASSERTION(tmTime->tm_min >= 0, "tm is not set correctly");
+ NS_ASSERTION(tmTime->tm_sec >= 0, "tm is not set correctly");
+ NS_ASSERTION(tmTime->tm_wday >= 0, "tm is not set correctly");
+
+ // Got the locale for the formatter:
+ CFLocaleRef formatterLocale;
+ if (!locale) {
+ formatterLocale = CFLocaleCopyCurrent();
+ } else {
+ CFStringRef localeStr = CFStringCreateWithCharacters(nullptr,
+ reinterpret_cast<const UniChar*>(mLocale.get()),
+ mLocale.Length());
+ formatterLocale = CFLocaleCreate(nullptr, localeStr);
+ CFRelease(localeStr);
+ }
+
+ // Get the date style for the formatter:
+ CFDateFormatterStyle dateStyle;
+ switch (dateFormatSelector) {
+ case kDateFormatLong:
+ dateStyle = kCFDateFormatterLongStyle;
+ break;
+ case kDateFormatShort:
+ dateStyle = kCFDateFormatterShortStyle;
+ break;
+ case kDateFormatYearMonth:
+ case kDateFormatWeekday:
+ dateStyle = kCFDateFormatterNoStyle; // formats handled below
+ break;
+ case kDateFormatNone:
+ dateStyle = kCFDateFormatterNoStyle;
+ break;
+ default:
+ NS_ERROR("Unknown nsDateFormatSelector");
+ res = NS_ERROR_FAILURE;
+ dateStyle = kCFDateFormatterNoStyle;
+ }
+
+ // Get the time style for the formatter:
+ CFDateFormatterStyle timeStyle;
+ switch (timeFormatSelector) {
+ case kTimeFormatSeconds:
+ case kTimeFormatSecondsForce24Hour: // 24 hour part fixed below
+ timeStyle = kCFDateFormatterMediumStyle;
+ break;
+ case kTimeFormatNoSeconds:
+ case kTimeFormatNoSecondsForce24Hour: // 24 hour part fixed below
+ timeStyle = kCFDateFormatterShortStyle;
+ break;
+ case kTimeFormatNone:
+ timeStyle = kCFDateFormatterNoStyle;
+ break;
+ default:
+ NS_ERROR("Unknown nsTimeFormatSelector");
+ res = NS_ERROR_FAILURE;
+ timeStyle = kCFDateFormatterNoStyle;
+ }
+
+ // Create the formatter and fix up its formatting as necessary:
+ CFDateFormatterRef formatter =
+ CFDateFormatterCreate(nullptr, formatterLocale, dateStyle, timeStyle);
+
+ CFRelease(formatterLocale);
+
+ if (dateFormatSelector == kDateFormatYearMonth ||
+ dateFormatSelector == kDateFormatWeekday) {
+ CFStringRef dateFormat =
+ dateFormatSelector == kDateFormatYearMonth ? CFSTR("yyyy/MM ") : CFSTR("EEE ");
+
+ CFStringRef oldFormat = CFDateFormatterGetFormat(formatter);
+ CFMutableStringRef newFormat = CFStringCreateMutableCopy(nullptr, 0, oldFormat);
+ CFStringInsert(newFormat, 0, dateFormat);
+ CFDateFormatterSetFormat(formatter, newFormat);
+ CFRelease(newFormat); // note we don't own oldFormat
+ }
+
+ if (timeFormatSelector == kTimeFormatSecondsForce24Hour ||
+ timeFormatSelector == kTimeFormatNoSecondsForce24Hour) {
+ // Replace "h" with "H", and remove "a":
+ CFStringRef oldFormat = CFDateFormatterGetFormat(formatter);
+ CFMutableStringRef newFormat = CFStringCreateMutableCopy(nullptr, 0, oldFormat);
+ CFIndex replaceCount = CFStringFindAndReplace(newFormat,
+ CFSTR("h"), CFSTR("H"),
+ CFRangeMake(0, CFStringGetLength(newFormat)),
+ 0);
+ NS_ASSERTION(replaceCount <= 2, "Unexpected number of \"h\" occurrences");
+ replaceCount = CFStringFindAndReplace(newFormat,
+ CFSTR("a"), CFSTR(""),
+ CFRangeMake(0, CFStringGetLength(newFormat)),
+ 0);
+ NS_ASSERTION(replaceCount <= 1, "Unexpected number of \"a\" occurrences");
+ CFDateFormatterSetFormat(formatter, newFormat);
+ CFRelease(newFormat); // note we don't own oldFormat
+ }
+
+ // Now get the formatted date:
+ CFGregorianDate date;
+ date.second = tmTime->tm_sec;
+ date.minute = tmTime->tm_min;
+ date.hour = tmTime->tm_hour;
+ date.day = tmTime->tm_mday; // Mac is 1-based, tm is 1-based
+ date.month = tmTime->tm_mon + 1; // Mac is 1-based, tm is 0-based
+ date.year = tmTime->tm_year + 1900;
+
+ CFTimeZoneRef timeZone = CFTimeZoneCopySystem(); // tmTime is in local time
+ CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime(date, timeZone);
+ CFRelease(timeZone);
+
+ CFStringRef formattedDate = CFDateFormatterCreateStringWithAbsoluteTime(nullptr,
+ formatter,
+ absTime);
+
+ CFIndex stringLen = CFStringGetLength(formattedDate);
+
+ AutoTArray<UniChar, 256> stringBuffer;
+ stringBuffer.SetLength(stringLen + 1);
+ CFStringGetCharacters(formattedDate, CFRangeMake(0, stringLen), stringBuffer.Elements());
+ stringOut.Assign(reinterpret_cast<char16_t*>(stringBuffer.Elements()), stringLen);
+
+ CFRelease(formattedDate);
+ CFRelease(formatter);
+
+ return res;
+}
+
+// performs a locale sensitive date formatting operation on the PRTime parameter
+nsresult nsDateTimeFormatMac::FormatPRTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const PRTime prTime,
+ nsAString& stringOut)
+{
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(prTime, PR_LocalTimeParameters, &explodedTime);
+
+ return FormatPRExplodedTime(locale, dateFormatSelector, timeFormatSelector, &explodedTime, stringOut);
+}
+
+// performs a locale sensitive date formatting operation on the PRExplodedTime parameter
+nsresult nsDateTimeFormatMac::FormatPRExplodedTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const PRExplodedTime* explodedTime,
+ nsAString& stringOut)
+{
+ struct tm tmTime;
+ memset( &tmTime, 0, sizeof(tmTime) );
+
+ tmTime.tm_yday = explodedTime->tm_yday;
+ tmTime.tm_wday = explodedTime->tm_wday;
+ tmTime.tm_year = explodedTime->tm_year;
+ tmTime.tm_year -= 1900;
+ tmTime.tm_mon = explodedTime->tm_month;
+ tmTime.tm_mday = explodedTime->tm_mday;
+ tmTime.tm_hour = explodedTime->tm_hour;
+ tmTime.tm_min = explodedTime->tm_min;
+ tmTime.tm_sec = explodedTime->tm_sec;
+
+ return FormatTMTime(locale, dateFormatSelector, timeFormatSelector, &tmTime, stringOut);
+}
+
diff --git a/intl/locale/mac/nsDateTimeFormatMac.h b/intl/locale/mac/nsDateTimeFormatMac.h
new file mode 100644
index 0000000000..dfdf703780
--- /dev/null
+++ b/intl/locale/mac/nsDateTimeFormatMac.h
@@ -0,0 +1,61 @@
+
+/* -*- Mode: C++; tab-width: 2; 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/. */
+#ifndef nsDateTimeFormatMac_h__
+#define nsDateTimeFormatMac_h__
+
+
+#include "nsCOMPtr.h"
+#include "nsIDateTimeFormat.h"
+
+
+class nsDateTimeFormatMac : public nsIDateTimeFormat {
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // performs a locale sensitive date formatting operation on the time_t parameter
+ NS_IMETHOD FormatTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const time_t timetTime,
+ nsAString& stringOut) override;
+
+ // performs a locale sensitive date formatting operation on the struct tm parameter
+ NS_IMETHOD FormatTMTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const struct tm* tmTime,
+ nsAString& stringOut) override;
+ // performs a locale sensitive date formatting operation on the PRTime parameter
+ NS_IMETHOD FormatPRTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const PRTime prTime,
+ nsAString& stringOut) override;
+
+ // performs a locale sensitive date formatting operation on the PRExplodedTime parameter
+ NS_IMETHOD FormatPRExplodedTime(nsILocale* locale,
+ const nsDateFormatSelector dateFormatSelector,
+ const nsTimeFormatSelector timeFormatSelector,
+ const PRExplodedTime* explodedTime,
+ nsAString& stringOut) override;
+
+ nsDateTimeFormatMac() {}
+
+protected:
+ virtual ~nsDateTimeFormatMac() {}
+
+private:
+ // init this interface to a specified locale
+ NS_IMETHOD Initialize(nsILocale* locale);
+
+ nsString mLocale;
+ nsString mAppLocale;
+ bool mUseDefaultLocale;
+};
+
+#endif /* nsDateTimeFormatMac_h__ */
diff --git a/intl/locale/mac/nsMacCharset.cpp b/intl/locale/mac/nsMacCharset.cpp
new file mode 100644
index 0000000000..956560fba8
--- /dev/null
+++ b/intl/locale/mac/nsMacCharset.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; 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 <Carbon/Carbon.h>
+#include "nsIPlatformCharset.h"
+#include "nsCOMPtr.h"
+#include "nsIServiceManager.h"
+#include "nsReadableUtils.h"
+#include "nsPlatformCharset.h"
+#include "nsEncoderDecoderUtils.h"
+
+NS_IMPL_ISUPPORTS(nsPlatformCharset, nsIPlatformCharset)
+
+nsPlatformCharset::nsPlatformCharset()
+{
+}
+nsPlatformCharset::~nsPlatformCharset()
+{
+}
+
+NS_IMETHODIMP
+nsPlatformCharset::GetCharset(nsPlatformCharsetSel selector, nsACString& oResult)
+{
+ oResult.AssignLiteral("UTF-8");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPlatformCharset::GetDefaultCharsetForLocale(const nsAString& localeName, nsACString &oResult)
+{
+ oResult.AssignLiteral("UTF-8");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPlatformCharset::Init()
+{
+ return NS_OK;
+}
+
+nsresult
+nsPlatformCharset::MapToCharset(nsAString& inANSICodePage, nsACString& outCharset)
+{
+ return NS_OK;
+}
+
+nsresult
+nsPlatformCharset::InitGetCharset(nsACString &oString)
+{
+ return NS_OK;
+}
+
+nsresult
+nsPlatformCharset::VerifyCharset(nsCString &aCharset)
+{
+ return NS_OK;
+}
diff --git a/intl/locale/moz.build b/intl/locale/moz.build
index 4b3b3c0b88..94a30873ba 100644
--- a/intl/locale/moz.build
+++ b/intl/locale/moz.build
@@ -9,6 +9,8 @@ toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
if toolkit == 'windows':
DIRS += ['windows']
+elif toolkit == 'cocoa':
+ DIRS += ['mac']
else:
DIRS += ['unix']
diff --git a/intl/locale/nsIDateTimeFormat.cpp b/intl/locale/nsIDateTimeFormat.cpp
index 1290fdc3ee..263b3abb4e 100644
--- a/intl/locale/nsIDateTimeFormat.cpp
+++ b/intl/locale/nsIDateTimeFormat.cpp
@@ -6,7 +6,9 @@
#include "nsIDateTimeFormat.h"
#include "mozilla/RefPtr.h"
-#if defined(XP_UNIX)
+#if defined(XP_MACOSX)
+#define USE_MAC_LOCALE
+#elif defined(XP_UNIX)
#define USE_UNIX_LOCALE
#endif
@@ -16,6 +18,9 @@
#ifdef USE_UNIX_LOCALE
#include "unix/nsDateTimeFormatUnix.h"
#endif
+#ifdef USE_MAC_LOCALE
+#include "mac/nsDateTimeFormatMac.h"
+#endif
using mozilla::MakeAndAddRef;
diff --git a/intl/locale/nsLocaleConstructors.h b/intl/locale/nsLocaleConstructors.h
index 212f70ea53..2d2e579740 100644
--- a/intl/locale/nsLocaleConstructors.h
+++ b/intl/locale/nsLocaleConstructors.h
@@ -15,7 +15,11 @@
#include "nsLanguageAtomService.h"
#include "nsPlatformCharset.h"
-#if defined(XP_UNIX)
+#if defined(XP_MACOSX)
+#define USE_MAC_LOCALE
+#endif
+
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
#define USE_UNIX_LOCALE
#endif
@@ -24,6 +28,11 @@
#include "windows/nsDateTimeFormatWin.h"
#endif
+#ifdef USE_MAC_LOCALE
+#include "mac/nsCollationMacUC.h"
+#include "mac/nsDateTimeFormatMac.h"
+#endif
+
#ifdef USE_UNIX_LOCALE
#include "unix/nsCollationUnix.h"
#include "unix/nsDateTimeFormatUnix.h"
diff --git a/intl/locale/nsLocaleService.cpp b/intl/locale/nsLocaleService.cpp
index 5984d12274..d81fb50c2e 100644
--- a/intl/locale/nsLocaleService.cpp
+++ b/intl/locale/nsLocaleService.cpp
@@ -17,6 +17,8 @@
#if defined(XP_WIN)
# include "nsWin32Locale.h"
+#elif defined(XP_MACOSX)
+# include <Carbon/Carbon.h>
#elif defined(XP_UNIX)
# include <locale.h>
# include <stdlib.h>
@@ -39,7 +41,7 @@ const char* LocaleList[LocaleListLength] =
#define NSILOCALE_MAX_ACCEPT_LANGUAGE 16
#define NSILOCALE_MAX_ACCEPT_LENGTH 18
-#if defined(XP_UNIX)
+#if (defined(XP_UNIX) && !defined(XP_MACOSX))
static int posix_locale_category[LocaleListLength] =
{
LC_COLLATE,
@@ -111,7 +113,7 @@ nsLocaleService::nsLocaleService(void)
rv = NewLocale(xpLocale, getter_AddRefs(mApplicationLocale));
NS_ENSURE_SUCCESS_VOID(rv);
#endif
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
RefPtr<nsLocale> resultLocale(new nsLocale());
NS_ENSURE_TRUE_VOID(resultLocale);
@@ -152,7 +154,36 @@ nsLocaleService::nsLocaleService(void)
}
mSystemLocale = do_QueryInterface(resultLocale);
mApplicationLocale = do_QueryInterface(resultLocale);
+
#endif // XP_UNIX
+
+#ifdef XP_MACOSX
+ // Get string representation of user's current locale
+ CFLocaleRef userLocaleRef = ::CFLocaleCopyCurrent();
+ CFStringRef userLocaleStr = ::CFLocaleGetIdentifier(userLocaleRef);
+ ::CFRetain(userLocaleStr);
+
+ AutoTArray<UniChar, 32> buffer;
+ int size = ::CFStringGetLength(userLocaleStr);
+ buffer.SetLength(size + 1);
+ CFRange range = ::CFRangeMake(0, size);
+ ::CFStringGetCharacters(userLocaleStr, range, buffer.Elements());
+ buffer[size] = 0;
+
+ // Convert the locale string to the format that Mozilla expects
+ nsAutoString xpLocale(reinterpret_cast<char16_t*>(buffer.Elements()));
+ xpLocale.ReplaceChar('_', '-');
+
+ nsresult rv = NewLocale(xpLocale, getter_AddRefs(mSystemLocale));
+ if (NS_SUCCEEDED(rv)) {
+ mApplicationLocale = mSystemLocale;
+ }
+
+ ::CFRelease(userLocaleStr);
+ ::CFRelease(userLocaleRef);
+
+ NS_ASSERTION(mApplicationLocale, "Failed to create locale objects");
+#endif // XP_MACOSX
}
nsLocaleService::~nsLocaleService(void)
@@ -175,7 +206,7 @@ nsLocaleService::NewLocale(const nsAString &aLocale, nsILocale **_retval)
NS_ConvertASCIItoUTF16 category(LocaleList[i]);
result = resultLocale->AddCategory(category, aLocale);
if (NS_FAILED(result)) return result;
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
category.AppendLiteral("##PLATFORM");
result = resultLocale->AddCategory(category, aLocale);
if (NS_FAILED(result)) return result;
diff --git a/intl/lwbrk/moz.build b/intl/lwbrk/moz.build
index dc7c2df1a2..da701be5e5 100644
--- a/intl/lwbrk/moz.build
+++ b/intl/lwbrk/moz.build
@@ -32,6 +32,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
SOURCES += [
'nsUniscribeBreaker.cpp',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'nsCarbonBreaker.cpp',
+ ]
else:
SOURCES += [
'nsRuleBreaker.cpp',
diff --git a/intl/lwbrk/nsCarbonBreaker.cpp b/intl/lwbrk/nsCarbonBreaker.cpp
new file mode 100644
index 0000000000..1b37bc1298
--- /dev/null
+++ b/intl/lwbrk/nsCarbonBreaker.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; 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 <CoreFoundation/CoreFoundation.h>
+#include <stdint.h>
+#include "nsDebug.h"
+#include "nscore.h"
+
+void
+NS_GetComplexLineBreaks(const char16_t* aText, uint32_t aLength,
+ uint8_t* aBreakBefore)
+{
+ NS_ASSERTION(aText, "aText shouldn't be null");
+
+ memset(aBreakBefore, 0, aLength * sizeof(uint8_t));
+
+ CFStringRef str = ::CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(aText), aLength, kCFAllocatorNull);
+ if (!str) {
+ return;
+ }
+
+ CFStringTokenizerRef st = ::CFStringTokenizerCreate(kCFAllocatorDefault, str,
+ ::CFRangeMake(0, aLength),
+ kCFStringTokenizerUnitLineBreak,
+ nullptr);
+ if (!st) {
+ ::CFRelease(str);
+ return;
+ }
+
+ CFStringTokenizerTokenType tt = ::CFStringTokenizerAdvanceToNextToken(st);
+ while (tt != kCFStringTokenizerTokenNone) {
+ CFRange r = ::CFStringTokenizerGetCurrentTokenRange(st);
+ if (r.location != 0) { // Ignore leading edge
+ aBreakBefore[r.location] = true;
+ }
+ tt = CFStringTokenizerAdvanceToNextToken(st);
+ }
+
+ ::CFRelease(st);
+ ::CFRelease(str);
+}
diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h
index 561c3c6d2d..529fde6f03 100644
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -101,6 +101,12 @@ public:
return mProcessType;
}
+#ifdef XP_MACOSX
+ task_t GetChildTask() {
+ return mChildTask;
+ }
+#endif
+
/**
* Must run on the IO thread. Cause the OS process to exit and
* ensure its OS resources are cleaned up.
diff --git a/ipc/glue/SharedMemoryBasic.h b/ipc/glue/SharedMemoryBasic.h
index 9b9668f689..d3c3e62860 100644
--- a/ipc/glue/SharedMemoryBasic.h
+++ b/ipc/glue/SharedMemoryBasic.h
@@ -6,6 +6,10 @@
#ifndef mozilla_ipc_SharedMemoryBasic_h
#define mozilla_ipc_SharedMemoryBasic_h
+#ifdef XP_DARWIN
+# include "mozilla/ipc/SharedMemoryBasic_mach.h"
+#else
# include "mozilla/ipc/SharedMemoryBasic_chromium.h"
+#endif
#endif // ifndef mozilla_ipc_SharedMemoryBasic_h
diff --git a/ipc/glue/SharedMemoryBasic_mach.h b/ipc/glue/SharedMemoryBasic_mach.h
new file mode 100644
index 0000000000..2c54652413
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_mach.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_ipc_SharedMemoryBasic_mach_h
+#define mozilla_ipc_SharedMemoryBasic_mach_h
+
+#include "base/file_descriptor_posix.h"
+#include "base/process.h"
+
+#include "SharedMemory.h"
+#include <mach/port.h>
+
+//
+// This is a low-level wrapper around platform shared memory. Don't
+// use it directly; use Shmem allocated through IPDL interfaces.
+//
+
+class MachPortSender;
+class ReceivePort;
+
+namespace mozilla {
+namespace ipc {
+
+class SharedMemoryBasic final : public SharedMemoryCommon<mach_port_t>
+{
+public:
+ static void SetupMachMemory(pid_t pid,
+ ReceivePort* listen_port,
+ MachPortSender* listen_port_ack,
+ MachPortSender* send_port,
+ ReceivePort* send_port_ack,
+ bool pidIsParent);
+
+ static void CleanupForPid(pid_t pid);
+
+ static void Shutdown();
+
+ SharedMemoryBasic();
+
+ virtual bool SetHandle(const Handle& aHandle) override;
+
+ virtual bool Create(size_t aNbytes) override;
+
+ virtual bool Map(size_t nBytes) override;
+
+ virtual void CloseHandle() override;
+
+ virtual void* memory() const override
+ {
+ return mMemory;
+ }
+
+ virtual SharedMemoryType Type() const override
+ {
+ return TYPE_BASIC;
+ }
+
+ static Handle NULLHandle()
+ {
+ return Handle();
+ }
+
+
+ virtual bool IsHandleValid(const Handle &aHandle) const override;
+
+ virtual bool ShareToProcess(base::ProcessId aProcessId,
+ Handle* aNewHandle) override;
+
+private:
+ ~SharedMemoryBasic();
+
+ void Unmap();
+ mach_port_t mPort;
+ // Pointer to mapped region, null if unmapped.
+ void *mMemory;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // ifndef mozilla_ipc_SharedMemoryBasic_mach_h
diff --git a/ipc/glue/SharedMemoryBasic_mach.mm b/ipc/glue/SharedMemoryBasic_mach.mm
new file mode 100644
index 0000000000..3ee5a854d5
--- /dev/null
+++ b/ipc/glue/SharedMemoryBasic_mach.mm
@@ -0,0 +1,664 @@
+/* -*- Mode: C++; tab-width: 8; 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 <map>
+
+#include <mach/vm_map.h>
+#include <mach/mach_port.h>
+#include <mach/mach_vm.h>
+#include <pthread.h>
+#include <unistd.h>
+#include "SharedMemoryBasic.h"
+#include "chrome/common/mach_ipc_mac.h"
+
+#include "mozilla/StaticMutex.h"
+
+#ifdef DEBUG
+#define LOG_ERROR(str, args...) \
+ PR_BEGIN_MACRO \
+ char *msg = PR_smprintf(str, ## args); \
+ NS_WARNING(msg); \
+ PR_smprintf_free(msg); \
+ PR_END_MACRO
+#else
+#define LOG_ERROR(str, args...) do { /* nothing */ } while(0)
+#endif
+
+#define CHECK_MACH_ERROR(kr, msg) \
+ PR_BEGIN_MACRO \
+ if (kr != KERN_SUCCESS) { \
+ LOG_ERROR("%s %s (%x)\n", msg, mach_error_string(kr), kr); \
+ return false; \
+ } \
+ PR_END_MACRO
+
+/*
+ * This code is responsible for sharing memory between processes. Memory can be
+ * shared between parent and child or between two children. Each memory region is
+ * referenced via a Mach port. Mach ports are also used for messaging when
+ * sharing a memory region.
+ *
+ * When the parent starts a child, it starts a thread whose only purpose is to
+ * communicate with the child about shared memory. Once the child has started,
+ * it starts a similar thread for communicating with the parent. Each side can
+ * communicate with the thread on the other side via Mach ports. When either
+ * side wants to share memory with the other, it sends a Mach message to the
+ * other side. Attached to the message is the port that references the shared
+ * memory region. When the other side receives the message, it automatically
+ * gets access to the region. It sends a reply (also via a Mach port) so that
+ * the originating side can continue.
+ *
+ * The two sides communicate using four ports. Two ports are used when the
+ * parent shares memory with the child. The other two are used when the child
+ * shares memory with the parent. One of these two ports is used for sending the
+ * "share" message and the other is used for the reply.
+ *
+ * If a child wants to share memory with another child, it sends a "GetPorts"
+ * message to the parent. The parent forwards this GetPorts message to the
+ * target child. The message includes some ports so that the children can talk
+ * directly. Both children start up a thread to communicate with the other child,
+ * similar to the way parent and child communicate. In the future, when these
+ * two children want to communicate, they re-use the channels that were created.
+ *
+ * When a child shuts down, the parent notifies all other children. Those
+ * children then have the opportunity to shut down any threads they might have
+ * been using to communicate directly with that child.
+ */
+
+namespace mozilla {
+namespace ipc {
+
+struct MemoryPorts {
+ MachPortSender* mSender;
+ ReceivePort* mReceiver;
+
+ MemoryPorts() {}
+ MemoryPorts(MachPortSender* sender, ReceivePort* receiver)
+ : mSender(sender), mReceiver(receiver) {}
+};
+
+// Protects gMemoryCommPorts and gThreads.
+static StaticMutex gMutex;
+
+static std::map<pid_t, MemoryPorts> gMemoryCommPorts;
+
+enum {
+ kGetPortsMsg = 1,
+ kSharePortsMsg,
+ kReturnIdMsg,
+ kReturnPortsMsg,
+ kShutdownMsg,
+ kCleanupMsg,
+};
+
+const int kTimeout = 1000;
+const int kLongTimeout = 60 * kTimeout;
+
+pid_t gParentPid = 0;
+
+struct PIDPair {
+ pid_t mRequester;
+ pid_t mRequested;
+
+ PIDPair(pid_t requester, pid_t requested)
+ : mRequester(requester), mRequested(requested) {}
+};
+
+struct ListeningThread {
+ pthread_t mThread;
+ MemoryPorts* mPorts;
+
+ ListeningThread() {}
+ ListeningThread(pthread_t thread, MemoryPorts* ports)
+ : mThread(thread), mPorts(ports) {}
+};
+
+struct SharePortsReply {
+ uint64_t serial;
+ mach_port_t port;
+};
+
+std::map<pid_t, ListeningThread> gThreads;
+
+static void *
+PortServerThread(void *argument);
+
+
+static void
+SetupMachMemory(pid_t pid,
+ ReceivePort* listen_port,
+ MachPortSender* listen_port_ack,
+ MachPortSender* send_port,
+ ReceivePort* send_port_ack,
+ bool pidIsParent)
+{
+ if (pidIsParent) {
+ gParentPid = pid;
+ }
+ MemoryPorts* listen_ports = new MemoryPorts(listen_port_ack, listen_port);
+ pthread_t thread;
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ int err = pthread_create(&thread, &attr, PortServerThread, listen_ports);
+ if (err) {
+ LOG_ERROR("pthread_create failed with %x\n", err);
+ return;
+ }
+
+ gMutex.AssertCurrentThreadOwns();
+ gThreads[pid] = ListeningThread(thread, listen_ports);
+ gMemoryCommPorts[pid] = MemoryPorts(send_port, send_port_ack);
+}
+
+// Send two communication ports to another process along with the pid of the process that is
+// listening on them.
+bool
+SendPortsMessage(MachPortSender* sender,
+ mach_port_t ports_in_receiver,
+ mach_port_t ports_out_receiver,
+ PIDPair pid_pair)
+{
+ MachSendMessage getPortsMsg(kGetPortsMsg);
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(ports_in_receiver))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(ports_out_receiver))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+
+ getPortsMsg.SetData(&pid_pair, sizeof(PIDPair));
+ kern_return_t err = sender->SendMessage(getPortsMsg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("Error sending get ports message %s (%x)\n", mach_error_string(err), err);
+ return false;
+ }
+ return true;
+}
+
+// Receive two communication ports from another process
+bool
+RecvPortsMessage(ReceivePort* receiver, mach_port_t* ports_in_sender, mach_port_t* ports_out_sender)
+{
+ MachReceiveMessage rcvPortsMsg;
+ kern_return_t err = receiver->WaitForMessage(&rcvPortsMsg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("Error receiving get ports message %s (%x)\n", mach_error_string(err), err);
+ }
+ if (rcvPortsMsg.GetTranslatedPort(0) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(0) failed");
+ return false;
+ }
+ *ports_in_sender = rcvPortsMsg.GetTranslatedPort(0);
+
+ if (rcvPortsMsg.GetTranslatedPort(1) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(1) failed");
+ return false;
+ }
+ *ports_out_sender = rcvPortsMsg.GetTranslatedPort(1);
+ return true;
+}
+
+// Send two communication ports to another process and receive two back
+bool
+RequestPorts(const MemoryPorts& request_ports,
+ mach_port_t ports_in_receiver,
+ mach_port_t* ports_in_sender,
+ mach_port_t* ports_out_sender,
+ mach_port_t ports_out_receiver,
+ PIDPair pid_pair)
+{
+ if (!SendPortsMessage(request_ports.mSender, ports_in_receiver, ports_out_receiver, pid_pair)) {
+ return false;
+ }
+ return RecvPortsMessage(request_ports.mReceiver, ports_in_sender, ports_out_sender);
+}
+
+MemoryPorts*
+GetMemoryPortsForPid(pid_t pid)
+{
+ gMutex.AssertCurrentThreadOwns();
+
+ if (gMemoryCommPorts.find(pid) == gMemoryCommPorts.end()) {
+ // We don't have the ports open to communicate with that pid, so we're going to
+ // ask our parent process over IPC to set them up for us.
+ if (gParentPid == 0) {
+ // If we're the top level parent process, we have no parent to ask.
+ LOG_ERROR("request for ports for pid %d, but we're the chrome process\n", pid);
+ return nullptr;
+ }
+ const MemoryPorts& parent = gMemoryCommPorts[gParentPid];
+
+ // Create two receiving ports in this process to send to the parent. One will be used for
+ // for listening for incoming memory to be shared, the other for getting the Handle of
+ // memory we share to the other process.
+ ReceivePort* ports_in_receiver = new ReceivePort();
+ ReceivePort* ports_out_receiver = new ReceivePort();
+ mach_port_t raw_ports_in_sender, raw_ports_out_sender;
+ if (!RequestPorts(parent,
+ ports_in_receiver->GetPort(),
+ &raw_ports_in_sender,
+ &raw_ports_out_sender,
+ ports_out_receiver->GetPort(),
+ PIDPair(getpid(), pid))) {
+ LOG_ERROR("failed to request ports\n");
+ return nullptr;
+ }
+ // Our parent process sent us two ports, one is for sending new memory to, the other
+ // is for replying with the Handle when we receive new memory.
+ MachPortSender* ports_in_sender = new MachPortSender(raw_ports_in_sender);
+ MachPortSender* ports_out_sender = new MachPortSender(raw_ports_out_sender);
+ SetupMachMemory(pid,
+ ports_in_receiver,
+ ports_in_sender,
+ ports_out_sender,
+ ports_out_receiver,
+ false);
+ MOZ_ASSERT(gMemoryCommPorts.find(pid) != gMemoryCommPorts.end());
+ }
+ return &gMemoryCommPorts.at(pid);
+}
+
+// We just received a port representing a region of shared memory, reply to
+// the process that set it with the mach_port_t that represents it in this process.
+// That will be the Handle to be shared over normal IPC
+void
+HandleSharePortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports)
+{
+ mach_port_t port = rmsg->GetTranslatedPort(0);
+ uint64_t* serial = reinterpret_cast<uint64_t*>(rmsg->GetData());
+ MachSendMessage msg(kReturnIdMsg);
+ // Construct the reply message, echoing the serial, and adding the port
+ SharePortsReply replydata;
+ replydata.port = port;
+ replydata.serial = *serial;
+ msg.SetData(&replydata, sizeof(SharePortsReply));
+ kern_return_t err = ports->mSender->SendMessage(msg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("SendMessage failed 0x%x %s\n", err, mach_error_string(err));
+ }
+}
+
+// We were asked by another process to get communications ports to some process. Return
+// those ports via an IPC message.
+bool
+SendReturnPortsMsg(MachPortSender* sender,
+ mach_port_t raw_ports_in_sender,
+ mach_port_t raw_ports_out_sender)
+{
+ MachSendMessage getPortsMsg(kReturnPortsMsg);
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(raw_ports_in_sender))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+
+ if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(raw_ports_out_sender))) {
+ LOG_ERROR("Adding descriptor to message failed");
+ return false;
+ }
+ kern_return_t err = sender->SendMessage(getPortsMsg, kTimeout);
+ if (KERN_SUCCESS != err) {
+ LOG_ERROR("Error sending get ports message %s (%x)\n", mach_error_string(err), err);
+ return false;
+ }
+ return true;
+}
+
+// We were asked for communcations ports to a process that isn't us. Assuming that process
+// is one of our children, forward that request on.
+void
+ForwardGetPortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports, PIDPair* pid_pair)
+{
+ if (rmsg->GetTranslatedPort(0) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(0) failed");
+ return;
+ }
+ if (rmsg->GetTranslatedPort(1) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(1) failed");
+ return;
+ }
+ mach_port_t raw_ports_in_sender, raw_ports_out_sender;
+ MemoryPorts* requestedPorts = GetMemoryPortsForPid(pid_pair->mRequested);
+ if (!requestedPorts) {
+ LOG_ERROR("failed to find port for process\n");
+ return;
+ }
+ if (!RequestPorts(*requestedPorts, rmsg->GetTranslatedPort(0), &raw_ports_in_sender,
+ &raw_ports_out_sender, rmsg->GetTranslatedPort(1), *pid_pair)) {
+ LOG_ERROR("failed to request ports\n");
+ return;
+ }
+ SendReturnPortsMsg(ports->mSender, raw_ports_in_sender, raw_ports_out_sender);
+}
+
+// We receieved a message asking us to get communications ports for another process
+void
+HandleGetPortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports)
+{
+ PIDPair* pid_pair;
+ if (rmsg->GetDataLength() != sizeof(PIDPair)) {
+ LOG_ERROR("Improperly formatted message\n");
+ return;
+ }
+ pid_pair = reinterpret_cast<PIDPair*>(rmsg->GetData());
+ if (pid_pair->mRequested != getpid()) {
+ // This request is for ports to a process that isn't us, forward it to that process
+ ForwardGetPortsMessage(rmsg, ports, pid_pair);
+ } else {
+ if (rmsg->GetTranslatedPort(0) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(0) failed");
+ return;
+ }
+
+ if (rmsg->GetTranslatedPort(1) == MACH_PORT_NULL) {
+ LOG_ERROR("GetTranslatedPort(1) failed");
+ return;
+ }
+
+ MachPortSender* ports_in_sender = new MachPortSender(rmsg->GetTranslatedPort(0));
+ MachPortSender* ports_out_sender = new MachPortSender(rmsg->GetTranslatedPort(1));
+
+ ReceivePort* ports_in_receiver = new ReceivePort();
+ ReceivePort* ports_out_receiver = new ReceivePort();
+ if (SendReturnPortsMsg(ports->mSender, ports_in_receiver->GetPort(), ports_out_receiver->GetPort())) {
+ SetupMachMemory(pid_pair->mRequester,
+ ports_out_receiver,
+ ports_out_sender,
+ ports_in_sender,
+ ports_in_receiver,
+ false);
+ }
+ }
+}
+
+static void *
+PortServerThread(void *argument)
+{
+ MemoryPorts* ports = static_cast<MemoryPorts*>(argument);
+ MachReceiveMessage child_message;
+ while (true) {
+ MachReceiveMessage rmsg;
+ kern_return_t err = ports->mReceiver->WaitForMessage(&rmsg, MACH_MSG_TIMEOUT_NONE);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("Wait for message failed 0x%x %s\n", err, mach_error_string(err));
+ continue;
+ }
+ if (rmsg.GetMessageID() == kShutdownMsg) {
+ delete ports->mSender;
+ delete ports->mReceiver;
+ delete ports;
+ return nullptr;
+ }
+ StaticMutexAutoLock smal(gMutex);
+ switch (rmsg.GetMessageID()) {
+ case kSharePortsMsg:
+ HandleSharePortsMessage(&rmsg, ports);
+ break;
+ case kGetPortsMsg:
+ HandleGetPortsMessage(&rmsg, ports);
+ break;
+ case kCleanupMsg:
+ if (gParentPid == 0) {
+ LOG_ERROR("Cleanup message not valid for parent process");
+ continue;
+ }
+
+ pid_t* pid;
+ if (rmsg.GetDataLength() != sizeof(pid_t)) {
+ LOG_ERROR("Improperly formatted message\n");
+ continue;
+ }
+ pid = reinterpret_cast<pid_t*>(rmsg.GetData());
+ SharedMemoryBasic::CleanupForPid(*pid);
+ break;
+ default:
+ LOG_ERROR("Unknown message\n");
+ }
+ }
+}
+
+void
+SharedMemoryBasic::SetupMachMemory(pid_t pid,
+ ReceivePort* listen_port,
+ MachPortSender* listen_port_ack,
+ MachPortSender* send_port,
+ ReceivePort* send_port_ack,
+ bool pidIsParent)
+{
+ StaticMutexAutoLock smal(gMutex);
+ mozilla::ipc::SetupMachMemory(pid, listen_port, listen_port_ack, send_port, send_port_ack, pidIsParent);
+}
+
+void
+SharedMemoryBasic::Shutdown()
+{
+ StaticMutexAutoLock smal(gMutex);
+
+ for (auto it = gThreads.begin(); it != gThreads.end(); ++it) {
+ MachSendMessage shutdownMsg(kShutdownMsg);
+ it->second.mPorts->mReceiver->SendMessageToSelf(shutdownMsg, kTimeout);
+ }
+ gThreads.clear();
+
+ for (auto it = gMemoryCommPorts.begin(); it != gMemoryCommPorts.end(); ++it) {
+ delete it->second.mSender;
+ delete it->second.mReceiver;
+ }
+ gMemoryCommPorts.clear();
+}
+
+void
+SharedMemoryBasic::CleanupForPid(pid_t pid)
+{
+ if (gThreads.find(pid) == gThreads.end()) {
+ return;
+ }
+ const ListeningThread& listeningThread = gThreads[pid];
+ MachSendMessage shutdownMsg(kShutdownMsg);
+ kern_return_t ret = listeningThread.mPorts->mReceiver->SendMessageToSelf(shutdownMsg, kTimeout);
+ if (ret != KERN_SUCCESS) {
+ LOG_ERROR("sending shutdown msg failed %s %x\n", mach_error_string(ret), ret);
+ }
+ gThreads.erase(pid);
+
+ if (gParentPid == 0) {
+ // We're the parent. Broadcast the cleanup message to everyone else.
+ for (auto it = gMemoryCommPorts.begin(); it != gMemoryCommPorts.end(); ++it) {
+ MachSendMessage msg(kCleanupMsg);
+ msg.SetData(&pid, sizeof(pid));
+ // We don't really care if this fails, we could be trying to send to an already shut down proc
+ it->second.mSender->SendMessage(msg, kTimeout);
+ }
+ }
+
+ MemoryPorts& ports = gMemoryCommPorts[pid];
+ delete ports.mSender;
+ delete ports.mReceiver;
+ gMemoryCommPorts.erase(pid);
+}
+
+SharedMemoryBasic::SharedMemoryBasic()
+ : mPort(MACH_PORT_NULL)
+ , mMemory(nullptr)
+{
+}
+
+SharedMemoryBasic::~SharedMemoryBasic()
+{
+ Unmap();
+ CloseHandle();
+}
+
+bool
+SharedMemoryBasic::SetHandle(const Handle& aHandle)
+{
+ MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized");
+
+ mPort = aHandle;
+ return true;
+}
+
+static inline void*
+toPointer(mach_vm_address_t address)
+{
+ return reinterpret_cast<void*>(static_cast<uintptr_t>(address));
+}
+
+static inline mach_vm_address_t
+toVMAddress(void* pointer)
+{
+ return static_cast<mach_vm_address_t>(reinterpret_cast<uintptr_t>(pointer));
+}
+
+bool
+SharedMemoryBasic::Create(size_t size)
+{
+ mach_vm_address_t address;
+
+ kern_return_t kr = mach_vm_allocate(mach_task_self(), &address, round_page(size), VM_FLAGS_ANYWHERE);
+ if (kr != KERN_SUCCESS) {
+ LOG_ERROR("Failed to allocate mach_vm_allocate shared memory (%zu bytes). %s (%x)\n",
+ size, mach_error_string(kr), kr);
+ return false;
+ }
+
+ memory_object_size_t memoryObjectSize = round_page(size);
+
+ kr = mach_make_memory_entry_64(mach_task_self(),
+ &memoryObjectSize,
+ address,
+ VM_PROT_DEFAULT,
+ &mPort,
+ MACH_PORT_NULL);
+ if (kr != KERN_SUCCESS) {
+ LOG_ERROR("Failed to make memory entry (%zu bytes). %s (%x)\n",
+ size, mach_error_string(kr), kr);
+ return false;
+ }
+
+ mMemory = toPointer(address);
+ Mapped(size);
+ return true;
+}
+
+bool
+SharedMemoryBasic::Map(size_t size)
+{
+ if (mMemory) {
+ return true;
+ }
+
+ if (MACH_PORT_NULL == mPort) {
+ return false;
+ }
+
+ kern_return_t kr;
+ mach_vm_address_t address = 0;
+
+ vm_prot_t vmProtection = VM_PROT_READ | VM_PROT_WRITE;
+
+ kr = mach_vm_map(mach_task_self(), &address, round_page(size), 0, VM_FLAGS_ANYWHERE,
+ mPort, 0, false, vmProtection, vmProtection, VM_INHERIT_NONE);
+ if (kr != KERN_SUCCESS) {
+ LOG_ERROR("Failed to map shared memory (%zu bytes) into %x, port %x. %s (%x)\n",
+ size, mach_task_self(), mPort, mach_error_string(kr), kr);
+ return false;
+ }
+
+ mMemory = toPointer(address);
+ Mapped(size);
+ return true;
+}
+
+bool
+SharedMemoryBasic::ShareToProcess(base::ProcessId pid,
+ Handle* aNewHandle)
+{
+ if (pid == getpid()) {
+ *aNewHandle = mPort;
+ return mach_port_mod_refs(mach_task_self(), *aNewHandle, MACH_PORT_RIGHT_SEND, 1) == KERN_SUCCESS;
+ }
+ StaticMutexAutoLock smal(gMutex);
+
+ // Serially number the messages, to check whether
+ // the reply we get was meant for us.
+ static uint64_t serial = 0;
+ uint64_t my_serial = serial;
+ serial++;
+
+ MemoryPorts* ports = GetMemoryPortsForPid(pid);
+ if (!ports) {
+ LOG_ERROR("Unable to get ports for process.\n");
+ return false;
+ }
+ MachSendMessage smsg(kSharePortsMsg);
+ smsg.AddDescriptor(MachMsgPortDescriptor(mPort, MACH_MSG_TYPE_COPY_SEND));
+ smsg.SetData(&my_serial, sizeof(uint64_t));
+ kern_return_t err = ports->mSender->SendMessage(smsg, kTimeout);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("sending port failed %s %x\n", mach_error_string(err), err);
+ return false;
+ }
+ MachReceiveMessage msg;
+ err = ports->mReceiver->WaitForMessage(&msg, kTimeout);
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("short timeout didn't get an id %s %x\n", mach_error_string(err), err);
+ err = ports->mReceiver->WaitForMessage(&msg, kLongTimeout);
+
+ if (err != KERN_SUCCESS) {
+ LOG_ERROR("long timeout didn't get an id %s %x\n", mach_error_string(err), err);
+ return false;
+ }
+ }
+ if (msg.GetDataLength() != sizeof(SharePortsReply)) {
+ LOG_ERROR("Improperly formatted reply\n");
+ return false;
+ }
+ SharePortsReply* msg_data = reinterpret_cast<SharePortsReply*>(msg.GetData());
+ mach_port_t id = msg_data->port;
+ uint64_t serial_check = msg_data->serial;
+ if (serial_check != my_serial) {
+ LOG_ERROR("Serials do not match up: %d vs %d", serial_check, my_serial);
+ return false;
+ }
+ *aNewHandle = id;
+ return true;
+}
+
+void
+SharedMemoryBasic::Unmap()
+{
+ if (!mMemory) {
+ return;
+ }
+ vm_address_t address = toVMAddress(mMemory);
+ kern_return_t kr = vm_deallocate(mach_task_self(), address, round_page(mMappedSize));
+ if (kr != KERN_SUCCESS) {
+ LOG_ERROR("Failed to deallocate shared memory. %s (%x)\n", mach_error_string(kr), kr);
+ return;
+ }
+ mMemory = nullptr;
+}
+
+void
+SharedMemoryBasic::CloseHandle()
+{
+ if (mPort != MACH_PORT_NULL) {
+ mach_port_deallocate(mach_task_self(), mPort);
+ mPort = MACH_PORT_NULL;
+ }
+}
+
+bool
+SharedMemoryBasic::IsHandleValid(const Handle& aHandle) const
+{
+ return aHandle > 0;
+}
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/ipc/glue/moz.build b/ipc/glue/moz.build
index d882e3fbd1..426e796668 100644
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -77,7 +77,13 @@ else:
'CrossProcessMutex_unimplemented.cpp',
]
-EXPORTS.mozilla.ipc += ['SharedMemoryBasic_chromium.h']
+if CONFIG['OS_ARCH'] == 'Darwin':
+ EXPORTS.mozilla.ipc += ['SharedMemoryBasic_mach.h']
+ SOURCES += [
+ 'SharedMemoryBasic_mach.mm',
+ ]
+else:
+ EXPORTS.mozilla.ipc += ['SharedMemoryBasic_chromium.h']
if CONFIG['OS_ARCH'] == 'Linux':
UNIFIED_SOURCES += [
diff --git a/js/src/ds/MemoryProtectionExceptionHandler.cpp b/js/src/ds/MemoryProtectionExceptionHandler.cpp
index 881a0e63d5..e617556182 100644
--- a/js/src/ds/MemoryProtectionExceptionHandler.cpp
+++ b/js/src/ds/MemoryProtectionExceptionHandler.cpp
@@ -10,10 +10,13 @@
#if defined(XP_WIN)
# include "jswin.h"
-#elif defined(XP_UNIX)
+#elif defined(XP_UNIX) && !defined(XP_DARWIN)
# include <signal.h>
# include <sys/types.h>
# include <unistd.h>
+#elif defined(XP_DARWIN)
+# include <mach/mach.h>
+# include <unistd.h>
#endif
#include "ds/SplayTree.h"
@@ -201,7 +204,7 @@ MemoryProtectionExceptionHandler::uninstall()
}
}
-#elif defined(XP_UNIX)
+#elif defined(XP_UNIX) && !defined(XP_DARWIN)
static struct sigaction sPrevSEGVHandler = {};
@@ -282,6 +285,465 @@ MemoryProtectionExceptionHandler::uninstall()
}
}
+#elif defined(XP_DARWIN)
+
+/*
+ * The fact that we need to be able to forward to other exception handlers
+ * makes this code much more complicated. The forwarding logic and the
+ * structures required to make it work are heavily based on the code at
+ * www.ravenbrook.com/project/mps/prototype/2013-06-24/machtest/machtest/main.c
+ */
+
+/* -------------------------------------------------------------------------- */
+/* Begin Mach definitions and helper functions */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * These are the message IDs associated with each exception type.
+ * We'll be using sIDRequest64, but we need the others for forwarding.
+ */
+static const mach_msg_id_t sIDRequest32 = 2401;
+static const mach_msg_id_t sIDRequestState32 = 2402;
+static const mach_msg_id_t sIDRequestStateIdentity32 = 2403;
+
+static const mach_msg_id_t sIDRequest64 = 2405;
+static const mach_msg_id_t sIDRequestState64 = 2406;
+static const mach_msg_id_t sIDRequestStateIdentity64 = 2407;
+
+/*
+ * Each message ID has an associated Mach message structure.
+ * We use the preprocessor to make defining them a little less arduous.
+ */
+# define REQUEST_HEADER_FIELDS\
+ mach_msg_header_t header;
+
+# define REQUEST_IDENTITY_FIELDS\
+ mach_msg_body_t msgh_body;\
+ mach_msg_port_descriptor_t thread;\
+ mach_msg_port_descriptor_t task;
+
+# define REQUEST_GENERAL_FIELDS(bits)\
+ NDR_record_t NDR;\
+ exception_type_t exception;\
+ mach_msg_type_number_t code_count;\
+ int##bits##_t code[2];
+
+# define REQUEST_STATE_FIELDS\
+ int flavor;\
+ mach_msg_type_number_t old_state_count;\
+ natural_t old_state[THREAD_STATE_MAX];
+
+# define REQUEST_TRAILER_FIELDS\
+ mach_msg_trailer_t trailer;
+
+# define EXCEPTION_REQUEST(bits)\
+ struct ExceptionRequest##bits\
+ {\
+ REQUEST_HEADER_FIELDS\
+ REQUEST_IDENTITY_FIELDS\
+ REQUEST_GENERAL_FIELDS(bits)\
+ REQUEST_TRAILER_FIELDS\
+ };\
+
+# define EXCEPTION_REQUEST_STATE(bits)\
+ struct ExceptionRequestState##bits\
+ {\
+ REQUEST_HEADER_FIELDS\
+ REQUEST_GENERAL_FIELDS(bits)\
+ REQUEST_STATE_FIELDS\
+ REQUEST_TRAILER_FIELDS\
+ };\
+
+# define EXCEPTION_REQUEST_STATE_IDENTITY(bits)\
+ struct ExceptionRequestStateIdentity##bits\
+ {\
+ REQUEST_HEADER_FIELDS\
+ REQUEST_IDENTITY_FIELDS\
+ REQUEST_GENERAL_FIELDS(bits)\
+ REQUEST_STATE_FIELDS\
+ REQUEST_TRAILER_FIELDS\
+ };\
+
+/* This is needed because not all fields are naturally aligned on 64-bit. */
+# ifdef __MigPackStructs
+# pragma pack(4)
+# endif
+
+EXCEPTION_REQUEST(32)
+EXCEPTION_REQUEST(64)
+EXCEPTION_REQUEST_STATE(32)
+EXCEPTION_REQUEST_STATE(64)
+EXCEPTION_REQUEST_STATE_IDENTITY(32)
+EXCEPTION_REQUEST_STATE_IDENTITY(64)
+
+/* We use this as a common base when forwarding to the previous handler. */
+union ExceptionRequestUnion {
+ mach_msg_header_t header;
+ ExceptionRequest32 r32;
+ ExceptionRequest64 r64;
+ ExceptionRequestState32 rs32;
+ ExceptionRequestState64 rs64;
+ ExceptionRequestStateIdentity32 rsi32;
+ ExceptionRequestStateIdentity64 rsi64;
+};
+
+/* This isn't really a full Mach message, but it's all we need to send. */
+struct ExceptionReply
+{
+ mach_msg_header_t header;
+ NDR_record_t NDR;
+ kern_return_t RetCode;
+};
+
+# ifdef __MigPackStructs
+# pragma pack()
+# endif
+
+# undef EXCEPTION_REQUEST_STATE_IDENTITY
+# undef EXCEPTION_REQUEST_STATE
+# undef EXCEPTION_REQUEST
+# undef REQUEST_STATE_FIELDS
+# undef REQUEST_GENERAL_FIELDS
+# undef REQUEST_IDENTITY_FIELDS
+# undef REQUEST_HEADER_FIELDS
+
+/*
+ * The exception handler we're forwarding to may not have the same behavior
+ * or thread state flavor as what we're using. These macros help populate
+ * the fields of the message we're about to send to the previous handler.
+ */
+# define COPY_REQUEST_COMMON(bits, id)\
+ dst.header = src.header;\
+ dst.header.msgh_id = id;\
+ dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer));\
+ dst.NDR = src.NDR;\
+ dst.exception = src.exception;\
+ dst.code_count = src.code_count;\
+ dst.code[0] = int##bits##_t(src.code[0]);\
+ dst.code[1] = int##bits##_t(src.code[1]);
+
+# define COPY_REQUEST_IDENTITY\
+ dst.msgh_body = src.msgh_body;\
+ dst.thread = src.thread;\
+ dst.task = src.task;
+
+# define COPY_REQUEST_STATE(flavor, stateCount, state)\
+ mach_msg_size_t stateSize = stateCount * sizeof(natural_t);\
+ dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer) -\
+ sizeof(dst.old_state) + stateSize);\
+ dst.flavor = flavor;\
+ dst.old_state_count = stateCount;\
+ memcpy(dst.old_state, state, stateSize);
+
+# define COPY_EXCEPTION_REQUEST(bits)\
+ static void\
+ CopyExceptionRequest##bits(ExceptionRequest64& src,\
+ ExceptionRequest##bits& dst)\
+ {\
+ COPY_REQUEST_COMMON(bits, sIDRequest##bits)\
+ COPY_REQUEST_IDENTITY\
+ }
+
+# define COPY_EXCEPTION_REQUEST_STATE(bits)\
+ static void\
+ CopyExceptionRequestState##bits(ExceptionRequest64& src,\
+ ExceptionRequestState##bits& dst,\
+ thread_state_flavor_t flavor,\
+ mach_msg_type_number_t stateCount,\
+ thread_state_t state)\
+ {\
+ COPY_REQUEST_COMMON(bits, sIDRequestState##bits)\
+ COPY_REQUEST_STATE(flavor, stateCount, state)\
+ }
+
+# define COPY_EXCEPTION_REQUEST_STATE_IDENTITY(bits)\
+ static void\
+ CopyExceptionRequestStateIdentity##bits(ExceptionRequest64& src,\
+ ExceptionRequestStateIdentity##bits& dst,\
+ thread_state_flavor_t flavor,\
+ mach_msg_type_number_t stateCount,\
+ thread_state_t state)\
+ {\
+ COPY_REQUEST_COMMON(bits, sIDRequestStateIdentity##bits)\
+ COPY_REQUEST_IDENTITY\
+ COPY_REQUEST_STATE(flavor, stateCount, state)\
+ }
+
+COPY_EXCEPTION_REQUEST(32)
+COPY_EXCEPTION_REQUEST_STATE(32)
+COPY_EXCEPTION_REQUEST_STATE_IDENTITY(32)
+COPY_EXCEPTION_REQUEST(64)
+COPY_EXCEPTION_REQUEST_STATE(64)
+COPY_EXCEPTION_REQUEST_STATE_IDENTITY(64)
+
+# undef COPY_EXCEPTION_REQUEST_STATE_IDENTITY
+# undef COPY_EXCEPTION_REQUEST_STATE
+# undef COPY_EXCEPTION_REQUEST
+# undef COPY_REQUEST_STATE
+# undef COPY_REQUEST_IDENTITY
+# undef COPY_REQUEST_COMMON
+
+/* -------------------------------------------------------------------------- */
+/* End Mach definitions and helper functions */
+/* -------------------------------------------------------------------------- */
+
+/* Every Mach exception handler is parameterized by these four properties. */
+struct MachExceptionParameters
+{
+ exception_mask_t mask;
+ mach_port_t port;
+ exception_behavior_t behavior;
+ thread_state_flavor_t flavor;
+};
+
+struct ExceptionHandlerState
+{
+ MachExceptionParameters current;
+ MachExceptionParameters previous;
+
+ /* Each Mach exception handler runs in its own thread. */
+ Thread handlerThread;
+};
+
+/* This choice of ID is arbitrary, but must not match our exception ID. */
+static const mach_msg_id_t sIDQuit = 42;
+
+static ExceptionHandlerState sMachExceptionState;
+
+/*
+ * The meat of our exception handler. This thread waits for an exception
+ * message, annotates the exception if needed, then forwards it to the
+ * previously installed handler (which will likely terminate the process).
+ */
+static void
+MachExceptionHandler()
+{
+ kern_return_t ret;
+ MachExceptionParameters& current = sMachExceptionState.current;
+ MachExceptionParameters& previous = sMachExceptionState.previous;
+
+ // We use the simplest kind of 64-bit exception message here.
+ ExceptionRequest64 request = {};
+ request.header.msgh_local_port = current.port;
+ request.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(request));
+ ret = mach_msg(&request.header, MACH_RCV_MSG, 0, request.header.msgh_size,
+ current.port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ // Restore the previous handler. We're going to forward to it
+ // anyway, and if we crash while doing so we don't want to hang.
+ task_set_exception_ports(mach_task_self(), previous.mask, previous.port,
+ previous.behavior, previous.flavor);
+
+ // If we failed even receiving the message, just give up.
+ if (ret != MACH_MSG_SUCCESS)
+ MOZ_CRASH("MachExceptionHandler: mach_msg failed to receive a message!");
+
+ // Terminate the thread if we're shutting down.
+ if (request.header.msgh_id == sIDQuit)
+ return;
+
+ // The only other valid message ID is the one associated with the
+ // EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES behavior we chose.
+ if (request.header.msgh_id != sIDRequest64)
+ MOZ_CRASH("MachExceptionHandler: Unexpected Message ID!");
+
+ // Make sure we can understand the exception we received.
+ if (request.exception != EXC_BAD_ACCESS || request.code_count != 2)
+ MOZ_CRASH("MachExceptionHandler: Unexpected exception type!");
+
+ // Get the address that the offending code tried to access.
+ uintptr_t address = uintptr_t(request.code[1]);
+
+ // If the faulting address is inside one of our protected regions, we
+ // want to annotate the crash to make it stand out from the crowd.
+ if (sProtectedRegions.isProtected(address)) {
+ ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
+ MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
+ }
+
+ // Forward to the previous handler which may be a debugger, the unix
+ // signal handler, the crash reporter or something else entirely.
+ if (previous.port != MACH_PORT_NULL) {
+ mach_msg_type_number_t stateCount;
+ thread_state_data_t state;
+ if ((uint32_t(previous.behavior) & ~MACH_EXCEPTION_CODES) != EXCEPTION_DEFAULT) {
+ // If the previous handler requested thread state, get it here.
+ stateCount = THREAD_STATE_MAX;
+ ret = thread_get_state(request.thread.name, previous.flavor, state, &stateCount);
+ if (ret != KERN_SUCCESS)
+ MOZ_CRASH("MachExceptionHandler: Could not get the thread state to forward!");
+ }
+
+ // Depending on the behavior of the previous handler, the forwarded
+ // exception message will have a different set of fields.
+ // Of particular note is that exception handlers that lack
+ // MACH_EXCEPTION_CODES will get 32-bit fields even on 64-bit
+ // systems. It appears that OSX simply truncates these fields.
+ ExceptionRequestUnion forward;
+ switch (uint32_t(previous.behavior)) {
+ case EXCEPTION_DEFAULT:
+ CopyExceptionRequest32(request, forward.r32);
+ break;
+ case EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES:
+ CopyExceptionRequest64(request, forward.r64);
+ break;
+ case EXCEPTION_STATE:
+ CopyExceptionRequestState32(request, forward.rs32,
+ previous.flavor, stateCount, state);
+ break;
+ case EXCEPTION_STATE | MACH_EXCEPTION_CODES:
+ CopyExceptionRequestState64(request, forward.rs64,
+ previous.flavor, stateCount, state);
+ break;
+ case EXCEPTION_STATE_IDENTITY:
+ CopyExceptionRequestStateIdentity32(request, forward.rsi32,
+ previous.flavor, stateCount, state);
+ break;
+ case EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES:
+ CopyExceptionRequestStateIdentity64(request, forward.rsi64,
+ previous.flavor, stateCount, state);
+ break;
+ default:
+ MOZ_CRASH("MachExceptionHandler: Unknown previous handler behavior!");
+ }
+
+ // Forward the generated message to the old port. The local and remote
+ // port fields *and their rights* are swapped on arrival, so we need to
+ // swap them back first.
+ forward.header.msgh_bits = (request.header.msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) |
+ MACH_MSGH_BITS(MACH_MSGH_BITS_LOCAL(request.header.msgh_bits),
+ MACH_MSGH_BITS_REMOTE(request.header.msgh_bits));
+ forward.header.msgh_local_port = forward.header.msgh_remote_port;
+ forward.header.msgh_remote_port = previous.port;
+ ret = mach_msg(&forward.header, MACH_SEND_MSG, forward.header.msgh_size, 0,
+ MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+ if (ret != MACH_MSG_SUCCESS)
+ MOZ_CRASH("MachExceptionHandler: Failed to forward to the previous handler!");
+ } else {
+ // There was no previous task-level exception handler, so defer to the
+ // host level one instead. We set the return code to KERN_FAILURE to
+ // indicate that we did not handle the exception.
+ // The reply message ID is always the request ID + 100.
+ ExceptionReply reply = {};
+ reply.header.msgh_bits =
+ MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.header.msgh_bits), 0);
+ reply.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(reply));
+ reply.header.msgh_remote_port = request.header.msgh_remote_port;
+ reply.header.msgh_local_port = MACH_PORT_NULL;
+ reply.header.msgh_id = request.header.msgh_id + 100;
+ reply.NDR = request.NDR;
+ reply.RetCode = KERN_FAILURE;
+ ret = mach_msg(&reply.header, MACH_SEND_MSG, reply.header.msgh_size, 0,
+ MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+ if (ret != MACH_MSG_SUCCESS)
+ MOZ_CRASH("MachExceptionHandler: Failed to forward to the host level!");
+ }
+}
+
+static void
+TerminateMachExceptionHandlerThread()
+{
+ // Send a simple quit message to the exception handler thread.
+ mach_msg_header_t msg;
+ msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
+ msg.msgh_size = static_cast<mach_msg_size_t>(sizeof(msg));
+ msg.msgh_remote_port = sMachExceptionState.current.port;
+ msg.msgh_local_port = MACH_PORT_NULL;
+ msg.msgh_reserved = 0;
+ msg.msgh_id = sIDQuit;
+ kern_return_t ret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL,
+ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ if (ret == MACH_MSG_SUCCESS)
+ sMachExceptionState.handlerThread.join();
+ else
+ MOZ_CRASH("MachExceptionHandler: Handler thread failed to terminate!");
+}
+
+bool
+MemoryProtectionExceptionHandler::install()
+{
+ MOZ_ASSERT(!sExceptionHandlerInstalled);
+
+ // If the exception handler is disabled, report success anyway.
+ if (MemoryProtectionExceptionHandler::isDisabled())
+ return true;
+
+ kern_return_t ret;
+ mach_port_t task = mach_task_self();
+
+ // Allocate a new exception port with receive rights.
+ sMachExceptionState.current = {};
+ MachExceptionParameters& current = sMachExceptionState.current;
+ ret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &current.port);
+ if (ret != KERN_SUCCESS)
+ return false;
+
+ // Give the new port send rights as well.
+ ret = mach_port_insert_right(task, current.port, current.port, MACH_MSG_TYPE_MAKE_SEND);
+ if (ret != KERN_SUCCESS) {
+ mach_port_deallocate(task, current.port);
+ current = {};
+ return false;
+ }
+
+ // Start the thread that will receive the messages from our exception port.
+ if (!sMachExceptionState.handlerThread.init(MachExceptionHandler)) {
+ mach_port_deallocate(task, current.port);
+ current = {};
+ return false;
+ }
+
+ // Set the other properties of our new exception handler.
+ current.mask = EXC_MASK_BAD_ACCESS;
+ current.behavior = exception_behavior_t(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES);
+ current.flavor = THREAD_STATE_NONE;
+
+ // Tell the task to use our exception handler, and save the previous one.
+ sMachExceptionState.previous = {};
+ MachExceptionParameters& previous = sMachExceptionState.previous;
+ mach_msg_type_number_t previousCount = 1;
+ ret = task_swap_exception_ports(task, current.mask, current.port, current.behavior,
+ current.flavor, &previous.mask, &previousCount,
+ &previous.port, &previous.behavior, &previous.flavor);
+ if (ret != KERN_SUCCESS) {
+ TerminateMachExceptionHandlerThread();
+ mach_port_deallocate(task, current.port);
+ previous = {};
+ current = {};
+ return false;
+ }
+
+ // We should have info on the previous exception handler, even if it's null.
+ MOZ_ASSERT(previousCount == 1);
+
+ sExceptionHandlerInstalled = true;
+ return sExceptionHandlerInstalled;
+}
+
+void
+MemoryProtectionExceptionHandler::uninstall()
+{
+ if (sExceptionHandlerInstalled) {
+ mach_port_t task = mach_task_self();
+
+ // Restore the previous exception handler.
+ MachExceptionParameters& previous = sMachExceptionState.previous;
+ task_set_exception_ports(task, previous.mask, previous.port,
+ previous.behavior, previous.flavor);
+
+ TerminateMachExceptionHandlerThread();
+
+ // Release the Mach IPC port we used.
+ mach_port_deallocate(task, sMachExceptionState.current.port);
+
+ sMachExceptionState.current = {};
+ sMachExceptionState.previous = {};
+
+ sExceptionHandlerInstalled = false;
+ }
+}
+
#else
#error "This platform is not supported!"
diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp
index d14aac1941..daae3d53b5 100644
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -126,8 +126,12 @@ DefaultJitOptions::DefaultJitOptions()
SET_DEFAULT(disableSharedStubs, false);
// Toggles whether sincos optimization is globally disabled.
- // See bug 984018 as to why this is disabled.
- SET_DEFAULT(disableSincos, true);
+ // See bug984018: The MacOS is the only one that has the sincos fast.
+ #if defined(XP_MACOSX)
+ SET_DEFAULT(disableSincos, false);
+ #else
+ SET_DEFAULT(disableSincos, true);
+ #endif
// Toggles whether sink code motion is globally disabled.
SET_DEFAULT(disableSink, true);
diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp
index 64c2da8122..75ab8fcb29 100644
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -49,7 +49,7 @@
# undef SystemFunction036
#endif
-#if defined(__DragonFly__) || \
+#if defined(XP_DARWIN) || defined(__DragonFly__) || \
defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
# include <stdlib.h>
# define HAVE_ARC4RANDOM
diff --git a/js/src/jsnativestack.cpp b/js/src/jsnativestack.cpp
index 34131f9667..42c84b2a42 100644
--- a/js/src/jsnativestack.cpp
+++ b/js/src/jsnativestack.cpp
@@ -8,7 +8,7 @@
#ifdef XP_WIN
# include "jswin.h"
-#elif defined(XP_UNIX)
+#elif defined(XP_DARWIN) || defined(DARWIN) || defined(XP_UNIX)
# include <pthread.h>
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
@@ -127,6 +127,10 @@ void*
js::GetNativeStackBaseImpl()
{
pthread_t thread = pthread_self();
+# if defined(XP_DARWIN) || defined(DARWIN)
+ return pthread_get_stackaddr_np(thread);
+
+# else
pthread_attr_t sattr;
pthread_attr_init(&sattr);
# if defined(__OpenBSD__)
@@ -166,6 +170,7 @@ js::GetNativeStackBaseImpl()
# else
return static_cast<char*>(stackBase) + stackSize;
# endif
+# endif
}
#endif /* !XP_WIN */
diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp
index b44fbdebca..6eb11b1cc1 100644
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1177,7 +1177,7 @@ FirstCharMatcher8bit(const char* text, uint32_t n, const char pat)
static const char16_t*
FirstCharMatcher16bit(const char16_t* text, uint32_t n, const char16_t pat)
{
-#if defined(XP_WIN)
+#if defined(XP_DARWIN) || defined(XP_WIN)
/*
* Performance of memchr is horrible in OSX. Windows is better,
* but it is still better to use UnrolledMatcher.
diff --git a/js/src/old-configure.in b/js/src/old-configure.in
index ec599b676d..2c0e45a4a5 100644
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -561,6 +561,11 @@ case "$host" in
esac
;;
+*-darwin*)
+ HOST_CFLAGS="$HOST_CFLAGS -DXP_UNIX -DXP_MACOSX"
+ HOST_OPTIMIZE_FLAGS="${HOST_OPTIMIZE_FLAGS=-O3}"
+ ;;
+
*-linux*|*-kfreebsd*-gnu|*-gnu*)
HOST_CFLAGS="$HOST_CFLAGS -DXP_UNIX"
HOST_OPTIMIZE_FLAGS="${HOST_OPTIMIZE_FLAGS=-O3}"
diff --git a/js/src/threading/posix/Thread.cpp b/js/src/threading/posix/Thread.cpp
index a5c2d2eacb..faca05c24d 100644
--- a/js/src/threading/posix/Thread.cpp
+++ b/js/src/threading/posix/Thread.cpp
@@ -151,7 +151,9 @@ js::ThisThread::SetName(const char* name)
#endif
int rv;
-#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+#ifdef XP_DARWIN
+ rv = pthread_setname_np(name);
+#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name);
rv = 0;
#elif defined(__NetBSD__)
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index 23c50814fc..251c8258cf 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -10,12 +10,14 @@
#include "mozilla/ThreadLocal.h"
#include "mozilla/Unused.h"
-#if defined(XP_UNIX)
+#if defined(XP_DARWIN)
+#include <mach/mach.h>
+#elif defined(XP_UNIX)
#include <sys/resource.h>
#elif defined(XP_WIN)
#include <processthreadsapi.h>
#include <windows.h>
-#endif // defined(XP_UNIX) || defined(XP_WIN)
+#endif // defined(XP_DARWIN) || defined(XP_UNIX) || defined(XP_WIN)
#include <locale.h>
#include <string.h>
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index eefc90cb4c..f1f2e07094 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -21,6 +21,9 @@
#include "jsclist.h"
#include "jsscript.h"
+#ifdef XP_DARWIN
+# include "wasm/WasmSignalHandlers.h"
+#endif
#include "builtin/AtomicsObject.h"
#include "builtin/Intl.h"
#include "builtin/Promise.h"
@@ -929,6 +932,10 @@ struct JSRuntime : public JS::shadow::Runtime,
*/
JSCList onNewGlobalObjectWatchers;
+#if defined(XP_DARWIN)
+ js::wasm::MachExceptionHandler wasmMachExceptionHandler;
+#endif
+
private:
js::FreeOp* defaultFreeOp_;
diff --git a/js/src/wasm/WasmSignalHandlers.cpp b/js/src/wasm/WasmSignalHandlers.cpp
index dae590acba..7699b336cc 100644
--- a/js/src/wasm/WasmSignalHandlers.cpp
+++ b/js/src/wasm/WasmSignalHandlers.cpp
@@ -1,7 +1,6 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* Copyright 2014 Mozilla Foundation
- * Copyright 2021 Moonchild Productions
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -229,6 +228,10 @@ class AutoSetHandlingSegFault
# define EPC_sig(p) ((p)->uc_mcontext.mc_pc)
# define RFP_sig(p) ((p)->uc_mcontext.mc_regs[30])
# endif
+#elif defined(XP_DARWIN)
+# define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip)
+# define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip)
+# define R15_sig(p) ((p)->uc_mcontext->__ss.__pc)
#else
# error "Don't know how to read/write to the thread state via the mcontext_t."
#endif
@@ -258,8 +261,34 @@ class AutoSetHandlingSegFault
#endif
// Define a context type for use in the emulator code. This is usually just
-// the same as CONTEXT.
+// the same as CONTEXT, but on Mac we use a different structure since we call
+// into the emulator code from a Mach exception handler rather than a
+// sigaction-style signal handler.
+#if defined(XP_DARWIN)
+# if defined(JS_CPU_X64)
+struct macos_x64_context {
+ x86_thread_state64_t thread;
+ x86_float_state64_t float_;
+};
+# define EMULATOR_CONTEXT macos_x64_context
+# elif defined(JS_CPU_X86)
+struct macos_x86_context {
+ x86_thread_state_t thread;
+ x86_float_state_t float_;
+};
+# define EMULATOR_CONTEXT macos_x86_context
+# elif defined(JS_CPU_ARM)
+struct macos_arm_context {
+ arm_thread_state_t thread;
+ arm_neon_state_t float_;
+};
+# define EMULATOR_CONTEXT macos_arm_context
+# else
+# error Unsupported architecture
+# endif
+#else
# define EMULATOR_CONTEXT CONTEXT
+#endif
#if defined(JS_CPU_X64)
# define PC_sig(p) RIP_sig(p)
@@ -351,6 +380,7 @@ StoreValueFromGPImm(SharedMem<void*> addr, size_t size, int32_t imm)
AtomicOperations::memcpySafeWhenRacy(addr, static_cast<void*>(&imm), size);
}
+# if !defined(XP_DARWIN)
MOZ_COLD static void*
AddressOfFPRegisterSlot(CONTEXT* context, FloatRegisters::Encoding encoding)
{
@@ -400,6 +430,57 @@ AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code)
}
MOZ_CRASH();
}
+# else
+MOZ_COLD static void*
+AddressOfFPRegisterSlot(EMULATOR_CONTEXT* context, FloatRegisters::Encoding encoding)
+{
+ switch (encoding) {
+ case X86Encoding::xmm0: return &context->float_.__fpu_xmm0;
+ case X86Encoding::xmm1: return &context->float_.__fpu_xmm1;
+ case X86Encoding::xmm2: return &context->float_.__fpu_xmm2;
+ case X86Encoding::xmm3: return &context->float_.__fpu_xmm3;
+ case X86Encoding::xmm4: return &context->float_.__fpu_xmm4;
+ case X86Encoding::xmm5: return &context->float_.__fpu_xmm5;
+ case X86Encoding::xmm6: return &context->float_.__fpu_xmm6;
+ case X86Encoding::xmm7: return &context->float_.__fpu_xmm7;
+ case X86Encoding::xmm8: return &context->float_.__fpu_xmm8;
+ case X86Encoding::xmm9: return &context->float_.__fpu_xmm9;
+ case X86Encoding::xmm10: return &context->float_.__fpu_xmm10;
+ case X86Encoding::xmm11: return &context->float_.__fpu_xmm11;
+ case X86Encoding::xmm12: return &context->float_.__fpu_xmm12;
+ case X86Encoding::xmm13: return &context->float_.__fpu_xmm13;
+ case X86Encoding::xmm14: return &context->float_.__fpu_xmm14;
+ case X86Encoding::xmm15: return &context->float_.__fpu_xmm15;
+ default: break;
+ }
+ MOZ_CRASH();
+}
+
+MOZ_COLD static void*
+AddressOfGPRegisterSlot(EMULATOR_CONTEXT* context, Registers::Code code)
+{
+ switch (code) {
+ case X86Encoding::rax: return &context->thread.__rax;
+ case X86Encoding::rcx: return &context->thread.__rcx;
+ case X86Encoding::rdx: return &context->thread.__rdx;
+ case X86Encoding::rbx: return &context->thread.__rbx;
+ case X86Encoding::rsp: return &context->thread.__rsp;
+ case X86Encoding::rbp: return &context->thread.__rbp;
+ case X86Encoding::rsi: return &context->thread.__rsi;
+ case X86Encoding::rdi: return &context->thread.__rdi;
+ case X86Encoding::r8: return &context->thread.__r8;
+ case X86Encoding::r9: return &context->thread.__r9;
+ case X86Encoding::r10: return &context->thread.__r10;
+ case X86Encoding::r11: return &context->thread.__r11;
+ case X86Encoding::r12: return &context->thread.__r12;
+ case X86Encoding::r13: return &context->thread.__r13;
+ case X86Encoding::r14: return &context->thread.__r14;
+ case X86Encoding::r15: return &context->thread.__r15;
+ default: break;
+ }
+ MOZ_CRASH();
+}
+# endif // !XP_DARWIN
MOZ_COLD static void
SetRegisterToCoercedUndefined(EMULATOR_CONTEXT* context, size_t size,
@@ -705,7 +786,276 @@ WasmFaultHandler(LPEXCEPTION_POINTERS exception)
return EXCEPTION_CONTINUE_SEARCH;
}
-#else // If not Windows, assume Unix-like
+#elif defined(XP_DARWIN)
+# include <mach/exc.h>
+
+static uint8_t**
+ContextToPC(EMULATOR_CONTEXT* context)
+{
+# if defined(JS_CPU_X64)
+ static_assert(sizeof(context->thread.__rip) == sizeof(void*),
+ "stored IP should be compile-time pointer-sized");
+ return reinterpret_cast<uint8_t**>(&context->thread.__rip);
+# elif defined(JS_CPU_X86)
+ static_assert(sizeof(context->thread.uts.ts32.__eip) == sizeof(void*),
+ "stored IP should be compile-time pointer-sized");
+ return reinterpret_cast<uint8_t**>(&context->thread.uts.ts32.__eip);
+# elif defined(JS_CPU_ARM)
+ static_assert(sizeof(context->thread.__pc) == sizeof(void*),
+ "stored IP should be compile-time pointer-sized");
+ return reinterpret_cast<uint8_t**>(&context->thread.__pc);
+# else
+# error Unsupported architecture
+# endif
+}
+
+// This definition was generated by mig (the Mach Interface Generator) for the
+// routine 'exception_raise' (exc.defs).
+#pragma pack(4)
+typedef struct {
+ mach_msg_header_t Head;
+ /* start of the kernel processed data */
+ mach_msg_body_t msgh_body;
+ mach_msg_port_descriptor_t thread;
+ mach_msg_port_descriptor_t task;
+ /* end of the kernel processed data */
+ NDR_record_t NDR;
+ exception_type_t exception;
+ mach_msg_type_number_t codeCnt;
+ int64_t code[2];
+} Request__mach_exception_raise_t;
+#pragma pack()
+
+// The full Mach message also includes a trailer.
+struct ExceptionRequest
+{
+ Request__mach_exception_raise_t body;
+ mach_msg_trailer_t trailer;
+};
+
+static bool
+HandleMachException(JSRuntime* rt, const ExceptionRequest& request)
+{
+ // Don't allow recursive handling of signals, see AutoSetHandlingSegFault.
+ if (rt->handlingSegFault)
+ return false;
+ AutoSetHandlingSegFault handling(rt);
+
+ // Get the port of the JSRuntime's thread from the message.
+ mach_port_t rtThread = request.body.thread.name;
+
+ // Read out the JSRuntime thread's register state.
+ EMULATOR_CONTEXT context;
+# if defined(JS_CPU_X64)
+ unsigned int thread_state_count = x86_THREAD_STATE64_COUNT;
+ unsigned int float_state_count = x86_FLOAT_STATE64_COUNT;
+ int thread_state = x86_THREAD_STATE64;
+ int float_state = x86_FLOAT_STATE64;
+# elif defined(JS_CPU_X86)
+ unsigned int thread_state_count = x86_THREAD_STATE_COUNT;
+ unsigned int float_state_count = x86_FLOAT_STATE_COUNT;
+ int thread_state = x86_THREAD_STATE;
+ int float_state = x86_FLOAT_STATE;
+# elif defined(JS_CPU_ARM)
+ unsigned int thread_state_count = ARM_THREAD_STATE_COUNT;
+ unsigned int float_state_count = ARM_NEON_STATE_COUNT;
+ int thread_state = ARM_THREAD_STATE;
+ int float_state = ARM_NEON_STATE;
+# else
+# error Unsupported architecture
+# endif
+ kern_return_t kret;
+ kret = thread_get_state(rtThread, thread_state,
+ (thread_state_t)&context.thread, &thread_state_count);
+ if (kret != KERN_SUCCESS)
+ return false;
+ kret = thread_get_state(rtThread, float_state,
+ (thread_state_t)&context.float_, &float_state_count);
+ if (kret != KERN_SUCCESS)
+ return false;
+
+ uint8_t** ppc = ContextToPC(&context);
+ uint8_t* pc = *ppc;
+
+ if (request.body.exception != EXC_BAD_ACCESS || request.body.codeCnt != 2)
+ return false;
+
+ WasmActivation* activation = rt->wasmActivationStack();
+ if (!activation)
+ return false;
+
+ const Instance* instance = activation->compartment()->wasm.lookupInstanceDeprecated(pc);
+ if (!instance || !instance->codeSegment().containsFunctionPC(pc))
+ return false;
+
+ uint8_t* faultingAddress = reinterpret_cast<uint8_t*>(request.body.code[1]);
+
+ // This check isn't necessary, but, since we can, check anyway to make
+ // sure we aren't covering up a real bug.
+ if (!IsHeapAccessAddress(*instance, faultingAddress))
+ return false;
+
+ HandleMemoryAccess(&context, pc, faultingAddress, *instance, ppc);
+
+ // Update the thread state with the new pc and register values.
+ kret = thread_set_state(rtThread, float_state, (thread_state_t)&context.float_, float_state_count);
+ if (kret != KERN_SUCCESS)
+ return false;
+ kret = thread_set_state(rtThread, thread_state, (thread_state_t)&context.thread, thread_state_count);
+ if (kret != KERN_SUCCESS)
+ return false;
+
+ return true;
+}
+
+// Taken from mach_exc in /usr/include/mach/mach_exc.defs.
+static const mach_msg_id_t sExceptionId = 2405;
+
+// The choice of id here is arbitrary, the only constraint is that sQuitId != sExceptionId.
+static const mach_msg_id_t sQuitId = 42;
+
+static void
+MachExceptionHandlerThread(JSRuntime* rt)
+{
+ mach_port_t port = rt->wasmMachExceptionHandler.port();
+ kern_return_t kret;
+
+ while(true) {
+ ExceptionRequest request;
+ kret = mach_msg(&request.body.Head, MACH_RCV_MSG, 0, sizeof(request),
+ port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+
+ // If we fail even receiving the message, we can't even send a reply!
+ // Rather than hanging the faulting thread (hanging the browser), crash.
+ if (kret != KERN_SUCCESS) {
+ fprintf(stderr, "MachExceptionHandlerThread: mach_msg failed with %d\n", (int)kret);
+ MOZ_CRASH();
+ }
+
+ // There are only two messages we should be receiving: an exception
+ // message that occurs when the runtime's thread faults and the quit
+ // message sent when the runtime is shutting down.
+ if (request.body.Head.msgh_id == sQuitId)
+ break;
+ if (request.body.Head.msgh_id != sExceptionId) {
+ fprintf(stderr, "Unexpected msg header id %d\n", (int)request.body.Head.msgh_bits);
+ MOZ_CRASH();
+ }
+
+ // Some thread just commited an EXC_BAD_ACCESS and has been suspended by
+ // the kernel. The kernel is waiting for us to reply with instructions.
+ // Our default is the "not handled" reply (by setting the RetCode field
+ // of the reply to KERN_FAILURE) which tells the kernel to continue
+ // searching at the process and system level. If this is an asm.js
+ // expected exception, we handle it and return KERN_SUCCESS.
+ bool handled = HandleMachException(rt, request);
+ kern_return_t replyCode = handled ? KERN_SUCCESS : KERN_FAILURE;
+
+ // This magic incantation to send a reply back to the kernel was derived
+ // from the exc_server generated by 'mig -v /usr/include/mach/mach_exc.defs'.
+ __Reply__exception_raise_t reply;
+ reply.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.body.Head.msgh_bits), 0);
+ reply.Head.msgh_size = sizeof(reply);
+ reply.Head.msgh_remote_port = request.body.Head.msgh_remote_port;
+ reply.Head.msgh_local_port = MACH_PORT_NULL;
+ reply.Head.msgh_id = request.body.Head.msgh_id + 100;
+ reply.NDR = NDR_record;
+ reply.RetCode = replyCode;
+ mach_msg(&reply.Head, MACH_SEND_MSG, sizeof(reply), 0, MACH_PORT_NULL,
+ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+ }
+}
+
+MachExceptionHandler::MachExceptionHandler()
+ : installed_(false),
+ thread_(),
+ port_(MACH_PORT_NULL)
+{}
+
+void
+MachExceptionHandler::uninstall()
+{
+ if (installed_) {
+ thread_port_t thread = mach_thread_self();
+ kern_return_t kret = thread_set_exception_ports(thread,
+ EXC_MASK_BAD_ACCESS,
+ MACH_PORT_NULL,
+ EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
+ THREAD_STATE_NONE);
+ mach_port_deallocate(mach_task_self(), thread);
+ if (kret != KERN_SUCCESS)
+ MOZ_CRASH();
+ installed_ = false;
+ }
+ if (thread_.joinable()) {
+ // Break the handler thread out of the mach_msg loop.
+ mach_msg_header_t msg;
+ msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
+ msg.msgh_size = sizeof(msg);
+ msg.msgh_remote_port = port_;
+ msg.msgh_local_port = MACH_PORT_NULL;
+ msg.msgh_reserved = 0;
+ msg.msgh_id = sQuitId;
+ kern_return_t kret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL,
+ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
+ if (kret != KERN_SUCCESS) {
+ fprintf(stderr, "MachExceptionHandler: failed to send quit message: %d\n", (int)kret);
+ MOZ_CRASH();
+ }
+
+ // Wait for the handler thread to complete before deallocating the port.
+ thread_.join();
+ }
+ if (port_ != MACH_PORT_NULL) {
+ DebugOnly<kern_return_t> kret = mach_port_destroy(mach_task_self(), port_);
+ MOZ_ASSERT(kret == KERN_SUCCESS);
+ port_ = MACH_PORT_NULL;
+ }
+}
+
+bool
+MachExceptionHandler::install(JSRuntime* rt)
+{
+ MOZ_ASSERT(!installed());
+ kern_return_t kret;
+ mach_port_t thread;
+
+ // Get a port which can send and receive data.
+ kret = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port_);
+ if (kret != KERN_SUCCESS)
+ goto error;
+ kret = mach_port_insert_right(mach_task_self(), port_, port_, MACH_MSG_TYPE_MAKE_SEND);
+ if (kret != KERN_SUCCESS)
+ goto error;
+
+ // Create a thread to block on reading port_.
+ if (!thread_.init(MachExceptionHandlerThread, rt))
+ goto error;
+
+ // Direct exceptions on this thread to port_ (and thus our handler thread).
+ // Note: we are totally clobbering any existing *thread* exception ports and
+ // not even attempting to forward. Breakpad and gdb both use the *process*
+ // exception ports which are only called if the thread doesn't handle the
+ // exception, so we should be fine.
+ thread = mach_thread_self();
+ kret = thread_set_exception_ports(thread,
+ EXC_MASK_BAD_ACCESS,
+ port_,
+ EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
+ THREAD_STATE_NONE);
+ mach_port_deallocate(mach_task_self(), thread);
+ if (kret != KERN_SUCCESS)
+ goto error;
+
+ installed_ = true;
+ return true;
+
+ error:
+ uninstall();
+ return false;
+}
+
+#else // If not Windows or Mac, assume Unix
enum class Signal {
SegFault,
@@ -809,7 +1159,7 @@ WasmFaultHandler(int signum, siginfo_t* info, void* context)
else
previousSignal->sa_handler(signum);
}
-# endif // XP_WIN || assume Unix-like
+# endif // XP_WIN || XP_DARWIN || assume unix
static void
RedirectIonBackedgesToInterruptCheck(JSRuntime* rt)
@@ -918,6 +1268,9 @@ ProcessHasSignalHandlers()
# if defined(XP_WIN)
if (!AddVectoredExceptionHandler(/* FirstHandler = */ true, WasmFaultHandler))
return false;
+# elif defined(XP_DARWIN)
+ // OSX handles seg faults via the Mach exception handler above, so don't
+ // install WasmFaultHandler.
# else
// SA_NODEFER allows us to reenter the signal handler if we crash while
// handling the signal, and fall through to the Breakpad handler by testing
@@ -953,6 +1306,12 @@ wasm::EnsureSignalHandlers(JSRuntime* rt)
if (!ProcessHasSignalHandlers())
return true;
+#if defined(XP_DARWIN)
+ // On OSX, each JSRuntime gets its own handler thread.
+ if (!rt->wasmMachExceptionHandler.installed() && !rt->wasmMachExceptionHandler.install(rt))
+ return false;
+#endif
+
return true;
}
diff --git a/js/src/wasm/WasmSignalHandlers.h b/js/src/wasm/WasmSignalHandlers.h
index 7fe3dd86e6..87faa15025 100644
--- a/js/src/wasm/WasmSignalHandlers.h
+++ b/js/src/wasm/WasmSignalHandlers.h
@@ -1,7 +1,6 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* Copyright 2014 Mozilla Foundation
- * Copyright 2021 Moonchild Productions
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +19,10 @@
#define wasm_signal_handlers_h
#include "mozilla/Attributes.h"
+
+#if defined(XP_DARWIN)
+# include <mach/mach.h>
+#endif
#include "threading/Thread.h"
struct JSRuntime;
@@ -43,6 +46,30 @@ EnsureSignalHandlers(JSRuntime* rt);
bool
HaveSignalHandlers();
+#if defined(XP_DARWIN)
+// On OSX we are forced to use the lower-level Mach exception mechanism instead
+// of Unix signals. Mach exceptions are not handled on the victim's stack but
+// rather require an extra thread. For simplicity, we create one such thread
+// per JSRuntime (upon the first use of asm.js in the JSRuntime). This thread
+// and related resources are owned by AsmJSMachExceptionHandler which is owned
+// by JSRuntime.
+class MachExceptionHandler
+{
+ bool installed_;
+ js::Thread thread_;
+ mach_port_t port_;
+
+ void uninstall();
+
+ public:
+ MachExceptionHandler();
+ ~MachExceptionHandler() { uninstall(); }
+ mach_port_t port() const { return port_; }
+ bool installed() const { return installed_; }
+ bool install(JSRuntime* rt);
+};
+#endif
+
// Test whether the given PC is within the innermost wasm activation. Return
// false if it is not, or it cannot be determined.
bool IsPCInWasmCode(void *pc);
diff --git a/js/xpconnect/shell/moz.build b/js/xpconnect/shell/moz.build
index 3361b7d810..c1789fdc71 100644
--- a/js/xpconnect/shell/moz.build
+++ b/js/xpconnect/shell/moz.build
@@ -14,6 +14,11 @@ SOURCES += [
'xpcshell.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'xpcshellMacUtils.mm',
+ ]
+
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
diff --git a/js/xpconnect/shell/xpcshell.cpp b/js/xpconnect/shell/xpcshell.cpp
index 3460e98a15..35e12449f8 100644
--- a/js/xpconnect/shell/xpcshell.cpp
+++ b/js/xpconnect/shell/xpcshell.cpp
@@ -10,6 +10,9 @@
#include "mozilla/WindowsDllBlocklist.h"
#include "nsXULAppAPI.h"
+#ifdef XP_MACOSX
+#include "xpcshellMacUtils.h"
+#endif
#ifdef XP_WIN
#include <windows.h>
#include <shlobj.h>
@@ -34,6 +37,10 @@ main(int argc, char** argv, char** envp)
gtk_parse_args(&argc, &argv);
#endif
+#ifdef XP_MACOSX
+ InitAutoreleasePool();
+#endif
+
// unbuffer stdout so that output is in the correct order; note that stderr
// is unbuffered by default
setbuf(stdout, 0);
@@ -44,5 +51,9 @@ main(int argc, char** argv, char** envp)
int result = XRE_XPCShellMain(argc, argv, envp);
+#ifdef XP_MACOSX
+ FinishAutoreleasePool();
+#endif
+
return result;
}
diff --git a/js/xpconnect/shell/xpcshellMacUtils.h b/js/xpconnect/shell/xpcshellMacUtils.h
new file mode 100644
index 0000000000..2e6b5cb359
--- /dev/null
+++ b/js/xpconnect/shell/xpcshellMacUtils.h
@@ -0,0 +1,8 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+// Functions to setup and release the Mac memory pool
+void InitAutoreleasePool();
+void FinishAutoreleasePool();
diff --git a/js/xpconnect/shell/xpcshellMacUtils.mm b/js/xpconnect/shell/xpcshellMacUtils.mm
new file mode 100644
index 0000000000..61d6a9ea9a
--- /dev/null
+++ b/js/xpconnect/shell/xpcshellMacUtils.mm
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 2; 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 <Foundation/Foundation.h>
+
+static NSAutoreleasePool *pool = NULL;
+
+void InitAutoreleasePool()
+{
+ pool = [[NSAutoreleasePool alloc] init];
+}
+
+void FinishAutoreleasePool()
+{
+ [pool release];
+}
diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp
index 569da5d56f..6888dee376 100644
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -127,6 +127,17 @@ SandboxDump(JSContext* cx, unsigned argc, Value* vp)
if (!cstr)
return false;
+#if defined(XP_MACOSX)
+ // Be nice and convert all \r to \n.
+ char* c = cstr;
+ char* cEnd = cstr + strlen(cstr);
+ while (c < cEnd) {
+ if (*c == '\r')
+ *c = '\n';
+ c++;
+ }
+#endif
+
fputs(cstr, stdout);
fflush(stdout);
args.rval().setBoolean(true);
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
index 48a3aa26ac..a02c5e103b 100644
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -3209,7 +3209,12 @@ XPCJSContext::Initialize()
// the web to base this decision primarily on the default stack size that the
// underlying platform makes available, but that seems to be what we do. :-(
-#if defined(MOZ_ASAN)
+#if defined(XP_MACOSX) || defined(DARWIN)
+ // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB,
+ // and give trusted script 180k extra. The stack is huge on mac anyway.
+ const size_t kStackQuota = 7 * 1024 * 1024;
+ const size_t kTrustedScriptBuffer = 180 * 1024;
+#elif defined(MOZ_ASAN)
// ASan requires more stack space due to red-zones, so give it double the
// default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements
// were not taken at the time of this writing, so we hazard a guess that
diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp
index 72ab89c402..ba56a4a0ee 100644
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -1276,6 +1276,22 @@ XRE_XPCShellMain(int argc, char** argv, char** envp)
argc -= 2;
argv += 2;
} else {
+#ifdef XP_MACOSX
+ // On OSX, the GreD needs to point to Contents/Resources in the .app
+ // bundle. Libraries will be loaded at a relative path to GreD, i.e.
+ // ../MacOS.
+ nsCOMPtr<nsIFile> tmpDir;
+ XRE_GetFileFromPath(argv[0], getter_AddRefs(greDir));
+ greDir->GetParent(getter_AddRefs(tmpDir));
+ tmpDir->Clone(getter_AddRefs(greDir));
+ tmpDir->SetNativeLeafName(NS_LITERAL_CSTRING("Resources"));
+ bool dirExists = false;
+ tmpDir->Exists(&dirExists);
+ if (dirExists) {
+ greDir = tmpDir.forget();
+ }
+ dirprovider.SetGREDirs(greDir);
+#else
nsAutoString workingDir;
if (!GetCurrentWorkingDirectory(workingDir)) {
printf("GetCurrentWorkingDirectory failed.\n");
@@ -1286,6 +1302,7 @@ XRE_XPCShellMain(int argc, char** argv, char** envp)
printf("NS_NewLocalFile failed.\n");
return 1;
}
+#endif
}
if (argc > 1 && !strcmp(argv[1], "-a")) {
@@ -1538,6 +1555,13 @@ XPCShellDirProvider::SetGREDirs(nsIFile* greDir)
{
mGREDir = greDir;
mGREDir->Clone(getter_AddRefs(mGREBinDir));
+#ifdef XP_MACOSX
+ nsAutoCString leafName;
+ mGREDir->GetNativeLeafName(leafName);
+ if (leafName.Equals("Resources")) {
+ mGREBinDir->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
+ }
+#endif
}
void
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
index 5b514a330b..8b91e38e8f 100644
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -55,6 +55,9 @@
#include "nsXBLBinding.h"
#include "nsContentUtils.h"
#include "nsIScriptError.h"
+#ifdef XP_MACOSX
+#include "nsIDocShell.h"
+#endif
#include "ChildIterator.h"
#include "nsError.h"
#include "nsLayoutUtils.h"
@@ -4298,7 +4301,11 @@ nsCSSFrameConstructor::FindXULTagData(Element* aElement,
SIMPLE_XUL_CREATE(menu, NS_NewMenuFrame),
SIMPLE_XUL_CREATE(menubutton, NS_NewMenuFrame),
SIMPLE_XUL_CREATE(menuitem, NS_NewMenuItemFrame),
+#ifdef XP_MACOSX
+ SIMPLE_TAG_CHAIN(menubar, nsCSSFrameConstructor::FindXULMenubarData),
+#else
SIMPLE_XUL_CREATE(menubar, NS_NewMenuBarFrame),
+#endif /* XP_MACOSX */
SIMPLE_TAG_CHAIN(popupgroup, nsCSSFrameConstructor::FindPopupGroupData),
SIMPLE_XUL_CREATE(iframe, NS_NewSubDocumentFrame),
SIMPLE_XUL_CREATE(editor, NS_NewSubDocumentFrame),
@@ -4371,6 +4378,31 @@ nsCSSFrameConstructor::FindXULDescriptionData(Element* aElement,
return &sDescriptionData;
}
+#ifdef XP_MACOSX
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULMenubarData(Element* aElement,
+ nsStyleContext* aStyleContext)
+{
+ nsCOMPtr<nsIDocShell> treeItem =
+ aStyleContext->PresContext()->GetDocShell();
+ if (treeItem && nsIDocShellTreeItem::typeChrome == treeItem->ItemType()) {
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ treeItem->GetParent(getter_AddRefs(parent));
+ if (!parent) {
+ // This is the root. Suppress the menubar, since on Mac
+ // window menus are not attached to the window.
+ static const FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
+ return &sSuppressData;
+ }
+ }
+
+ static const FrameConstructionData sMenubarData =
+ SIMPLE_XUL_FCDATA(NS_NewMenuBarFrame);
+ return &sMenubarData;
+}
+#endif /* XP_MACOSX */
+
/* static */
const nsCSSFrameConstructor::FrameConstructionData*
nsCSSFrameConstructor::FindXULListBoxBodyData(Element* aElement,
diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h
index 738ac82eca..c4f94ceccd 100644
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1468,6 +1468,10 @@ private:
FindXULLabelData(Element* aElement, nsStyleContext* aStyleContext);
static const FrameConstructionData*
FindXULDescriptionData(Element* aElement, nsStyleContext* aStyleContext);
+#ifdef XP_MACOSX
+ static const FrameConstructionData*
+ FindXULMenubarData(Element* aElement, nsStyleContext* aStyleContext);
+#endif /* XP_MACOSX */
static const FrameConstructionData*
FindXULListBoxBodyData(Element* aElement, nsStyleContext* aStyleContext);
static const FrameConstructionData*
diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp
index 8f3de7f78d..f7be42b5ff 100644
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2895,9 +2895,15 @@ nsRootPresContext::ComputePluginGeometryUpdates(nsIFrame* aFrame,
aList->ComputeVisibilityForRoot(aBuilder, &region);
}
+#ifdef XP_MACOSX
+ // We control painting of Mac plugins, so just apply geometry updates now.
+ // This is not happening during a paint event.
+ ApplyPluginGeometryUpdates();
+#else
if (XRE_IsParentProcess()) {
InitApplyPluginGeometryTimer();
}
+#endif
}
static void
@@ -2933,6 +2939,8 @@ nsRootPresContext::CancelApplyPluginGeometryTimer()
}
}
+#ifndef XP_MACOSX
+
static bool
HasOverlap(const LayoutDeviceIntPoint& aOffset1,
const nsTArray<LayoutDeviceIntRect>& aClipRects1,
@@ -3017,6 +3025,8 @@ PluginGetGeometryUpdate(nsTHashtable<nsRefPtrHashKey<nsIContent>>& aPlugins,
}
}
+#endif // #ifndef XP_MACOSX
+
static void
PluginDidSetGeometry(nsTHashtable<nsRefPtrHashKey<nsIContent>>& aPlugins)
{
@@ -3033,6 +3043,7 @@ PluginDidSetGeometry(nsTHashtable<nsRefPtrHashKey<nsIContent>>& aPlugins)
void
nsRootPresContext::ApplyPluginGeometryUpdates()
{
+#ifndef XP_MACOSX
CancelApplyPluginGeometryTimer();
nsTArray<nsIWidget::Configuration> configurations;
@@ -3044,6 +3055,7 @@ nsRootPresContext::ApplyPluginGeometryUpdates()
SortConfigurations(&configurations);
widget->ConfigureChildren(configurations);
}
+#endif // #ifndef XP_MACOSX
PluginDidSetGeometry(mRegisteredPlugins);
}
@@ -3051,6 +3063,7 @@ nsRootPresContext::ApplyPluginGeometryUpdates()
void
nsRootPresContext::CollectPluginGeometryUpdates(LayerManager* aLayerManager)
{
+#ifndef XP_MACOSX
// Collect and pass plugin widget configurations down to the compositor
// for transmission to the chrome process.
NS_ASSERTION(aLayerManager, "layer manager is invalid!");
@@ -3073,6 +3086,7 @@ nsRootPresContext::CollectPluginGeometryUpdates(LayerManager* aLayerManager)
clm->StorePluginWidgetConfigurations(configurations);
}
PluginDidSetGeometry(mRegisteredPlugins);
+#endif // #ifndef XP_MACOSX
}
static void
diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp
index eeb059f278..23876cc112 100644
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -8724,7 +8724,9 @@ PresShell::WillPaintWindow()
return;
}
+#ifndef XP_MACOSX
rootPresContext->ApplyPluginGeometryUpdates();
+#endif
}
void
diff --git a/layout/build/moz.build b/layout/build/moz.build
index 3e76edc491..d7996af8d9 100644
--- a/layout/build/moz.build
+++ b/layout/build/moz.build
@@ -63,6 +63,10 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
LOCAL_INCLUDES += [
'/dom/system/windows',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/dom/system/mac',
+ ]
elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
LOCAL_INCLUDES += [
'/widget/gtk',
diff --git a/layout/forms/nsListControlFrame.cpp b/layout/forms/nsListControlFrame.cpp
index 50e05776ef..5e157b7842 100644
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -834,7 +834,11 @@ nsListControlFrame::HandleListSelection(nsIDOMEvent* aEvent,
nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
bool isShift;
bool isControl;
+#ifdef XP_MACOSX
+ mouseEvent->GetMetaKey(&isControl);
+#else
mouseEvent->GetCtrlKey(&isControl);
+#endif
mouseEvent->GetShiftKey(&isShift);
return PerformSelection(aClickedIndex, isShift, isControl); // might destroy us
}
@@ -1904,7 +1908,11 @@ nsListControlFrame::DragMove(nsIDOMEvent* aMouseEvent)
nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
NS_ASSERTION(mouseEvent, "aMouseEvent is not an nsIDOMMouseEvent!");
bool isControl;
+#ifdef XP_MACOSX
+ mouseEvent->GetMetaKey(&isControl);
+#else
mouseEvent->GetCtrlKey(&isControl);
+#endif
nsWeakFrame weakFrame(this);
// Turn SHIFT on when you are dragging, unless control is on.
bool wasChanged = PerformSelection(selectedIndex,
@@ -2112,8 +2120,14 @@ nsListControlFrame::KeyDown(nsIDOMEvent* aKeyEvent)
bool dropDownMenuOnUpDown;
bool dropDownMenuOnSpace;
+#ifdef XP_MACOSX
+ dropDownMenuOnUpDown = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
+ dropDownMenuOnSpace = !keyEvent->IsAlt() && !keyEvent->IsControl() &&
+ !keyEvent->IsMeta();
+#else
dropDownMenuOnUpDown = keyEvent->IsAlt();
dropDownMenuOnSpace = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
+#endif
bool withinIncrementalSearchTime =
keyEvent->mTime - gLastKeyTime <= INCREMENTAL_SEARCH_KEYPRESS_TIME;
if ((dropDownMenuOnUpDown &&
diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp
index af3d2f9457..7e819ddf3b 100644
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3055,7 +3055,11 @@ nsFrame::GetDataForTableSelection(const nsFrameSelection* aFrameSelection,
{
// In Browser, special 'table selection' key must be pressed for table selection
// or when just Shift is pressed and we're already in table/cell selection mode
+#ifdef XP_MACOSX
+ doTableSelection = aMouseEvent->IsMeta() || (aMouseEvent->IsShift() && selectingTableCells);
+#else
doTableSelection = aMouseEvent->IsControl() || (aMouseEvent->IsShift() && selectingTableCells);
+#endif
}
if (!doTableSelection)
return NS_OK;
@@ -3324,7 +3328,13 @@ nsFrame::HandlePress(nsPresContext* aPresContext,
if (!frameselection || frameselection->GetDisplaySelection() == nsISelectionController::SELECTION_OFF)
return NS_OK;//nothing to do we cannot affect selection from here
+#ifdef XP_MACOSX
+ if (mouseEvent->IsControl())
+ return NS_OK;//short circuit. hard coded for mac due to time restraints.
+ bool control = mouseEvent->IsMeta();
+#else
bool control = mouseEvent->IsControl();
+#endif
RefPtr<nsFrameSelection> fc = const_cast<nsFrameSelection*>(frameselection);
if (mouseEvent->mClickCount > 1) {
diff --git a/layout/generic/nsPluginFrame.cpp b/layout/generic/nsPluginFrame.cpp
index ea42d9bd37..eee3f46b8b 100644
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -69,6 +69,11 @@
#endif /* MOZ_LOGGING */
#include "mozilla/Logging.h"
+#ifdef XP_MACOSX
+#include "gfxQuartzNativeDrawing.h"
+#include "mozilla/gfx/QuartzSupport.h"
+#endif
+
#ifdef MOZ_X11
#include "mozilla/X11Util.h"
using mozilla::DefaultXDisplay;
@@ -313,6 +318,15 @@ nsPluginFrame::PrepForDrawing(nsIWidget *aWidget)
mInnerView->AttachWidgetEventHandler(mWidget);
+#ifdef XP_MACOSX
+ // On Mac, we need to invalidate ourselves since even windowed
+ // plugins are painted through Thebes and we need to ensure
+ // the PaintedLayer containing the plugin is updated.
+ if (parentWidget == GetNearestWidget()) {
+ InvalidateFrame();
+ }
+#endif
+
RegisterPluginForGeometryUpdates();
// Here we set the background color for this widget because some plugins will use
@@ -551,12 +565,27 @@ nsPluginFrame::FixupWindow(const nsSize& aSize)
nsIntPoint origin = GetWindowOriginInPixels(windowless);
// window must be in "display pixels"
+#if defined(XP_MACOSX)
+ // window must be in "display pixels"
+ double scaleFactor = 1.0;
+ if (NS_FAILED(mInstanceOwner->GetContentsScaleFactor(&scaleFactor))) {
+ scaleFactor = 1.0;
+ }
+ int intScaleFactor = ceil(scaleFactor);
+ window->x = origin.x / intScaleFactor;
+ window->y = origin.y / intScaleFactor;
+ window->width = presContext->AppUnitsToDevPixels(aSize.width) / intScaleFactor;
+ window->height = presContext->AppUnitsToDevPixels(aSize.height) / intScaleFactor;
+#else
window->x = origin.x;
window->y = origin.y;
window->width = presContext->AppUnitsToDevPixels(aSize.width);
window->height = presContext->AppUnitsToDevPixels(aSize.height);
+#endif
+#ifndef XP_MACOSX
mInstanceOwner->UpdateWindowPositionAndClipRect(false);
+#endif
NotifyPluginReflowObservers();
}
@@ -587,6 +616,13 @@ nsPluginFrame::CallSetWindow(bool aCheckIsHidden)
RefPtr<nsPluginInstanceOwner> instanceOwnerRef(mInstanceOwner);
// refresh the plugin port as well
+#ifdef XP_MACOSX
+ mInstanceOwner->FixUpPluginWindow(nsPluginInstanceOwner::ePluginPaintEnable);
+ // Bail now if our frame has been destroyed.
+ if (!instanceOwnerRef->GetFrame()) {
+ return NS_ERROR_FAILURE;
+ }
+#endif
window->window = mInstanceOwner->GetPluginPort();
// Adjust plugin dimensions according to pixel snap results
@@ -606,11 +642,24 @@ nsPluginFrame::CallSetWindow(bool aCheckIsHidden)
intBounds.x += intOffset.x;
intBounds.y += intOffset.y;
+#if defined(XP_MACOSX)
+ // window must be in "display pixels"
+ double scaleFactor = 1.0;
+ if (NS_FAILED(instanceOwnerRef->GetContentsScaleFactor(&scaleFactor))) {
+ scaleFactor = 1.0;
+ }
+
+ size_t intScaleFactor = ceil(scaleFactor);
+ window->x = intBounds.x / intScaleFactor;
+ window->y = intBounds.y / intScaleFactor;
+ window->width = intBounds.width / intScaleFactor;
+ window->height = intBounds.height / intScaleFactor;
+#else
window->x = intBounds.x;
window->y = intBounds.y;
window->width = intBounds.width;
window->height = intBounds.height;
-
+#endif
// BE CAREFUL: By the time we get here the PluginFrame is sometimes destroyed
// and poisoned. If we reference local fields (implicit this deref),
// we will crash.
@@ -1012,6 +1061,11 @@ nsPluginFrame::NotifyPluginReflowObservers()
void
nsPluginFrame::DidSetWidgetGeometry()
{
+#if defined(XP_MACOSX)
+ if (mInstanceOwner && !IsHidden()) {
+ mInstanceOwner->FixUpPluginWindow(nsPluginInstanceOwner::ePluginPaintEnable);
+ }
+#else
if (!mWidget && mInstanceOwner) {
// UpdateWindowVisibility will notify the plugin of position changes
// by updating the NPWindow and calling NPP_SetWindow/AsyncSetWindow.
@@ -1022,20 +1076,29 @@ nsPluginFrame::DidSetWidgetGeometry()
nsLayoutUtils::IsPopup(nsLayoutUtils::GetDisplayRootFrame(this)) ||
!mNextConfigurationBounds.IsEmpty());
}
+#endif
}
bool
nsPluginFrame::IsOpaque() const
{
+#if defined(XP_MACOSX)
+ return false;
+#else
+
if (mInstanceOwner && mInstanceOwner->UseAsyncRendering()) {
return false;
}
return !IsTransparentMode();
+#endif
}
bool
nsPluginFrame::IsTransparentMode() const
{
+#if defined(XP_MACOSX)
+ return false;
+#else
if (!mInstanceOwner)
return false;
@@ -1057,6 +1120,7 @@ nsPluginFrame::IsTransparentMode() const
bool transparent = false;
pi->IsTransparent(&transparent);
return transparent;
+#endif
}
void
@@ -1077,20 +1141,27 @@ nsPluginFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
DO_GLOBAL_REFLOW_COUNT_DSP("nsPluginFrame");
+#ifndef XP_MACOSX
if (mWidget && aBuilder->IsInTransform()) {
// Windowed plugins should not be rendered inside a transform.
return;
}
+#endif
if (aBuilder->IsForPainting() && mInstanceOwner) {
// Update plugin frame for both content scaling and full zoom changes.
mInstanceOwner->ResolutionMayHaveChanged();
+#ifdef XP_MACOSX
+ mInstanceOwner->WindowFocusMayHaveChanged();
+#endif
if (mInstanceOwner->UseAsyncRendering()) {
NPWindow* window = nullptr;
mInstanceOwner->GetWindow(window);
bool isVisible = window && window->width > 0 && window->height > 0;
if (isVisible && aBuilder->ShouldSyncDecodeImages()) {
+#ifndef XP_MACOSX
mInstanceOwner->UpdateWindowVisibility(true);
+#endif
}
mInstanceOwner->NotifyPaintWaiter(aBuilder);
@@ -1167,8 +1238,8 @@ nsPluginFrame::PrintPlugin(nsRenderingContext& aRenderingContext,
window.clipRect.left = 0; window.clipRect.right = 0;
// platform specific printing code
-#if defined(XP_UNIX)
- // Doesn't work in a thebes world.
+#if defined(XP_UNIX) || defined(XP_MACOSX)
+ // Doesn't work in a thebes world, or on OS X.
(void)window;
(void)npprint;
#elif defined(XP_WIN)
@@ -1306,7 +1377,20 @@ nsPluginFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
if (window->width <= 0 || window->height <= 0)
return nullptr;
- IntSize size(window->width, window->height);
+#if defined(XP_MACOSX)
+ // window is in "display pixels", but size needs to be in device pixels
+ // window must be in "display pixels"
+ double scaleFactor = 1.0;
+ if (NS_FAILED(mInstanceOwner->GetContentsScaleFactor(&scaleFactor))) {
+ scaleFactor = 1.0;
+ }
+
+ size_t intScaleFactor = ceil(scaleFactor);
+#else
+ size_t intScaleFactor = 1;
+#endif
+
+ IntSize size(window->width * intScaleFactor, window->height * intScaleFactor);
nsRect area = GetContentRectRelativeToSelf() + aItem->ToReferenceFrame();
gfxRect r = nsLayoutUtils::RectToGfxRect(area, PresContext()->AppUnitsPerDevPixel());
@@ -1336,6 +1420,11 @@ nsPluginFrame::BuildLayer(nsDisplayListBuilder* aBuilder,
NS_ASSERTION(layer->GetType() == Layer::TYPE_IMAGE, "Bad layer type");
ImageLayer* imglayer = static_cast<ImageLayer*>(layer.get());
+#ifdef XP_MACOSX
+ if (!mInstanceOwner->UseAsyncRendering()) {
+ mInstanceOwner->DoCocoaEventDrawRect(r, nullptr);
+ }
+#endif
imglayer->SetScaleToSize(size, ScaleMode::STRETCH);
imglayer->SetContainer(container);
@@ -1456,11 +1545,37 @@ nsPluginFrame::HandleEvent(nsPresContext* aPresContext,
return rv;
#endif
+#ifdef XP_MACOSX
+ // we want to process some native mouse events in the cocoa event model
+ if ((anEvent->mMessage == eMouseEnterIntoWidget ||
+ anEvent->mMessage == eWheel) &&
+ mInstanceOwner->GetEventModel() == NPEventModelCocoa) {
+ *anEventStatus = mInstanceOwner->ProcessEvent(*anEvent);
+ // Due to plugin code reentering Gecko, this frame may be dead at this
+ // point.
+ return rv;
+ }
+
+ // These two calls to nsIPresShell::SetCapturingContext() (on mouse-down
+ // and mouse-up) are needed to make the routing of mouse events while
+ // dragging conform to standard OS X practice, and to the Cocoa NPAPI spec.
+ // See bug 525078 and bug 909678.
+ if (anEvent->mMessage == eMouseDown) {
+ nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED);
+ }
+#endif
+
rv = nsFrame::HandleEvent(aPresContext, anEvent, anEventStatus);
// We need to be careful from this point because the call to
// nsFrame::HandleEvent() might have killed us.
+#ifdef XP_MACOSX
+ if (anEvent->mMessage == eMouseUp) {
+ nsIPresShell::SetCapturingContent(nullptr, 0);
+ }
+#endif
+
return rv;
}
@@ -1636,7 +1751,11 @@ NS_NewObjectFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
bool
nsPluginFrame::IsPaintedByGecko() const
{
+#ifdef XP_MACOSX
+ return true;
+#else
return !mWidget;
+#endif
}
NS_IMPL_FRAMEARENA_HELPERS(nsPluginFrame)
diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp
index 301d799525..f8a231b00c 100644
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -551,6 +551,10 @@ nsFrameSelection::nsFrameSelection()
mSelectedCellIndex = 0;
nsAutoCopyListener *autoCopy = nullptr;
+ // On macOS, cache the current selection to send to osx service menu.
+#ifdef XP_MACOSX
+ autoCopy = nsAutoCopyListener::GetInstance(nsIClipboard::kSelectionCache);
+#endif
// Check to see if the autocopy pref is enabled
// and add the autocopy listener if it is
@@ -1973,6 +1977,16 @@ nsFrameSelection::RepaintSelection(SelectionType aSelectionType)
return NS_ERROR_NULL_POINTER;
NS_ENSURE_STATE(mShell);
+// On macOS, update the selection cache to the new active selection
+// aka the current selection.
+#ifdef XP_MACOSX
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ // Check an active window exists otherwise there cannot be a current selection
+ // and that it's a normal selection.
+ if (fm->GetActiveWindow() && aSelectionType == SelectionType::eNormal) {
+ UpdateSelectionCacheOnRepaintSelection(mDomSelections[index]);
+ }
+#endif
return mDomSelections[index]->Repaint(mShell->GetPresContext());
}
@@ -2696,7 +2710,11 @@ printf("aTarget == %d\n", aTarget);
// Any other mouseup actions require that Ctrl or Cmd key is pressed
// else stop table selection mode
bool doMouseUpAction = false;
+#ifdef XP_MACOSX
+ doMouseUpAction = aMouseEvent->IsMeta();
+#else
doMouseUpAction = aMouseEvent->IsControl();
+#endif
if (!doMouseUpAction)
{
#ifdef DEBUG_TABLE_SELECTION
diff --git a/layout/printing/nsPrintEngine.cpp b/layout/printing/nsPrintEngine.cpp
index 23edeb1fe0..d232b669bb 100644
--- a/layout/printing/nsPrintEngine.cpp
+++ b/layout/printing/nsPrintEngine.cpp
@@ -1004,6 +1004,10 @@ nsPrintEngine::GetCurrentPrintSettings(nsIPrintSettings * *aCurrentPrintSettings
nsresult
nsPrintEngine::CheckForPrinters(nsIPrintSettings* aPrintSettings)
{
+#if defined(XP_MACOSX)
+ // Mac doesn't support retrieving a printer list.
+ return NS_OK;
+#else
NS_ENSURE_ARG_POINTER(aPrintSettings);
// See if aPrintSettings already has a printer
@@ -1023,6 +1027,7 @@ nsPrintEngine::CheckForPrinters(nsIPrintSettings* aPrintSettings)
rv = aPrintSettings->SetPrinterName(printerName.get());
}
return rv;
+#endif
}
//----------------------------------------------------------------------
diff --git a/layout/reftests/reftest.list b/layout/reftests/reftest.list
index 15840f38e5..90320cba30 100644
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -347,6 +347,9 @@ include text-svgglyphs/reftest.list
# text-transform/
include text-transform/reftest.list
+# theme (osx)
+include ../../toolkit/themes/osx/reftests/reftest.list
+
include ../../toolkit/content/tests/reftests/reftest.list
# -moz-transform/
diff --git a/layout/style/nsComputedDOMStyle.cpp b/layout/style/nsComputedDOMStyle.cpp
index b3c4ccc148..65f42805de 100644
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -6647,7 +6647,14 @@ nsComputedDOMStyle::DoGetAnimationIterationCount()
RefPtr<nsROCSSPrimitiveValue> iterationCount = new nsROCSSPrimitiveValue;
float f = animation->GetIterationCount();
- float inf = NS_IEEEPositiveInfinity();
+ /* Need a nasty hack here to work around an optimizer bug in gcc
+ 4.2 on Mac, which somehow gets confused when directly comparing
+ a float to the return value of NS_IEEEPositiveInfinity when
+ building 32-bit builds. */
+#ifdef XP_MACOSX
+ volatile
+#endif
+ float inf = NS_IEEEPositiveInfinity();
if (f == inf) {
iterationCount->SetIdent(eCSSKeyword_infinite);
} else {
diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css
index 281b75d769..db75151d48 100644
--- a/layout/style/res/forms.css
+++ b/layout/style/res/forms.css
@@ -593,11 +593,15 @@ input[type="checkbox"]:disabled:hover:active {
cursor: inherit;
}
+% On Mac, the native theme takes care of this.
+% See nsNativeThemeCocoa::ThemeDrawsFocusForWidget.
+%ifndef XP_MACOSX
input[type="checkbox"]:-moz-focusring,
input[type="radio"]:-moz-focusring {
/* Don't specify the outline-color, we should always use initial value. */
outline: 1px dotted;
}
+%endif
input[type="checkbox"]:hover:active,
input[type="radio"]:hover:active {
@@ -719,10 +723,12 @@ input[type="color"]:-moz-system-metric(color-picker-available):active:hover,
input[type="reset"]:active:hover,
input[type="button"]:active:hover,
input[type="submit"]:active:hover {
+%ifndef XP_MACOSX
padding-block-start: 0px;
padding-inline-end: 5px;
padding-block-end: 0px;
padding-inline-start: 7px;
+%endif
border-style: inset;
background-color: ButtonFace;
}
diff --git a/layout/xul/nsButtonBoxFrame.cpp b/layout/xul/nsButtonBoxFrame.cpp
index 3e0529e600..ba0b7fb2ab 100644
--- a/layout/xul/nsButtonBoxFrame.cpp
+++ b/layout/xul/nsButtonBoxFrame.cpp
@@ -126,6 +126,8 @@ nsButtonBoxFrame::HandleEvent(nsPresContext* aPresContext,
break;
}
+// On mac, Return fires the default button, not the focused one.
+#ifndef XP_MACOSX
case eKeyPress: {
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
if (!keyEvent) {
@@ -140,6 +142,7 @@ nsButtonBoxFrame::HandleEvent(nsPresContext* aPresContext,
}
break;
}
+#endif
case eKeyUp: {
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
diff --git a/layout/xul/nsMenuBarListener.cpp b/layout/xul/nsMenuBarListener.cpp
index 70955c4301..23df81937a 100644
--- a/layout/xul/nsMenuBarListener.cpp
+++ b/layout/xul/nsMenuBarListener.cpp
@@ -76,9 +76,15 @@ void nsMenuBarListener::InitAccessKey()
if (mAccessKey >= 0)
return;
- // Compiled-in defaults, in case we can't get LookAndFeel...
+ // Compiled-in defaults, in case we can't get LookAndFeel --
+ // mac doesn't have menu shortcuts, other platforms use alt.
+#ifdef XP_MACOSX
+ mAccessKey = 0;
+ mAccessKeyMask = 0;
+#else
mAccessKey = nsIDOMKeyEvent::DOM_VK_ALT;
mAccessKeyMask = MODIFIER_ALT;
+#endif
// Get the menu access key value from prefs, overriding the default:
mAccessKey = Preferences::GetInt("ui.key.menuAccessKey", mAccessKey);
@@ -248,7 +254,8 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent)
aKeyEvent->PreventDefault();
}
}
- // Also need to handle F10 specially.
+#ifndef XP_MACOSX
+ // Also need to handle F10 specially on Non-Mac platform.
else if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) {
if ((GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) {
// The F10 key just went down by itself or with ctrl pressed.
@@ -266,6 +273,7 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent)
}
}
}
+#endif // !XP_MACOSX
}
return NS_OK;
diff --git a/layout/xul/nsMenuFrame.cpp b/layout/xul/nsMenuFrame.cpp
index 30cab242ea..933cc43cf3 100644
--- a/layout/xul/nsMenuFrame.cpp
+++ b/layout/xul/nsMenuFrame.cpp
@@ -404,12 +404,26 @@ nsMenuFrame::HandleEvent(nsPresContext* aPresContext,
if (aEvent->mMessage == eKeyPress && !IsDisabled()) {
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
uint32_t keyCode = keyEvent->mKeyCode;
- // Toggle menulist on unmodified F4 or Alt arrow
+#ifdef XP_MACOSX
+ // On mac, open menulist on either up/down arrow or space (w/o Cmd pressed)
+ if (!IsOpen() && ((keyEvent->mCharCode == ' ' && !keyEvent->IsMeta()) ||
+ (keyCode == NS_VK_UP || keyCode == NS_VK_DOWN))) {
+
+ // When pressing space, don't open the menu if performing an incremental search.
+ if (keyEvent->mCharCode != ' ' ||
+ !nsMenuPopupFrame::IsWithinIncrementalTime(keyEvent->mTime)) {
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ OpenMenu(false);
+ }
+ }
+#else
+ // On other platforms, toggle menulist on unmodified F4 or Alt arrow
if ((keyCode == NS_VK_F4 && !keyEvent->IsAlt()) ||
((keyCode == NS_VK_UP || keyCode == NS_VK_DOWN) && keyEvent->IsAlt())) {
*aEventStatus = nsEventStatus_eConsumeNoDefault;
ToggleMenuState();
}
+#endif
}
else if (aEvent->mMessage == eMouseDown &&
aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp
index 1d47652dcb..378d719d44 100644
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -1502,8 +1502,17 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS
screenPoint.MoveBy(margin.left + offsetForContextMenu.x,
margin.top + offsetForContextMenu.y);
- // Screen positioned popups can be flipped vertically but never horizontally
+#ifdef XP_MACOSX
+ // OSX tooltips follow standard flip rule but other popups flip horizontally not vertically
+ if (mPopupType == ePopupTypeTooltip) {
+ vFlip = FlipStyle_Outside;
+ } else {
+ hFlip = FlipStyle_Outside;
+ }
+#else
+ // Other OS screen positioned popups can be flipped vertically but never horizontally
vFlip = FlipStyle_Outside;
+#endif // #ifdef XP_MACOSX
}
// If a panel is being moved or has flip="none", don't constrain or flip it. But always do this for
diff --git a/layout/xul/nsRepeatService.h b/layout/xul/nsRepeatService.h
index c731f8cf59..81321a4729 100644
--- a/layout/xul/nsRepeatService.h
+++ b/layout/xul/nsRepeatService.h
@@ -14,7 +14,11 @@
#define INITAL_REPEAT_DELAY 250
+#ifdef XP_MACOSX
+#define REPEAT_DELAY 25
+#else
#define REPEAT_DELAY 50
+#endif
class nsITimer;
diff --git a/layout/xul/nsSliderFrame.cpp b/layout/xul/nsSliderFrame.cpp
index b98ac4a03f..812c233fc1 100644
--- a/layout/xul/nsSliderFrame.cpp
+++ b/layout/xul/nsSliderFrame.cpp
@@ -664,7 +664,11 @@ nsSliderFrame::GetScrollToClick()
return false;
}
+#ifdef XP_MACOSX
+ return true;
+#else
return false;
+#endif
}
nsIFrame*
@@ -1174,8 +1178,8 @@ nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent)
return false;
}
-#if defined(MOZ_WIDGET_GTK)
- // On Linux, clicking the scrollbar thumb should never scroll to click.
+#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
+ // On Mac and Linux, clicking the scrollbar thumb should never scroll to click.
if (IsEventOverThumb(aEvent)) {
return false;
}
@@ -1183,7 +1187,11 @@ nsSliderFrame::ShouldScrollToClickForEvent(WidgetGUIEvent* aEvent)
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
+#ifdef XP_MACOSX
+ bool invertPref = mouseEvent->IsAlt();
+#else
bool invertPref = mouseEvent->IsShift();
+#endif
return GetScrollToClick() != invertPref;
}
diff --git a/layout/xul/nsXULPopupManager.cpp b/layout/xul/nsXULPopupManager.cpp
index 5fc533e1b9..6e8cc3dda1 100644
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -712,6 +712,21 @@ nsXULPopupManager::ShowMenu(nsIContent *aMenu,
nsAutoString position;
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist = do_QueryInterface(aMenu);
+ bool isNonEditableMenulist = false;
+ if (menulist) {
+ bool editable;
+ menulist->GetEditable(&editable);
+ isNonEditableMenulist = !editable;
+ }
+
+ if (isNonEditableMenulist) {
+ position.AssignLiteral("selection");
+ }
+ else
+#endif
+
if (onMenuBar || !onmenu)
position.AssignLiteral("after_start");
else
@@ -1799,6 +1814,15 @@ nsXULPopupManager::MayShowPopup(nsMenuPopupFrame* aPopup)
return false;
}
+#ifdef XP_MACOSX
+ if (rootWin) {
+ auto globalWin = nsGlobalWindow::Cast(rootWin.get());
+ if (globalWin->IsInModalState()) {
+ return false;
+ }
+ }
+#endif
+
// cannot open a popup that is a submenu of a menupopup that isn't open.
nsMenuFrame* menuFrame = do_QueryFrame(aPopup->GetParent());
if (menuFrame) {
@@ -2304,6 +2328,7 @@ nsXULPopupManager::HandleKeyboardEventWithKeyCode(
switch (keyCode) {
case nsIDOMKeyEvent::DOM_VK_UP:
case nsIDOMKeyEvent::DOM_VK_DOWN:
+#ifndef XP_MACOSX
// roll up the popup when alt+up/down are pressed within a menulist.
bool alt;
aKeyEvent->GetAltKey(&alt);
@@ -2312,6 +2337,7 @@ nsXULPopupManager::HandleKeyboardEventWithKeyCode(
break;
}
MOZ_FALLTHROUGH;
+#endif
case nsIDOMKeyEvent::DOM_VK_LEFT:
case nsIDOMKeyEvent::DOM_VK_RIGHT:
@@ -2340,7 +2366,9 @@ nsXULPopupManager::HandleKeyboardEventWithKeyCode(
break;
case nsIDOMKeyEvent::DOM_VK_TAB:
+#ifndef XP_MACOSX
case nsIDOMKeyEvent::DOM_VK_F10:
+#endif
if (aTopVisibleMenuItem &&
!aTopVisibleMenuItem->Frame()->GetContent()->AttrValueIs(kNameSpaceID_None,
nsGkAtoms::activateontab, nsGkAtoms::_true, eCaseMatters)) {
diff --git a/layout/xul/tree/nsTreeBodyFrame.cpp b/layout/xul/tree/nsTreeBodyFrame.cpp
index 262884c9a8..ec054a234e 100644
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -2613,7 +2613,9 @@ nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
// Save last values, we will need them.
int32_t lastDropRow = mSlots->mDropRow;
int16_t lastDropOrient = mSlots->mDropOrient;
+#ifndef XP_MACOSX
int16_t lastScrollLines = mSlots->mScrollLines;
+#endif
// Find out the current drag action
uint32_t lastDragAction = mSlots->mDragAction;
@@ -2631,6 +2633,9 @@ nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
mSlots->mDropAllowed = false;
InvalidateDropFeedback(lastDropRow, lastDropOrient);
}
+#ifdef XP_MACOSX
+ ScrollByLines(mSlots->mScrollLines);
+#else
if (!lastScrollLines) {
// Cancel any previously initialized timer.
if (mSlots->mTimer) {
@@ -2643,6 +2648,7 @@ nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
LazyScrollCallback, nsITimer::TYPE_ONE_SHOT,
getter_AddRefs(mSlots->mTimer));
}
+#endif
// Bail out to prevent spring loaded timer and feedback line settings.
return NS_OK;
}
@@ -2836,6 +2842,53 @@ nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
if (!mView || !GetContent ()->GetComposedDoc()->GetWindow())
return;
+#ifdef XP_MACOSX
+ nsIContent* baseElement = GetBaseElement();
+ nsIFrame* treeFrame =
+ baseElement ? baseElement->GetPrimaryFrame() : nullptr;
+ nsCOMPtr<nsITreeSelection> selection;
+ mView->GetSelection(getter_AddRefs(selection));
+ nsITheme* theme = PresContext()->GetTheme();
+ // On Mac, we support native theming of selected rows. On 10.10 and higher,
+ // this means applying vibrancy which require us to register the theme
+ // geometrics for the row. In order to make the vibrancy effect to work
+ // properly, we also need the tree to be themed as a source list.
+ if (selection && treeFrame && theme &&
+ treeFrame->StyleDisplay()->mAppearance == NS_THEME_MAC_SOURCE_LIST) {
+ // Loop through our onscreen rows. If the row is selected and a
+ // -moz-appearance is provided, RegisterThemeGeometry might be necessary.
+ const auto end = std::min(mRowCount, LastVisibleRow() + 1);
+ for (auto i = FirstVisibleRow(); i < end; i++) {
+ bool isSelected;
+ selection->IsSelected(i, &isSelected);
+ if (isSelected) {
+ PrefillPropertyArray(i, nullptr);
+ nsAutoString properties;
+ mView->GetRowProperties(i, properties);
+ nsTreeUtils::TokenizeProperties(properties, mScratchArray);
+ nsStyleContext* rowContext =
+ GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
+ auto appearance = rowContext->StyleDisplay()->mAppearance;
+ if (appearance) {
+ if (theme->ThemeSupportsWidget(PresContext(), this, appearance)) {
+ nsITheme::ThemeGeometryType type =
+ theme->ThemeGeometryTypeForWidget(this, appearance);
+ if (type != nsITheme::eThemeGeometryTypeUnknown) {
+ nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight *
+ (i - FirstVisibleRow()), mInnerBox.width,
+ mRowHeight);
+ aBuilder->RegisterThemeGeometry(type,
+ LayoutDeviceIntRect::FromUnknownRect(
+ (rowRect + aBuilder->ToReferenceFrame(this)).ToNearestPixels(
+ PresContext()->AppUnitsPerDevPixel())));
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+
aLists.Content()->AppendNewToTop(new (aBuilder)
nsDisplayTreeBody(aBuilder, this));
}
diff --git a/mailnews/addrbook/public/nsAbBaseCID.h b/mailnews/addrbook/public/nsAbBaseCID.h
index 611300d0ab..0dcc630c50 100644
--- a/mailnews/addrbook/public/nsAbBaseCID.h
+++ b/mailnews/addrbook/public/nsAbBaseCID.h
@@ -387,6 +387,47 @@
#define NS_ABVIEW_CONTRACTID \
"@mozilla.org/addressbook/abview;1"
+#ifdef XP_MACOSX
+//
+// nsAbOSXDirectory
+//
+#define NS_ABOSXDIRECTORY_PREFIX "moz-abosxdirectory"
+#define NS_ABOSXCARD_PREFIX "moz-abosxcard"
+
+#define NS_ABOSXDIRECTORY_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX NS_ABOSXDIRECTORY_PREFIX
+
+#define NS_ABOSXDIRECTORY_CID \
+{ /* {83781cc6-c682-11d6-bdeb-0005024967b8}*/ \
+ 0x83781cc6, 0xc682, 0x11d6, \
+ {0xbd, 0xeb, 0x00, 0x05, 0x02, 0x49, 0x67, 0xb8} \
+}
+
+//
+// nsAbOSXCard
+//
+#define NS_ABOSXCARD_CONTRACTID \
+ NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX NS_ABOSXCARD_PREFIX
+
+#define NS_ABOSXCARD_CID \
+{ /* {89bbf582-c682-11d6-bc9d-0005024967b8}*/ \
+ 0x89bbf582, 0xc682, 0x11d6, \
+ {0xbc, 0x9d, 0x00, 0x05, 0x02, 0x49, 0x67, 0xb8} \
+}
+
+//
+// OS X directory factory
+//
+#define NS_ABOSXDIRFACTORY_CONTRACTID \
+ NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX NS_ABOSXDIRECTORY_PREFIX
+
+#define NS_ABOSXDIRFACTORY_CID \
+{ /* {90efe2fe-c682-11d6-9c83-0005024967b8}*/ \
+ 0x90efe2fe, 0xc682, 0x11d6, \
+ {0x9c, 0x83, 0x00, 0x05, 0x02, 0x49, 0x67, 0xb8} \
+}
+#endif
+
#define NS_MSGVCARDSERVICE_CID \
{ 0x3c4ac0da, 0x2cda, 0x4018, \
{ 0x95, 0x51, 0xe1, 0x58, 0xb2, 0xe1, 0x22, 0xd3 }}
diff --git a/mailnews/base/ispdata/moz.build b/mailnews/base/ispdata/moz.build
index 363c2ba0c2..7de5dc2e32 100644
--- a/mailnews/base/ispdata/moz.build
+++ b/mailnews/base/ispdata/moz.build
@@ -2,4 +2,6 @@
# 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/.
-# Stub. This was configuring special snowflake OSX stuff. \ No newline at end of file
+# Disable movemail for Thunderbird on OSX
+if CONFIG['MOZ_MOVEMAIL'] and not (CONFIG['MOZ_THUNDERBIRD'] and CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa'):
+ FINAL_TARGET_FILES.isp += ['movemail.rdf']
diff --git a/mailnews/base/prefs/content/AccountWizard.xul b/mailnews/base/prefs/content/AccountWizard.xul
index 9d825084b2..2aee2e3a5d 100644
--- a/mailnews/base/prefs/content/AccountWizard.xul
+++ b/mailnews/base/prefs/content/AccountWizard.xul
@@ -343,7 +343,11 @@
checked="true"/>
</hbox>
<spacer flex="1"/>
+#ifndef XP_MACOSX
<description>&clickFinish.label;</description>
+#else
+ <description>&clickFinish.labelMac;</description>
+#endif
</vbox>
</wizardpage>
diff --git a/mailnews/base/search/src/nsMsgFilter.cpp b/mailnews/base/search/src/nsMsgFilter.cpp
index d7e9ac9408..e94240f299 100644
--- a/mailnews/base/search/src/nsMsgFilter.cpp
+++ b/mailnews/base/search/src/nsMsgFilter.cpp
@@ -822,6 +822,11 @@ nsresult nsMsgFilter::ConvertMoveOrCopyToFolderValue(nsIMsgRuleAction *filterAct
if (offset != -1)
moveValue.Cut(offset, 4);
+#ifdef XP_MACOSX
+ nsCString unescapedMoveValue;
+ MsgUnescapeString(moveValue, 0, unescapedMoveValue);
+ moveValue = unescapedMoveValue;
+#endif
destFolderUri.Append('/');
if (filterVersion == k45Version)
{
diff --git a/mailnews/base/src/moz.build b/mailnews/base/src/moz.build
index 65853daf96..91bf235c34 100644
--- a/mailnews/base/src/moz.build
+++ b/mailnews/base/src/moz.build
@@ -61,6 +61,8 @@ if CONFIG['OS_ARCH'] == 'WINNT':
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'qt'):
SOURCES += ['nsMessengerUnixIntegration.cpp']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += ['nsMessengerOSXIntegration.mm']
EXTRA_COMPONENTS += [
'folderLookupService.js',
diff --git a/mailnews/base/src/nsMessenger.cpp b/mailnews/base/src/nsMessenger.cpp
index 8719530f79..953b462b6a 100644
--- a/mailnews/base/src/nsMessenger.cpp
+++ b/mailnews/base/src/nsMessenger.cpp
@@ -698,6 +698,7 @@ nsresult nsMessenger::SaveAttachment(nsIFile *aFile,
saveListener->QueryInterface(NS_GET_IID(nsIStreamListener),
getter_AddRefs(convertedListener));
+#ifndef XP_MACOSX
// if the content type is bin hex we are going to do a hokey hack and make sure we decode the bin hex
// when saving an attachment to disk..
if (MsgLowerCaseEqualsLiteral(aContentType, APPLICATION_BINHEX))
@@ -712,6 +713,7 @@ nsresult nsMessenger::SaveAttachment(nsIFile *aFile,
channelSupport,
getter_AddRefs(convertedListener));
}
+#endif
nsCOMPtr<nsIURI> dummyNull;
if (fetchService)
rv = fetchService->FetchMimePart(URL, fullMessageUri.get(),
@@ -769,6 +771,10 @@ nsMessenger::SaveAttachmentToFolder(const nsACString& contentType, const nsACStr
ConvertAndSanitizeFileName(PromiseFlatCString(displayName).get(), unescapedFileName);
rv = attachmentDestination->Append(unescapedFileName);
NS_ENSURE_SUCCESS(rv, rv);
+#ifdef XP_MACOSX
+ rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE, ATTACHMENT_PERMISSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
rv = SaveAttachment(attachmentDestination, url, messageUri, contentType, nullptr, nullptr);
attachmentDestination.swap(*aOutFile);
diff --git a/mailnews/base/src/nsMessengerOSXIntegration.h b/mailnews/base/src/nsMessengerOSXIntegration.h
new file mode 100644
index 0000000000..91f42f2a2a
--- /dev/null
+++ b/mailnews/base/src/nsMessengerOSXIntegration.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#ifndef __nsMessengerOSXIntegration_h
+#define __nsMessengerOSXIntegration_h
+
+#include "nsIMessengerOSIntegration.h"
+#include "nsIFolderListener.h"
+#include "nsIAtom.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIObserver.h"
+#include "nsIAlertsService.h"
+#include "mozINewMailListener.h"
+
+#define NS_MESSENGEROSXINTEGRATION_CID \
+ {0xaa83266, 0x4225, 0x4c4b, \
+ {0x93, 0xf8, 0x94, 0xb1, 0x82, 0x58, 0x6f, 0x93}}
+
+class nsIStringBundle;
+
+class nsMessengerOSXIntegration : public nsIMessengerOSIntegration,
+ public nsIFolderListener,
+ public nsIObserver,
+ public mozINewMailListener
+{
+public:
+ nsMessengerOSXIntegration();
+ virtual nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGEROSINTEGRATION
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_MOZINEWMAILLISTENER
+
+private:
+ virtual ~nsMessengerOSXIntegration();
+
+ nsCOMPtr<nsIAtom> mBiffStateAtom;
+ nsCOMPtr<nsIAtom> mNewMailReceivedAtom;
+ nsresult ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI);
+ nsresult OnAlertFinished();
+ nsresult OnAlertClicked(const char16_t * aAlertCookie);
+#ifdef MOZ_SUITE
+ nsresult OnAlertClickedSimple();
+#endif
+ nsresult GetStringBundle(nsIStringBundle **aBundle);
+ void FillToolTipInfo(nsIMsgFolder *aFolder, int32_t aNewCount);
+ nsresult GetFirstFolderWithNewMail(nsIMsgFolder* aFolder, nsCString& aFolderURI);
+ nsresult BadgeDockIcon();
+ nsresult RestoreDockIcon();
+ nsresult BounceDockIcon();
+ nsresult GetNewMailAuthors(nsIMsgFolder* aFolder, nsString& aAuthors, int32_t aNewCount, int32_t* aNotDisplayed);
+
+ int32_t mUnreadTotal;
+ int32_t mUnreadChat;
+};
+
+#endif // __nsMessengerOSXIntegration_h
diff --git a/mailnews/base/src/nsMessengerOSXIntegration.mm b/mailnews/base/src/nsMessengerOSXIntegration.mm
new file mode 100644
index 0000000000..a38716e179
--- /dev/null
+++ b/mailnews/base/src/nsMessengerOSXIntegration.mm
@@ -0,0 +1,700 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nscore.h"
+#include "nsMsgUtils.h"
+#include "nsArrayUtils.h"
+#include "nsMessengerOSXIntegration.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgFolder.h"
+#include "nsCOMPtr.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIDirectoryService.h"
+#include "MailNewsTypes.h"
+#include "nsIWindowMediator.h"
+#include "nsIDOMChromeWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMessengerWindowService.h"
+#include "prprf.h"
+#include "nsIAlertsService.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWindowWatcher.h"
+#include "nsMsgLocalCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMessenger.h"
+#include "nsObjCExceptions.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozINewMailNotificationService.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+#include <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+
+#define kBiffAnimateDockIconPref "mail.biff.animate_dock_icon"
+#define kMaxDisplayCount 10
+
+using namespace mozilla::mailnews;
+
+// HACK: Limitations in Focus/SetFocus on Mac (see bug 465446)
+nsresult FocusAppNative()
+{
+ ProcessSerialNumber psn;
+
+ if (::GetCurrentProcess(&psn) != 0)
+ return NS_ERROR_FAILURE;
+
+ if (::SetFrontProcess(&psn) != 0)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+static void openMailWindow(const nsCString& aUri)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
+ if (topMostMsgWindow)
+ {
+ if (!aUri.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUri(do_CreateInstance(NS_MAILBOXURL_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ rv = msgUri->SetSpec(aUri);
+ if (NS_FAILED(rv))
+ return;
+
+ bool isMessageUri = false;
+ msgUri->GetIsMessageUri(&isMessageUri);
+ if (isMessageUri)
+ {
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ // SeaMonkey only supports message uris, whereas Thunderbird only
+ // supports message headers. This should be simplified/removed when
+ // bug 507593 is implemented.
+#ifdef MOZ_SUITE
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar", msgUri,
+ getter_AddRefs(newWindow));
+#else
+ nsCOMPtr<nsIMessenger> messenger(do_CreateInstance(NS_MESSENGER_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ return;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ messenger->MsgHdrFromURI(aUri, getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ wwatch->OpenWindow(0, "chrome://messenger/content/messageWindow.xul",
+ "_blank", "all,chrome,dialog=no,status,toolbar", msgHdr,
+ getter_AddRefs(newWindow));
+ }
+#endif
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(aUri);
+ }
+ }
+
+ FocusAppNative();
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(domWindow);
+ privateWindow->Focus();
+ }
+ }
+ else
+ {
+ // the user doesn't have a mail window open already so open one for them...
+ nsCOMPtr<nsIMessengerWindowService> messengerWindowService =
+ do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID);
+ // if we want to preselect the first account with new mail,
+ // here is where we would try to generate a uri to pass in
+ // (and add code to the messenger window service to make that work)
+ if (messengerWindowService)
+ messengerWindowService->OpenMessengerWindowWithUri(
+ "mail:3pane", aUri.get(), nsMsgKey_None);
+ }
+}
+
+nsMessengerOSXIntegration::nsMessengerOSXIntegration()
+{
+ mBiffStateAtom = MsgGetAtom("BiffState");
+ mNewMailReceivedAtom = MsgGetAtom("NewMailReceived");
+ mUnreadTotal = 0;
+}
+
+nsMessengerOSXIntegration::~nsMessengerOSXIntegration()
+{
+ RestoreDockIcon();
+}
+
+NS_IMPL_ADDREF(nsMessengerOSXIntegration)
+NS_IMPL_RELEASE(nsMessengerOSXIntegration)
+
+NS_INTERFACE_MAP_BEGIN(nsMessengerOSXIntegration)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(mozINewMailListener)
+NS_INTERFACE_MAP_END
+
+
+nsresult
+nsMessengerOSXIntegration::Init()
+{
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return observerService->AddObserver(this, "mail-startup-done", false);
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemUnicharPropertyChanged(nsIMsgFolder *, nsIAtom *, const char16_t *, const char16_t *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+ if (!strcmp(aTopic, "alertfinished"))
+ return OnAlertFinished();
+
+ if (!strcmp(aTopic, "alertclickcallback"))
+ return OnAlertClicked(aData);
+
+#ifdef MOZ_SUITE
+ // SeaMonkey does most of the GUI work in JS code when clicking on a mail
+ // notification, so it needs an extra function here
+ if (!strcmp(aTopic, "alertclicksimplecallback"))
+ return OnAlertClickedSimple();
+#endif
+
+ if (!strcmp(aTopic, "mail-startup-done")) {
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ observerService->RemoveObserver(this, "mail-startup-done");
+
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ }
+
+ // Register with the new mail service for changes to the unread message count
+ nsCOMPtr<mozINewMailNotificationService> newmail
+ = do_GetService(MOZ_NEWMAILNOTIFICATIONSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv); // This should really be an assert with a helpful message
+ rv = newmail->AddListener(this, mozINewMailNotificationService::count);
+ NS_ENSURE_SUCCESS(rv, rv); // This should really be an assert with a helpful message
+
+ // Get the initial unread count. Ignore return value; if code above didn't fail, this won't
+ rv = newmail->GetMessageCount(&mUnreadTotal);
+ BadgeDockIcon();
+
+ // register with the mail sesson for folder events
+ // we care about new count, biff status
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::GetStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (bundleService && NS_SUCCEEDED(rv))
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle));
+ bundle.swap(*aBundle);
+ return rv;
+}
+
+void
+nsMessengerOSXIntegration::FillToolTipInfo(nsIMsgFolder *aFolder, int32_t aNewCount)
+{
+ if (aFolder)
+ {
+ nsString authors;
+ int32_t numNotDisplayed;
+ nsresult rv = GetNewMailAuthors(aFolder, authors, aNewCount, &numNotDisplayed);
+
+ // If all senders are vetoed, the authors string will be empty.
+ if (NS_FAILED(rv) || authors.IsEmpty())
+ return;
+
+ // If this isn't the root folder, get it so we can report for it.
+ // GetRootFolder always returns the server's root, so calling on the root itself is fine.
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ aFolder->GetRootFolder(getter_AddRefs(rootFolder));
+ if (!rootFolder)
+ return;
+
+ nsString accountName;
+ rootFolder->GetPrettiestName(accountName);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ GetStringBundle(getter_AddRefs(bundle));
+ if (bundle)
+ {
+ nsAutoString numNewMsgsText;
+ numNewMsgsText.AppendInt(aNewCount);
+ nsString finalText;
+ nsCString uri;
+ aFolder->GetURI(uri);
+
+ if (numNotDisplayed > 0)
+ {
+ nsAutoString numNotDisplayedText;
+ numNotDisplayedText.AppendInt(numNotDisplayed);
+ const char16_t *formatStrings[3] = { numNewMsgsText.get(), authors.get(), numNotDisplayedText.get() };
+ bundle->FormatStringFromName(u"macBiffNotification_messages_extra",
+ formatStrings,
+ 3,
+ getter_Copies(finalText));
+ }
+ else
+ {
+ const char16_t *formatStrings[2] = { numNewMsgsText.get(), authors.get() };
+
+ if (aNewCount == 1)
+ {
+ bundle->FormatStringFromName(u"macBiffNotification_message",
+ formatStrings,
+ 2,
+ getter_Copies(finalText));
+ // Since there is only 1 message, use the most recent mail's URI instead of the folder's
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ uint32_t numNewKeys;
+ uint32_t *newMessageKeys;
+ rv = db->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = db->GetMsgHdrForKey(newMessageKeys[numNewKeys - 1],
+ getter_AddRefs(hdr));
+ if (NS_SUCCEEDED(rv) && hdr)
+ aFolder->GetUriForMsg(hdr, uri);
+ }
+ NS_Free(newMessageKeys);
+ }
+ }
+ else
+ bundle->FormatStringFromName(u"macBiffNotification_messages",
+ formatStrings,
+ 2,
+ getter_Copies(finalText));
+ }
+ ShowAlertMessage(accountName, finalText, uri);
+ } // if we got a bundle
+ } // if we got a folder
+}
+
+nsresult
+nsMessengerOSXIntegration::ShowAlertMessage(const nsAString& aAlertTitle,
+ const nsAString& aAlertText,
+ const nsACString& aFolderURI)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIAlertsService> alertsService (do_GetService(NS_ALERTSERVICE_CONTRACTID, &rv));
+ // If we have an nsIAlertsService implementation, use it:
+ if (NS_SUCCEEDED(rv))
+ {
+ alertsService->ShowAlertNotification(EmptyString(),
+ aAlertTitle, aAlertText, true,
+ NS_ConvertASCIItoUTF16(aFolderURI),
+ this, EmptyString(),
+ NS_LITERAL_STRING("auto"),
+ EmptyString(), EmptyString(),
+ nullptr,
+ false,
+ false);
+ }
+
+ BounceDockIcon();
+
+ if (NS_FAILED(rv))
+ OnAlertFinished();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aFolder,
+ nsIAtom *aProperty,
+ int64_t aOldValue,
+ int64_t aNewValue)
+{
+ // if we got new mail show an alert
+ if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail)
+ {
+ bool performingBiff = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ aFolder->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetPerformingBiff(&performingBiff);
+ if (!performingBiff)
+ return NS_OK; // kick out right now...
+
+ // Biff happens for the root folder, but we want info for the child with new mail
+ nsCString folderUri;
+ GetFirstFolderWithNewMail(aFolder, folderUri);
+ nsCOMPtr<nsIMsgFolder> childFolder;
+ nsresult rv = aFolder->GetChildWithURI(folderUri, true, true,
+ getter_AddRefs(childFolder));
+ if (NS_FAILED(rv) || !childFolder)
+ return NS_ERROR_FAILURE;
+
+ int32_t numNewMessages = 0;
+ childFolder->GetNumNewMessages(true, &numNewMessages);
+ FillToolTipInfo(childFolder, numNewMessages);
+ }
+ else if (mNewMailReceivedAtom == aProperty)
+ {
+ FillToolTipInfo(aFolder, aNewValue);
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::OnAlertClicked(const char16_t* aAlertCookie)
+{
+ openMailWindow(NS_ConvertUTF16toUTF8(aAlertCookie));
+ return NS_OK;
+}
+
+#ifdef MOZ_SUITE
+nsresult
+nsMessengerOSXIntegration::OnAlertClickedSimple()
+{
+ // SeaMonkey only function; only focus the app here, rest of the work will
+ // be done in suite/mailnews/mailWidgets.xml
+ FocusAppNative();
+ return NS_OK;
+}
+#endif
+
+nsresult
+nsMessengerOSXIntegration::OnAlertFinished()
+{
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::BounceDockIcon()
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool bounceDockIcon = false;
+ rv = prefBranch->GetBoolPref(kBiffAnimateDockIconPref, &bounceDockIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!bounceDockIcon)
+ return NS_OK;
+
+ nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
+ if (mediator)
+ {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mediator->GetMostRecentWindow(u"mail:3pane", getter_AddRefs(domWindow));
+ if (domWindow)
+ {
+ nsCOMPtr<nsIDOMChromeWindow> chromeWindow(do_QueryInterface(domWindow));
+ chromeWindow->GetAttention();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::RestoreDockIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ id tile = [[NSApplication sharedApplication] dockTile];
+ [tile setBadgeLabel: nil];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsMessengerOSXIntegration::BadgeDockIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t unreadCount = mUnreadTotal;
+ // If count is less than one, we should restore the original dock icon.
+ if (unreadCount < 1)
+ {
+ RestoreDockIcon();
+ return NS_OK;
+ }
+
+ // Draw the number, first giving extensions a chance to modify.
+ // Extensions might wish to transform "1000" into "100+" or some
+ // other short string. Getting back the empty string will cause
+ // nothing to be drawn and us to return early.
+ nsresult rv;
+ nsCOMPtr<nsIObserverService> os
+ (do_GetService("@mozilla.org/observer-service;1", &rv));
+ if (NS_FAILED(rv))
+ {
+ RestoreDockIcon();
+ return rv;
+ }
+
+ nsCOMPtr<nsISupportsString> str
+ (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ {
+ RestoreDockIcon();
+ return rv;
+ }
+
+ nsAutoString total;
+ total.AppendInt(unreadCount);
+ str->SetData(total);
+ os->NotifyObservers(str, "before-unread-count-display",
+ total.get());
+ nsAutoString badgeString;
+ str->GetData(badgeString);
+ if (badgeString.IsEmpty())
+ {
+ RestoreDockIcon();
+ return NS_OK;
+ }
+
+ id tile = [[NSApplication sharedApplication] dockTile];
+ [tile setBadgeLabel:[NSString stringWithFormat:@"%S", (const unichar*)badgeString.get()]];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, uint32_t oldFlag, uint32_t newFlag)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemAdded(nsIMsgFolder *, nsISupports *)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemBoolPropertyChanged(nsIMsgFolder *aItem,
+ nsIAtom *aProperty,
+ bool aOldValue,
+ bool aNewValue)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *)
+{
+ return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::GetNewMailAuthors(nsIMsgFolder* aFolder,
+ nsString& aAuthors,
+ int32_t aNewCount,
+ int32_t* aNotDisplayed)
+{
+ // Get a list of names or email addresses for the folder's authors
+ // with new mail. Note that we only process the most recent "new"
+ // mail (aNewCount), working from most recently added. Duplicates
+ // are removed, and names are displayed to a set limit
+ // (kMaxDisplayCount) with the remaining count being returned in
+ // aNotDisplayed. Extension developers can listen for
+ // "newmail-notification-requested" and then make a decision about
+ // including a given author or not. As a result, it is possible that
+ // the resulting length of aAuthors will be 0.
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
+ uint32_t numNewKeys = 0;
+ if (NS_SUCCEEDED(rv) && db)
+ {
+ nsCOMPtr<nsIObserverService> os =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get proper l10n list separator -- ", " in English
+ nsCOMPtr<nsIStringBundle> bundle;
+ GetStringBundle(getter_AddRefs(bundle));
+ if (!bundle)
+ return NS_ERROR_FAILURE;
+
+ uint32_t *newMessageKeys;
+ rv = db->GetNewList(&numNewKeys, &newMessageKeys);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsString listSeparator;
+ bundle->GetStringFromName(u"macBiffNotification_separator", getter_Copies(listSeparator));
+
+ int32_t displayed = 0;
+ for (int32_t i = numNewKeys - 1; i >= 0; i--, aNewCount--)
+ {
+ if (0 == aNewCount || displayed == kMaxDisplayCount)
+ break;
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = db->GetMsgHdrForKey(newMessageKeys[i],
+ getter_AddRefs(hdr));
+ if (NS_SUCCEEDED(rv) && hdr)
+ {
+ nsString author;
+ rv = hdr->GetMime2DecodedAuthor(author);
+ if (NS_FAILED(rv))
+ continue;
+
+ nsString name;
+ ExtractName(DecodedHeader(author), name);
+
+ // Give extensions a chance to suppress notifications for this author
+ nsCOMPtr<nsISupportsPRBool> notify =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
+
+ notify->SetData(true);
+ os->NotifyObservers(notify, "newmail-notification-requested",
+ author.get());
+
+ bool includeSender;
+ notify->GetData(&includeSender);
+
+ // Don't add unwanted or duplicate names
+ if (includeSender && aAuthors.Find(name, true) == -1)
+ {
+ if (displayed > 0)
+ aAuthors.Append(listSeparator);
+ aAuthors.Append(name);
+ displayed++;
+ }
+ }
+ }
+ }
+ NS_Free(newMessageKeys);
+ }
+ *aNotDisplayed = aNewCount;
+ return rv;
+}
+
+nsresult
+nsMessengerOSXIntegration::GetFirstFolderWithNewMail(nsIMsgFolder* aFolder, nsCString& aFolderURI)
+{
+ // Find the subfolder in aFolder with new mail and return the folderURI
+ if (aFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // enumerate over the folders under this root folder till we find one with new mail....
+ nsCOMPtr<nsIArray> allFolders;
+ nsresult rv = aFolder->GetDescendants(getter_AddRefs(allFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = allFolders->Enumerate(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ nsCOMPtr<nsISupports> supports;
+ int32_t numNewMessages = 0;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv) && supports)
+ {
+ msgFolder = do_QueryInterface(supports, &rv);
+ if (msgFolder)
+ {
+ numNewMessages = 0;
+ msgFolder->GetNumNewMessages(false, &numNewMessages);
+ if (numNewMessages)
+ break; // kick out of the while loop
+ }
+ } // if we have a folder
+ } // if we have more potential folders to enumerate
+ } // if enumerator
+
+ if (msgFolder)
+ msgFolder->GetURI(aFolderURI);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Method implementations for mozINewMailListener
+ */
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnCountChanged(uint32_t count)
+{
+ mUnreadTotal = count;
+ BadgeDockIcon();
+ return NS_OK;
+}
diff --git a/mailnews/base/src/nsMsgMailSession.cpp b/mailnews/base/src/nsMsgMailSession.cpp
index 20d6f045c3..f9e396988c 100644
--- a/mailnews/base/src/nsMsgMailSession.cpp
+++ b/mailnews/base/src/nsMsgMailSession.cpp
@@ -353,9 +353,11 @@ NS_IMETHODIMP nsMsgMailSession::AddMsgWindow(nsIMsgWindow *msgWindow)
NS_IMETHODIMP nsMsgMailSession::RemoveMsgWindow(nsIMsgWindow *msgWindow)
{
mWindows.RemoveObject(msgWindow);
- // For suite, we don't want to disable mailnews when the last mail window
- // is closed (other component windows may still be open).
-#if !defined(MOZ_SUITE)
+ // Mac keeps a hidden window open so the app doesn't shut down when
+ // the last window is closed. So don't shutdown the account manager in that
+ // case. Similarly, for suite, we don't want to disable mailnews when the
+ // last mail window is closed.
+#if !defined(XP_MACOSX) && !defined(MOZ_SUITE)
if (!mWindows.Count())
{
nsresult rv;
diff --git a/mailnews/base/src/nsStatusBarBiffManager.cpp b/mailnews/base/src/nsStatusBarBiffManager.cpp
index 08dfb7b76e..27f393986d 100644
--- a/mailnews/base/src/nsStatusBarBiffManager.cpp
+++ b/mailnews/base/src/nsStatusBarBiffManager.cpp
@@ -136,11 +136,13 @@ nsresult nsStatusBarBiffManager::PlayBiffSound(const char *aPrefBranch)
}
}
}
+#ifndef XP_MACOSX
// if nothing played, play the default system sound
if (!customSoundPlayed) {
rv = mSound->PlayEventSound(nsISound::EVENT_NEW_MAIL_RECEIVED);
NS_ENSURE_SUCCESS(rv, rv);
}
+#endif
return rv;
}
diff --git a/mailnews/base/util/nsMsgUtils.cpp b/mailnews/base/util/nsMsgUtils.cpp
index 6fc884d510..44609bea00 100644
--- a/mailnews/base/util/nsMsgUtils.cpp
+++ b/mailnews/base/util/nsMsgUtils.cpp
@@ -611,7 +611,7 @@ nsresult NS_MsgCreatePathStringFromFolderURI(const char *aFolderURI,
? oldPath.FindChar('/', startSlashPos + 1) - 1 : oldPath.Length() - 1;
if (endSlashPos < 0)
endSlashPos = oldPath.Length();
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
bool isLocalUri = aScheme.EqualsLiteral("none") ||
aScheme.EqualsLiteral("pop3") ||
aScheme.EqualsLiteral("rss");
@@ -636,7 +636,7 @@ nsresult NS_MsgCreatePathStringFromFolderURI(const char *aFolderURI,
CopyUTF16toMUTF7(pathPiece, tmp);
CopyASCIItoUTF16(tmp, pathPiece);
}
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
// Don't hash path pieces because local mail folder uri's have already
// been hashed. We're only doing this on the mac to limit potential
// regressions.
diff --git a/mailnews/build/moz.build b/mailnews/build/moz.build
index 9561fd33d8..9620e8f3d4 100644
--- a/mailnews/build/moz.build
+++ b/mailnews/build/moz.build
@@ -35,6 +35,10 @@ if CONFIG['OS_ARCH'] == 'WINNT':
else:
OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ OS_LIBS += CONFIG['TK_LIBS']
+ OS_LIBS += ['-framework Cocoa']
+
LOCAL_INCLUDES += [
'/mailnews/addrbook/src',
'/mailnews/base/search/src',
diff --git a/mailnews/build/nsMailModule.cpp b/mailnews/build/nsMailModule.cpp
index dea26047a0..5b86b4c3ef 100644
--- a/mailnews/build/nsMailModule.cpp
+++ b/mailnews/build/nsMailModule.cpp
@@ -102,6 +102,9 @@
#ifdef XP_WIN
#include "nsMessengerWinIntegration.h"
#endif
+#ifdef XP_MACOSX
+#include "nsMessengerOSXIntegration.h"
+#endif
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
#include "nsMessengerUnixIntegration.h"
#endif
@@ -154,6 +157,12 @@
#include "nsAbOutlookDirectory.h"
#endif
+#ifdef XP_MACOSX
+#include "nsAbOSXDirectory.h"
+#include "nsAbOSXCard.h"
+#include "nsAbOSXDirFactory.h"
+#endif
+
////////////////////////////////////////////////////////////////////////////////
// bayesian spam filter includes
////////////////////////////////////////////////////////////////////////////////
@@ -370,6 +379,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgShutdownService)
#ifdef XP_WIN
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMessengerWinIntegration, Init)
#endif
+#ifdef XP_MACOSX
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMessengerOSXIntegration, Init)
+#endif
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMessengerUnixIntegration, Init)
#endif
@@ -427,6 +439,9 @@ NS_DEFINE_NAMED_CID(NS_MSGNOTIFICATIONSERVICE_CID);
#ifdef XP_WIN
NS_DEFINE_NAMED_CID(NS_MESSENGERWININTEGRATION_CID);
#endif
+#ifdef XP_MACOSX
+NS_DEFINE_NAMED_CID(NS_MESSENGEROSXINTEGRATION_CID);
+#endif
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
NS_DEFINE_NAMED_CID(NS_MESSENGERUNIXINTEGRATION_CID);
#endif
@@ -484,6 +499,12 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbView)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsMsgVCardService)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbLDIFService)
+#ifdef XP_MACOSX
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOSXDirectory)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOSXCard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAbOSXDirFactory)
+#endif
+
NS_DEFINE_NAMED_CID(NS_ABMANAGER_CID);
NS_DEFINE_NAMED_CID(NS_ABDIRECTORY_CID);
NS_DEFINE_NAMED_CID(NS_ABMDBDIRECTORY_CID);
@@ -515,6 +536,11 @@ NS_DEFINE_NAMED_CID(NS_ABLDAP_REPLICATIONQUERY_CID);
NS_DEFINE_NAMED_CID(NS_ABLDAP_PROCESSREPLICATIONDATA_CID);
#endif
NS_DEFINE_NAMED_CID(NS_ABDIRECTORYQUERYPROXY_CID);
+#ifdef XP_MACOSX
+NS_DEFINE_NAMED_CID(NS_ABOSXDIRECTORY_CID);
+NS_DEFINE_NAMED_CID(NS_ABOSXCARD_CID);
+NS_DEFINE_NAMED_CID(NS_ABOSXDIRFACTORY_CID);
+#endif
NS_DEFINE_NAMED_CID(NS_ABVIEW_CID);
NS_DEFINE_NAMED_CID(NS_MSGVCARDSERVICE_CID);
NS_DEFINE_NAMED_CID(NS_ABLDIFSERVICE_CID);
@@ -897,6 +923,9 @@ const mozilla::Module::CIDEntry kMailNewsCIDs[] = {
#ifdef XP_WIN
{ &kNS_MESSENGERWININTEGRATION_CID, false, NULL, nsMessengerWinIntegrationConstructor},
#endif
+#ifdef XP_MACOSX
+ { &kNS_MESSENGEROSXINTEGRATION_CID, false, NULL, nsMessengerOSXIntegrationConstructor},
+#endif
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
{ &kNS_MESSENGERUNIXINTEGRATION_CID, false, NULL, nsMessengerUnixIntegrationConstructor},
#endif
@@ -939,6 +968,11 @@ const mozilla::Module::CIDEntry kMailNewsCIDs[] = {
{ &kNS_ABLDAPDIRFACTORY_CID, false, NULL, nsAbLDAPDirFactoryConstructor },
#endif
{ &kNS_ABDIRECTORYQUERYPROXY_CID, false, NULL, nsAbDirectoryQueryProxyConstructor },
+#ifdef XP_MACOSX
+ { &kNS_ABOSXDIRECTORY_CID, false, NULL, nsAbOSXDirectoryConstructor },
+ { &kNS_ABOSXCARD_CID, false, NULL, nsAbOSXCardConstructor },
+ { &kNS_ABOSXDIRFACTORY_CID, false, NULL, nsAbOSXDirFactoryConstructor },
+#endif
{ &kNS_ABVIEW_CID, false, NULL, nsAbViewConstructor },
{ &kNS_MSGVCARDSERVICE_CID, false, NULL, nsMsgVCardServiceConstructor },
{ &kNS_ABLDIFSERVICE_CID, false, NULL, nsAbLDIFServiceConstructor },
@@ -1107,6 +1141,9 @@ const mozilla::Module::ContractIDEntry kMailNewsContracts[] = {
#ifdef XP_WIN
{ NS_MESSENGEROSINTEGRATION_CONTRACTID, &kNS_MESSENGERWININTEGRATION_CID },
#endif
+#ifdef XP_MACOSX
+ { NS_MESSENGEROSINTEGRATION_CONTRACTID, &kNS_MESSENGEROSXINTEGRATION_CID },
+#endif
#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_GTK2)
{ NS_MESSENGEROSINTEGRATION_CONTRACTID, &kNS_MESSENGERUNIXINTEGRATION_CID },
#endif
@@ -1153,6 +1190,11 @@ const mozilla::Module::ContractIDEntry kMailNewsContracts[] = {
#endif
{ NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &kNS_ABDIRECTORYQUERYPROXY_CID },
+#ifdef XP_MACOSX
+ { NS_ABOSXDIRECTORY_CONTRACTID, &kNS_ABOSXDIRECTORY_CID },
+ { NS_ABOSXCARD_CONTRACTID, &kNS_ABOSXCARD_CID },
+ { NS_ABOSXDIRFACTORY_CONTRACTID, &kNS_ABOSXDIRFACTORY_CID },
+#endif
{ NS_ABVIEW_CONTRACTID, &kNS_ABVIEW_CID },
{ NS_MSGVCARDSERVICE_CONTRACTID, &kNS_MSGVCARDSERVICE_CID },
{ NS_ABLDIFSERVICE_CONTRACTID, &kNS_ABLDIFSERVICE_CID },
@@ -1301,6 +1343,10 @@ static const mozilla::Module::CategoryEntry kMailNewsCategories[] = {
{ XPCOM_DIRECTORY_PROVIDER_CATEGORY, "mail-directory-provider", NS_MAILDIRPROVIDER_CONTRACTID },
{ "content-policy", NS_MSGCONTENTPOLICY_CONTRACTID, NS_MSGCONTENTPOLICY_CONTRACTID },
MAILNEWSDLF_CATEGORIES
+#ifdef XP_MACOSX
+ { "app-startup", NS_MESSENGEROSINTEGRATION_CONTRACTID, "service," NS_MESSENGEROSINTEGRATION_CONTRACTID}
+,
+#endif
// Address Book Entries
{ "command-line-handler", "m-addressbook", NS_ABMANAGERSTARTUPHANDLER_CONTRACTID },
// Bayesian Filter Entries
diff --git a/mailnews/compose/src/moz.build b/mailnews/compose/src/moz.build
index 831a0340f4..dcb9960a66 100644
--- a/mailnews/compose/src/moz.build
+++ b/mailnews/compose/src/moz.build
@@ -35,6 +35,16 @@ SOURCES += [
'nsURLFetcher.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsMsgAppleDoubleEncode.cpp',
+ 'nsMsgAppleEncode.cpp',
+ ]
+ EXPORTS += [
+ 'nsMsgAppleCodes.h',
+ 'nsMsgAppleDouble.h',
+ ]
+
EXTRA_COMPONENTS += [
'nsSMTPProtocolHandler.js',
'nsSMTPProtocolHandler.manifest',
diff --git a/mailnews/compose/src/nsMsgAppleCodes.h b/mailnews/compose/src/nsMsgAppleCodes.h
new file mode 100644
index 0000000000..d8ca2f327c
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleCodes.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 4; 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/. */
+
+/*
+** AD_Codes.h
+**
+** ---------------
+**
+** Head file for Apple Decode/Encode essential codes.
+**
+**
+*/
+
+#ifndef ad_codes_h
+#define ad_codes_h
+
+/*
+** applefile definitions used
+*/
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=mac68k
+#endif
+
+#define APPLESINGLE_MAGIC 0x00051600L
+#define APPLEDOUBLE_MAGIC 0x00051607L
+#define VERSION 0x00020000
+
+#define NUM_ENTRIES 6
+
+#define ENT_DFORK 1L
+#define ENT_RFORK 2L
+#define ENT_NAME 3L
+#define ENT_COMMENT 4L
+#define ENT_DATES 8L
+#define ENT_FINFO 9L
+#define CONVERT_TIME 1265437696L
+
+/*
+** data type used in the encoder/decoder.
+*/
+typedef struct ap_header
+{
+ int32_t magic;
+ int32_t version;
+ char fill[16];
+ int16_t entries;
+
+} ap_header;
+
+typedef struct ap_entry
+{
+ int32_t id;
+ int32_t offset;
+ int32_t length;
+
+} ap_entry;
+
+typedef struct ap_dates
+{
+ int32_t create, modify, backup, access;
+
+} ap_dates;
+
+typedef struct myFInfo /* the mac FInfo structure for the cross platform. */
+{
+ int32_t fdType, fdCreator;
+ int16_t fdFlags;
+ int32_t fdLocation; /* it really should be a pointer, but just a place-holder */
+ int16_t fdFldr;
+
+} myFInfo;
+
+PR_BEGIN_EXTERN_C
+/*
+** string utils.
+*/
+int write_stream(appledouble_encode_object *p_ap_encode_obj, const char *s,int len);
+
+int fill_apple_mime_header(appledouble_encode_object *p_ap_encode_obj);
+int ap_encode_file_infor(appledouble_encode_object *p_ap_encode_obj);
+int ap_encode_header(appledouble_encode_object* p_ap_encode_obj, bool firstTime);
+int ap_encode_data( appledouble_encode_object* p_ap_encode_obj, bool firstTime);
+
+/*
+** the prototypes for the ap_decoder.
+*/
+int fetch_a_line(appledouble_decode_object* p_ap_decode_obj, char *buff);
+int ParseFileHeader(appledouble_decode_object* p_ap_decode_obj);
+int ap_seek_part_start(appledouble_decode_object* p_ap_decode_obj);
+void parse_param(char *p, char **param, char**define, char **np);
+int ap_seek_to_boundary(appledouble_decode_object* p_ap_decode_obj, bool firstime);
+int ap_parse_header(appledouble_decode_object* p_ap_decode_obj,bool firstime);
+int ap_decode_file_infor(appledouble_decode_object* p_ap_decode_obj);
+int ap_decode_process_header(appledouble_decode_object* p_ap_decode_obj, bool firstime);
+int ap_decode_process_data( appledouble_decode_object* p_ap_decode_obj, bool firstime);
+
+PR_END_EXTERN_C
+
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=reset
+#endif
+
+#endif /* ad_codes_h */
diff --git a/mailnews/compose/src/nsMsgAppleDouble.h b/mailnews/compose/src/nsMsgAppleDouble.h
new file mode 100644
index 0000000000..f4ae934add
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleDouble.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C; tab-width: 4; 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/. */
+
+/*
+* AppleDouble.h
+* -------------
+*
+* The header file for a stream based apple single/double encodor/decodor.
+*
+* 2aug95 mym
+*
+*/
+
+
+#ifndef AppleDouble_h
+#define AppleDouble_h
+
+#include "msgCore.h"
+#include "nsComposeStrings.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+
+#include <CoreServices/CoreServices.h>
+
+#define NOERR 0
+#define errDone 1
+ /* Done with current operation. */
+#define errEOB 2
+ /* End of a buffer. */
+#define errEOP 3
+ /* End of a Part. */
+
+
+#define errFileOpen NS_ERROR_GET_CODE(NS_MSG_UNABLE_TO_OPEN_TMP_FILE)
+#define errFileWrite -202 /*Error writing temporary file.*/
+#define errUsrCancel -2 /*MK_INTERRUPTED */
+#define errDecoding -1
+
+/*
+** The envirment block data type.
+*/
+enum
+{
+ kInit,
+ kDoingHeaderPortion,
+ kDoneHeaderPortion,
+ kDoingDataPortion,
+ kDoneDataPortion
+};
+
+typedef struct _appledouble_encode_object
+{
+ char fname[256];
+ FSIORefNum fileId; /* the id for the open file (data/resource fork) */
+
+ int state;
+ int text_file_type; /* if the file has a text file type with it. */
+ char *boundary; /* the boundary string. */
+
+ int status; /* the error code if anyerror happens. */
+ char b_overflow[200];
+ int s_overflow;
+
+ int state64; /* the left over state of base64 enocding */
+ int ct; /* the character count of base64 encoding */
+ int c1, c2; /* the left of the last base64 encoding */
+
+ char *outbuff; /* the outbuff by the caller. */
+ int s_outbuff; /* the size of the buffer. */
+ int pos_outbuff; /* the offset in the current buffer. */
+
+} appledouble_encode_object;
+
+/* The possible content transfer encodings */
+
+enum
+{
+ kEncodeNone,
+ kEncodeQP,
+ kEncodeBase64,
+ kEncodeUU
+};
+
+enum
+{
+ kGeneralMine,
+ kAppleDouble,
+ kAppleSingle
+};
+
+enum
+{
+ kInline,
+ kDontCare
+};
+
+enum
+{
+ kHeaderPortion,
+ kDataPortion
+};
+
+/* the decode states. */
+enum
+{
+ kBeginParseHeader = 3,
+ kParsingHeader,
+ kBeginSeekBoundary,
+ kSeekingBoundary,
+ kBeginHeaderPortion,
+ kProcessingHeaderPortion,
+ kBeginDataPortion,
+ kProcessingDataPortion,
+ kFinishing
+};
+
+/* uuencode states */
+enum
+{
+ kWaitingForBegin = (int) 0,
+ kBegin,
+ kMainBody,
+ kEnd
+};
+
+typedef struct _appledouble_decode_object
+{
+ int is_binary;
+ int is_apple_single; /* if the object encoded is in apple single */
+ int write_as_binhex;
+
+ int messagetype;
+ char* boundary0; /* the boundary for the enclosure. */
+ int deposition; /* the deposition. */
+ int encoding; /* the encoding method. */
+ int which_part;
+
+ char fname[256];
+ // nsIOFileStream *fileSpec; /* the stream for data fork work. */
+
+ int state;
+
+ int rksize; /* the resource fork size count. */
+ int dksize; /* the data fork size count. */
+
+ int status; /* the error code if anyerror happens. */
+ char b_leftover[256];
+ int s_leftover;
+
+ int encode; /* the encode type of the message. */
+ int state64; /* the left over state of base64 enocding */
+ int left; /* the character count of base64 encoding */
+ int c[4]; /* the left of the last base64 encoding */
+ int uu_starts_line; /* is decoder at the start of a line? (uuencode) */
+ int uu_state; /* state w/r/t the uuencode body */
+ int uu_bytes_written; /* bytes written from the current tuple (uuencode) */
+ int uu_line_bytes; /* encoded bytes remaining in the current line (uuencode) */
+
+ char *inbuff; /* the outbuff by the caller. */
+ int s_inbuff; /* the size of the buffer. */
+ int pos_inbuff; /* the offset in the current buffer. */
+
+
+ nsCOMPtr <nsIFile> tmpFile; /* the temp file to hold the decode data fork */
+ /* when doing the binhex exporting. */
+ nsCOMPtr <nsIOutputStream> tmpFileStream; /* The output File Stream */
+ int32_t data_size; /* the size of the data in the tmp file. */
+
+} appledouble_decode_object;
+
+
+/*
+** The protypes.
+*/
+
+PR_BEGIN_EXTERN_C
+
+int ap_encode_init(appledouble_encode_object *p_ap_encode_obj,
+ const char* fname,
+ char* separator);
+
+int ap_encode_next(appledouble_encode_object* p_ap_encode_obj,
+ char *to_buff,
+ int32_t buff_size,
+ int32_t* real_size);
+
+int ap_encode_end(appledouble_encode_object* p_ap_encode_obj,
+ bool is_aborting);
+
+int ap_decode_init(appledouble_decode_object* p_ap_decode_obj,
+ bool is_apple_single,
+ bool write_as_bin_hex,
+ void *closure);
+
+int ap_decode_next(appledouble_decode_object* p_ap_decode_obj,
+ char *in_buff,
+ int32_t buff_size);
+
+int ap_decode_end(appledouble_decode_object* p_ap_decode_obj,
+ bool is_aborting);
+
+PR_END_EXTERN_C
+
+#endif
diff --git a/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp b/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp
new file mode 100644
index 0000000000..4d71781233
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+/*
+*
+* apple-double.c
+* --------------
+*
+* The codes to do apple double encoding/decoding.
+*
+* 02aug95 mym created.
+*
+*/
+#include "nsID.h"
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsMsgAppleDouble.h"
+#include "nsMsgAppleCodes.h"
+#include "nsMsgCompUtils.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsMimeTypes.h"
+#include "prmem.h"
+#include "nsNetUtil.h"
+
+
+void
+MacGetFileType(nsIFile *fs,
+ bool *useDefault,
+ char **fileType,
+ char **encoding)
+{
+ if ((fs == NULL) || (fileType == NULL) || (encoding == NULL))
+ return;
+
+ bool exists = false;
+ fs->Exists(&exists);
+ if (!exists)
+ return;
+
+ *useDefault = TRUE;
+ *fileType = NULL;
+ *encoding = NULL;
+
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(fs);
+ FSRef fsRef;
+ FSCatalogInfo catalogInfo;
+ OSErr err = errFileOpen;
+ if (NS_SUCCEEDED(macFile->GetFSRef(&fsRef)))
+ err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, nullptr, nullptr, nullptr);
+
+ if ( (err != noErr) || (((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'TEXT') )
+ *fileType = strdup(APPLICATION_OCTET_STREAM);
+ else
+ {
+ // At this point, we should call the mime service and
+ // see what we can find out?
+ nsresult rv;
+ nsCOMPtr <nsIURI> tURI;
+ if (NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(tURI), fs)) && tURI)
+ {
+ nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && mimeFinder)
+ {
+ nsAutoCString mimeType;
+ rv = mimeFinder->GetTypeFromURI(tURI, mimeType);
+ if (NS_SUCCEEDED(rv))
+ {
+ *fileType = ToNewCString(mimeType);
+ return;
+ }
+ }
+ }
+
+ // If we hit here, return something...default to this...
+ *fileType = strdup(APPLICATION_OCTET_STREAM);
+ }
+}
+
+//#pragma cplusplus reset
+
+/*
+* ap_encode_init
+* --------------
+*
+* Setup the encode envirment
+*/
+
+int ap_encode_init( appledouble_encode_object *p_ap_encode_obj,
+ const char *fname,
+ char *separator)
+{
+ nsCOMPtr <nsIFile> myFile;
+ NS_NewNativeLocalFile(nsDependentCString(fname), true, getter_AddRefs(myFile));
+ bool exists;
+ if (myFile && NS_SUCCEEDED(myFile->Exists(&exists)) && !exists)
+ return -1;
+
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(myFile);
+ nsAutoCString path;
+ macFile->GetNativePath(path);
+
+ memset(p_ap_encode_obj, 0, sizeof(appledouble_encode_object));
+
+ /*
+ ** Fill out the source file inforamtion.
+ */
+ memcpy(p_ap_encode_obj->fname, path.get(), path.Length());
+ p_ap_encode_obj->fname[path.Length()] = '\0';
+
+ p_ap_encode_obj->boundary = strdup(separator);
+ return noErr;
+}
+
+/*
+** ap_encode_next
+** --------------
+**
+** return :
+** noErr : everything is ok
+** errDone : when encoding is done.
+** errors : otherwise.
+*/
+int ap_encode_next(
+ appledouble_encode_object* p_ap_encode_obj,
+ char *to_buff,
+ int32_t buff_size,
+ int32_t* real_size)
+{
+ int status;
+
+ /*
+ ** install the out buff now.
+ */
+ p_ap_encode_obj->outbuff = to_buff;
+ p_ap_encode_obj->s_outbuff = buff_size;
+ p_ap_encode_obj->pos_outbuff = 0;
+
+ /*
+ ** first copy the outstandind data in the overflow buff to the out buffer.
+ */
+ if (p_ap_encode_obj->s_overflow)
+ {
+ status = write_stream(p_ap_encode_obj,
+ (const char*)(p_ap_encode_obj->b_overflow),
+ p_ap_encode_obj->s_overflow);
+ if (status != noErr)
+ return status;
+
+ p_ap_encode_obj->s_overflow = 0;
+ }
+
+ /*
+ ** go the next processing stage based on the current state.
+ */
+ switch (p_ap_encode_obj->state)
+ {
+ case kInit:
+ /*
+ ** We are in the starting position, fill out the header.
+ */
+ status = fill_apple_mime_header(p_ap_encode_obj);
+ if (status != noErr)
+ break; /* some error happens */
+
+ p_ap_encode_obj->state = kDoingHeaderPortion;
+ status = ap_encode_header(p_ap_encode_obj, true);
+ /* it is the first time to calling */
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneHeaderPortion;
+ }
+ else
+ {
+ break; /* we need more work on header portion. */
+ }
+
+ /*
+ ** we are done with the header, so let's go to the data port.
+ */
+ p_ap_encode_obj->state = kDoingDataPortion;
+ status = ap_encode_data(p_ap_encode_obj, true);
+ /* it is first time call do data portion */
+
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneDataPortion;
+ status = noErr;
+ }
+ break;
+
+ case kDoingHeaderPortion:
+
+ status = ap_encode_header(p_ap_encode_obj, false);
+ /* continue with the header portion. */
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneHeaderPortion;
+ }
+ else
+ {
+ break; /* we need more work on header portion. */
+ }
+
+ /*
+ ** start the data portion.
+ */
+ p_ap_encode_obj->state = kDoingDataPortion;
+ status = ap_encode_data(p_ap_encode_obj, true);
+ /* it is the first time calling */
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneDataPortion;
+ status = noErr;
+ }
+ break;
+
+ case kDoingDataPortion:
+
+ status = ap_encode_data(p_ap_encode_obj, false);
+ /* it is not the first time */
+
+ if (status == errDone)
+ {
+ p_ap_encode_obj->state = kDoneDataPortion;
+ status = noErr;
+ }
+ break;
+
+ case kDoneDataPortion:
+ status = errDone; /* we are really done. */
+
+ break;
+ }
+
+ *real_size = p_ap_encode_obj->pos_outbuff;
+ return status;
+}
+
+/*
+** ap_encode_end
+** -------------
+**
+** clear the apple encoding.
+*/
+
+int ap_encode_end(
+ appledouble_encode_object *p_ap_encode_obj,
+ bool is_aborting)
+{
+ /*
+ ** clear up the apple doubler.
+ */
+ if (p_ap_encode_obj == NULL)
+ return noErr;
+
+ if (p_ap_encode_obj->fileId) /* close the file if it is open. */
+ ::FSCloseFork(p_ap_encode_obj->fileId);
+
+ PR_FREEIF(p_ap_encode_obj->boundary); /* the boundary string. */
+
+ return noErr;
+}
diff --git a/mailnews/compose/src/nsMsgAppleEncode.cpp b/mailnews/compose/src/nsMsgAppleEncode.cpp
new file mode 100644
index 0000000000..27e39a8cda
--- /dev/null
+++ b/mailnews/compose/src/nsMsgAppleEncode.cpp
@@ -0,0 +1,703 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+/*
+ *
+ * apple_double_encode.c
+ * ---------------------
+ *
+ * The routines doing the Apple Double Encoding.
+ *
+ * 2aug95 mym Created.
+ *
+ */
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+#include "nsMimeTypes.h"
+#include "prprf.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgAppleDouble.h"
+#include "nsMsgAppleCodes.h"
+#include "nsILocalFileMac.h"
+
+/*
+** Local Functions prototypes.
+*/
+static int output64chunk( appledouble_encode_object* p_ap_encode_obj,
+ int c1, int c2, int c3, int pads);
+
+static int to64(appledouble_encode_object* p_ap_encode_obj,
+ char *p,
+ int in_size);
+
+static int finish64(appledouble_encode_object* p_ap_encode_obj);
+
+
+#define BUFF_LEFT(p) ((p)->s_outbuff - (p)->pos_outbuff)
+
+/*
+** write_stream.
+*/
+int write_stream(
+ appledouble_encode_object *p_ap_encode_obj,
+ const char *out_string,
+ int len)
+{
+ if (p_ap_encode_obj->pos_outbuff + len < p_ap_encode_obj->s_outbuff)
+ {
+ memcpy(p_ap_encode_obj->outbuff + p_ap_encode_obj->pos_outbuff,
+ out_string,
+ len);
+ p_ap_encode_obj->pos_outbuff += len;
+ return noErr;
+ }
+ else
+ {
+ /*
+ ** If the buff doesn't have enough space, use the overflow buffer then.
+ */
+ int s_len = p_ap_encode_obj->s_outbuff - p_ap_encode_obj->pos_outbuff;
+
+ memcpy(p_ap_encode_obj->outbuff + p_ap_encode_obj->pos_outbuff,
+ out_string,
+ s_len);
+ memcpy(p_ap_encode_obj->b_overflow + p_ap_encode_obj->s_overflow,
+ out_string + s_len,
+ p_ap_encode_obj->s_overflow += (len - s_len));
+ p_ap_encode_obj->pos_outbuff += s_len;
+ return errEOB;
+ }
+}
+
+int fill_apple_mime_header(
+ appledouble_encode_object *p_ap_encode_obj)
+{
+ int status;
+
+ char tmpstr[266];
+
+#if 0
+// strcpy(tmpstr, "Content-Type: multipart/mixed; boundary=\"-\"\n\n---\n");
+// status = write_stream(p_ap_encode_env,
+// tmpstr,
+// strlen(tmpstr));
+// if (status != noErr)
+// return status;
+
+ PR_snprintf(tmpstr, sizeof(tmpstr),
+ "Content-Type: multipart/appledouble; boundary=\"=\"; name=\"");
+ status = write_stream(p_ap_encode_obj, (const char*)tmpstr, strlen(tmpstr));
+ if (status != noErr)
+ return status;
+
+ status = write_stream(p_ap_encode_obj,
+ p_ap_encode_obj->fname,
+ strlen(p_ap_encode_obj->fname));
+ if (status != noErr)
+ return status;
+
+ PR_snprintf(tmpstr, sizeof(tmpstr),
+ "\"\r\nContent-Disposition: inline; filename=\"%s\"\r\n\r\n\r\n--=\r\n",
+ p_ap_encode_obj->fname);
+#endif /* 0 */
+ PR_snprintf(tmpstr, sizeof(tmpstr), "--%s" CRLF, p_ap_encode_obj->boundary);
+ status = write_stream(p_ap_encode_obj, (const char*)tmpstr, strlen(tmpstr));
+ return status;
+}
+
+int ap_encode_file_infor(
+ appledouble_encode_object *p_ap_encode_obj)
+{
+ ap_header head;
+ ap_entry entries[NUM_ENTRIES];
+ ap_dates dates;
+ short i;
+ long comlen;
+ char comment[256];
+ int status;
+
+ nsCOMPtr <nsIFile> resFile;
+ NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true,
+ getter_AddRefs(resFile));
+ if (!resFile)
+ return errFileOpen;
+
+ FSRef ref;
+ nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(resFile);
+ if (NS_FAILED(macFile->GetFSRef(&ref)))
+ return errFileOpen;
+
+ FSCatalogInfo catalogInfo;
+ if (::FSGetCatalogInfo(&ref, kFSCatInfoFinderInfo, &catalogInfo, nullptr, nullptr, nullptr) != noErr)
+ {
+ return errFileOpen;
+ }
+
+ /* get a file comment, if possible */
+#if 1
+ // Carbon doesn't support GetWDInfo(). (Bug 555684)
+
+ // not sure why working directories are needed here...
+ comlen = 0;
+#else
+ long procID;
+ procID = 0;
+ GetWDInfo(p_ap_encode_obj->vRefNum, &fpb->ioVRefNum, &fpb->ioDirID, &procID);
+ IOParam vinfo;
+ memset((void *) &vinfo, '\0', sizeof (vinfo));
+ GetVolParmsInfoBuffer vp;
+ vinfo.ioCompletion = nil;
+ vinfo.ioVRefNum = fpb->ioVRefNum;
+ vinfo.ioBuffer = (Ptr) &vp;
+ vinfo.ioReqCount = sizeof (vp);
+ comlen = 0;
+ if (PBHGetVolParmsSync((HParmBlkPtr) &vinfo) == noErr &&
+ ((vp.vMAttrib >> bHasDesktopMgr) & 1))
+ {
+ DTPBRec dtp;
+ memset((void *) &dtp, '\0', sizeof (dtp));
+ dtp.ioVRefNum = fpb->ioVRefNum;
+ if (PBDTGetPath(&dtp) == noErr)
+ {
+ dtp.ioCompletion = nil;
+ dtp.ioDTBuffer = (Ptr) comment;
+ dtp.ioNamePtr = fpb->ioNamePtr;
+ dtp.ioDirID = fpb->ioFlParID;
+ if (PBDTGetCommentSync(&dtp) == noErr)
+ comlen = dtp.ioDTActCount;
+ }
+ }
+#endif /* ! 1 */
+
+ /* write header */
+// head.magic = dfork ? APPLESINGLE_MAGIC : APPLEDOUBLE_MAGIC;
+ head.magic = APPLEDOUBLE_MAGIC; /* always do apple double */
+ head.version = VERSION;
+ memset(head.fill, '\0', sizeof (head.fill));
+ head.entries = NUM_ENTRIES - 1;
+ status = to64(p_ap_encode_obj,
+ (char *) &head,
+ sizeof (head));
+ if (status != noErr)
+ return status;
+
+ /* write entry descriptors */
+ nsAutoCString leafname;
+ macFile->GetNativeLeafName(leafname);
+ entries[0].offset = sizeof (head) + sizeof (ap_entry) * head.entries;
+ entries[0].id = ENT_NAME;
+ entries[0].length = leafname.Length();
+ entries[1].id = ENT_FINFO;
+ entries[1].length = sizeof (FInfo) + sizeof (FXInfo);
+ entries[2].id = ENT_DATES;
+ entries[2].length = sizeof (ap_dates);
+ entries[3].id = ENT_COMMENT;
+ entries[3].length = comlen;
+ entries[4].id = ENT_RFORK;
+ entries[4].length = catalogInfo.rsrcLogicalSize;
+ entries[5].id = ENT_DFORK;
+ entries[5].length = catalogInfo.dataLogicalSize;
+
+ /* correct the link in the entries. */
+ for (i = 1; i < NUM_ENTRIES; ++i)
+ {
+ entries[i].offset = entries[i-1].offset + entries[i-1].length;
+ }
+ status = to64(p_ap_encode_obj,
+ (char *) entries,
+ sizeof (ap_entry) * head.entries);
+ if (status != noErr)
+ return status;
+
+ /* write name */
+ status = to64(p_ap_encode_obj,
+ (char *) leafname.get(),
+ leafname.Length());
+ if (status != noErr)
+ return status;
+
+ /* write finder info */
+ status = to64(p_ap_encode_obj,
+ (char *) &catalogInfo.finderInfo,
+ sizeof (FInfo));
+ if (status != noErr)
+ return status;
+
+ status = to64(p_ap_encode_obj,
+ (char *) &catalogInfo.extFinderInfo,
+ sizeof (FXInfo));
+ if (status != noErr)
+ return status;
+
+ /* write dates */
+ dates.create = catalogInfo.createDate.lowSeconds + CONVERT_TIME;
+ dates.modify = catalogInfo.contentModDate.lowSeconds + CONVERT_TIME;
+ dates.backup = catalogInfo.backupDate.lowSeconds + CONVERT_TIME;
+ dates.access = catalogInfo.accessDate.lowSeconds + CONVERT_TIME;
+ status = to64(p_ap_encode_obj,
+ (char *) &dates,
+ sizeof (ap_dates));
+ if (status != noErr)
+ return status;
+
+ /* write comment */
+ if (comlen)
+ {
+ status = to64(p_ap_encode_obj,
+ comment,
+ comlen * sizeof(char));
+ }
+ /*
+ ** Get some help information on deciding the file type.
+ */
+ if (((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'TEXT' ||
+ ((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'text')
+ {
+ p_ap_encode_obj->text_file_type = true;
+ }
+
+ return status;
+}
+/*
+** ap_encode_header
+**
+** encode the file header and the resource fork.
+**
+*/
+int ap_encode_header(
+ appledouble_encode_object* p_ap_encode_obj,
+ bool firstime)
+{
+ char rd_buff[256];
+ FSIORefNum fileId;
+ OSErr retval = noErr;
+ int status;
+ ByteCount inCount;
+
+ if (firstime)
+ {
+ PL_strcpy(rd_buff,
+ "Content-Type: application/applefile\r\nContent-Transfer-Encoding: base64\r\n\r\n");
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+ if (status != noErr)
+ return status;
+
+ status = ap_encode_file_infor(p_ap_encode_obj);
+ if (status != noErr)
+ return status;
+
+ /*
+ ** preparing to encode the resource fork.
+ */
+ nsCOMPtr <nsIFile> myFile;
+ NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true, getter_AddRefs(myFile));
+ if (!myFile)
+ return errFileOpen;
+
+ FSRef ref;
+ nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(myFile);
+ if (NS_FAILED(macFile->GetFSRef(&ref)))
+ return errFileOpen;
+
+ HFSUniStr255 forkName;
+ ::FSGetResourceForkName(&forkName);
+ retval = ::FSOpenFork(&ref, forkName.length, forkName.unicode, fsRdPerm, &p_ap_encode_obj->fileId);
+ if (retval != noErr)
+ return retval;
+ }
+
+ fileId = p_ap_encode_obj->fileId;
+ while (retval == noErr)
+ {
+ if (BUFF_LEFT(p_ap_encode_obj) < 400)
+ break;
+
+ inCount = 0;
+ retval = ::FSReadFork(fileId, fsAtMark, 0, 256, rd_buff, &inCount);
+ if (inCount)
+ {
+ status = to64(p_ap_encode_obj,
+ rd_buff,
+ inCount);
+ if (status != noErr)
+ return status;
+ }
+ }
+
+ if (retval == eofErr)
+ {
+ ::FSCloseFork(fileId);
+ p_ap_encode_obj->fileId = 0;
+
+ status = finish64(p_ap_encode_obj);
+ if (status != noErr)
+ return status;
+
+ /*
+ ** write out the boundary
+ */
+ PR_snprintf(rd_buff, sizeof(rd_buff),
+ CRLF "--%s" CRLF,
+ p_ap_encode_obj->boundary);
+
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+ if (status == noErr)
+ status = errDone;
+ }
+ return status;
+}
+
+#if 0
+// This is unused for now and Clang complains about that is it is ifdefed out
+static void replace(char *p, int len, char frm, char to)
+{
+ for (; len > 0; len--, p++)
+ if (*p == frm) *p = to;
+}
+#endif
+
+/* Description of the various file formats and their magic numbers */
+struct magic
+{
+ const char *name; /* Name of the file format */
+ const char *num; /* The magic number */
+ int len; /* Length (0 means strlen(magicnum)) */
+};
+
+/* The magic numbers of the file formats we know about */
+static struct magic magic[] =
+{
+ { "image/gif", "GIF", 0 },
+ { "image/jpeg", "\377\330\377", 0 },
+ { "video/mpeg", "\0\0\001\263", 4 },
+ { "application/postscript", "%!", 0 },
+};
+static int num_magic = MOZ_ARRAY_LENGTH(magic);
+
+static const char *text_type = TEXT_PLAIN; /* the text file type. */
+static const char *default_type = APPLICATION_OCTET_STREAM;
+
+
+/*
+ * Determins the format of the file "inputf". The name
+ * of the file format (or NULL on error) is returned.
+ */
+static const char *magic_look(char *inbuff, int numread)
+{
+ int i, j;
+
+ for (i=0; i<num_magic; i++)
+ {
+ if (magic[i].len == 0)
+ magic[i].len = strlen(magic[i].num);
+ }
+
+ for (i=0; i<num_magic; i++)
+ {
+ if (numread >= magic[i].len)
+ {
+ for (j=0; j<magic[i].len; j++)
+ {
+ if (inbuff[j] != magic[i].num[j]) break;
+ }
+
+ if (j == magic[i].len)
+ return magic[i].name;
+ }
+ }
+
+ return default_type;
+}
+/*
+** ap_encode_data
+**
+** ---------------
+**
+** encode on the data fork.
+**
+*/
+int ap_encode_data(
+ appledouble_encode_object* p_ap_encode_obj,
+ bool firstime)
+{
+ char rd_buff[256];
+ FSIORefNum fileId;
+ OSErr retval = noErr;
+ ByteCount in_count;
+ int status;
+
+ if (firstime)
+ {
+ const char* magic_type;
+
+ /*
+ ** preparing to encode the data fork.
+ */
+ nsCOMPtr <nsIFile> resFile;
+ NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true,
+ getter_AddRefs(resFile));
+ if (!resFile)
+ return errFileOpen;
+
+ FSRef ref;
+ nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(resFile);
+ if (NS_FAILED(macFile->GetFSRef(&ref)))
+ return errFileOpen;
+
+ HFSUniStr255 forkName;
+ ::FSGetDataForkName(&forkName);
+ retval = ::FSOpenFork(&ref, forkName.length, forkName.unicode, fsRdPerm, &fileId);
+ if (retval != noErr)
+ return retval;
+
+ p_ap_encode_obj->fileId = fileId;
+
+
+ if (!p_ap_encode_obj->text_file_type)
+ {
+ /*
+ ** do a smart check for the file type.
+ */
+ in_count = 0;
+ retval = ::FSReadFork(fileId, fsFromStart, 0, 256, rd_buff, &in_count);
+ magic_type = magic_look(rd_buff, in_count);
+
+ /* don't forget to rewind the index to start point. */
+ ::FSSetForkPosition(fileId, fsFromStart, 0);
+ /* and reset retVal just in case... */
+ if (retval == eofErr)
+ retval = noErr;
+ }
+ else
+ {
+ magic_type = text_type; /* we already know it is a text type. */
+ }
+
+ /*
+ ** the data portion header information.
+ */
+ nsAutoCString leafName;
+ resFile->GetNativeLeafName(leafName);
+ PR_snprintf(rd_buff, sizeof(rd_buff),
+ "Content-Type: %s; name=\"%s\"" CRLF "Content-Transfer-Encoding: base64" CRLF "Content-Disposition: inline; filename=\"%s\"" CRLF CRLF,
+ magic_type,
+ leafName.get(),
+ leafName.get());
+
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+ if (status != noErr)
+ return status;
+ }
+
+ while (retval == noErr)
+ {
+ if (BUFF_LEFT(p_ap_encode_obj) < 400)
+ break;
+
+ in_count = 0;
+ retval = ::FSReadFork(p_ap_encode_obj->fileId, fsAtMark, 0, 256, rd_buff, &in_count);
+ if (in_count)
+ {
+#if 0
+/* replace(rd_buff, in_count, '\r', '\n'); */
+#endif
+/* ** may be need to do character set conversion here for localization. ** */
+ status = to64(p_ap_encode_obj,
+ rd_buff,
+ in_count);
+ if (status != noErr)
+ return status;
+ }
+ }
+
+ if (retval == eofErr)
+ {
+ ::FSCloseFork(p_ap_encode_obj->fileId);
+ p_ap_encode_obj->fileId = 0;
+
+ status = finish64(p_ap_encode_obj);
+ if (status != noErr)
+ return status;
+
+ /* write out the boundary */
+
+ PR_snprintf(rd_buff, sizeof(rd_buff),
+ CRLF "--%s--" CRLF CRLF,
+ p_ap_encode_obj->boundary);
+
+ status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff));
+
+ if (status == noErr)
+ status = errDone;
+ }
+ return status;
+}
+
+static char basis_64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*
+** convert the stream in the inbuff to 64 format and put it in the out buff.
+** To make the life easier, the caller will responcable of the cheking of the outbuff's bundary.
+*/
+static int
+to64(appledouble_encode_object* p_ap_encode_obj,
+ char *p,
+ int in_size)
+{
+ int status;
+ int c1, c2, c3, ct;
+ unsigned char *inbuff = (unsigned char*)p;
+
+ ct = p_ap_encode_obj->ct; /* the char count left last time. */
+
+ /*
+ ** resume the left state of the last conversion.
+ */
+ switch (p_ap_encode_obj->state64)
+ {
+ case 0:
+ p_ap_encode_obj->c1 = c1 = *inbuff ++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 1;
+ return noErr;
+ }
+ p_ap_encode_obj->c2 = c2 = *inbuff ++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 2;
+ return noErr;
+ }
+ c3 = *inbuff ++; --in_size;
+ break;
+ case 1:
+ c1 = p_ap_encode_obj->c1;
+ p_ap_encode_obj->c2 = c2 = *inbuff ++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 2;
+ return noErr;
+ }
+ c3 = *inbuff ++; --in_size;
+ break;
+ case 2:
+ c1 = p_ap_encode_obj->c1;
+ c2 = p_ap_encode_obj->c2;
+ c3 = *inbuff ++; --in_size;
+ break;
+ }
+
+ while (in_size >= 0)
+ {
+ status = output64chunk(p_ap_encode_obj,
+ c1,
+ c2,
+ c3,
+ 0);
+ if (status != noErr)
+ return status;
+
+ ct += 4;
+ if (ct > 71)
+ {
+ status = write_stream(p_ap_encode_obj,
+ CRLF,
+ 2);
+ if (status != noErr)
+ return status;
+
+ ct = 0;
+ }
+
+ if (in_size <= 0)
+ {
+ p_ap_encode_obj->state64 = 0;
+ break;
+ }
+
+ c1 = (int)*inbuff++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->c1 = c1;
+ p_ap_encode_obj->state64 = 1;
+ break;
+ }
+ c2 = *inbuff++;
+ if (--in_size <= 0)
+ {
+ p_ap_encode_obj->c1 = c1;
+ p_ap_encode_obj->c2 = c2;
+ p_ap_encode_obj->state64 = 2;
+ break;
+ }
+ c3 = *inbuff++;
+ in_size--;
+ }
+ p_ap_encode_obj->ct = ct;
+ return status;
+}
+
+/*
+** clear the left base64 encodes.
+*/
+static int
+finish64(appledouble_encode_object* p_ap_encode_obj)
+{
+ int status;
+
+ switch (p_ap_encode_obj->state64)
+ {
+ case 0:
+ break;
+ case 1:
+ status = output64chunk(p_ap_encode_obj,
+ p_ap_encode_obj->c1,
+ 0,
+ 0,
+ 2);
+ break;
+ case 2:
+ status = output64chunk(p_ap_encode_obj,
+ p_ap_encode_obj->c1,
+ p_ap_encode_obj->c2,
+ 0,
+ 1);
+ break;
+ }
+ status = write_stream(p_ap_encode_obj, CRLF, 2);
+ p_ap_encode_obj->state64 = 0;
+ p_ap_encode_obj->ct = 0;
+ return status;
+}
+
+static int output64chunk(
+ appledouble_encode_object* p_ap_encode_obj,
+ int c1, int c2, int c3, int pads)
+{
+ char tmpstr[32];
+ char *p = tmpstr;
+
+ *p++ = basis_64[c1>>2];
+ *p++ = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)];
+ if (pads == 2)
+ {
+ *p++ = '=';
+ *p++ = '=';
+ }
+ else if (pads)
+ {
+ *p++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
+ *p++ = '=';
+ }
+ else
+ {
+ *p++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
+ *p++ = basis_64[c3 & 0x3F];
+ }
+ return write_stream(p_ap_encode_obj, (const char*) tmpstr, p-tmpstr);
+}
diff --git a/mailnews/compose/src/nsMsgAttachmentHandler.cpp b/mailnews/compose/src/nsMsgAttachmentHandler.cpp
index 3c7e08bc13..4994c3fe69 100644
--- a/mailnews/compose/src/nsMsgAttachmentHandler.cpp
+++ b/mailnews/compose/src/nsMsgAttachmentHandler.cpp
@@ -36,6 +36,77 @@
#include "mozilla/mailnews/MimeEncoder.h"
#include "nsIPrincipal.h"
+///////////////////////////////////////////////////////////////////////////
+// Mac Specific Attachment Handling for AppleDouble Encoded Files
+///////////////////////////////////////////////////////////////////////////
+#ifdef XP_MACOSX
+
+#define AD_WORKING_BUFF_SIZE FILE_IO_BUFFER_SIZE
+
+extern void MacGetFileType(nsIFile *fs, bool *useDefault, char **type, char **encoding);
+
+#include "nsILocalFileMac.h"
+
+/* static */
+nsresult nsSimpleZipper::Zip(nsIFile *aInputFile, nsIFile *aOutputFile)
+{
+ // create zipwriter object
+ nsresult rv;
+ nsCOMPtr<nsIZipWriter> zipWriter = do_CreateInstance("@mozilla.org/zipwriter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = zipWriter->Open(aOutputFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddToZip(zipWriter, aInputFile, EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we're done.
+ zipWriter->Close();
+ return rv;
+}
+
+/* static */
+nsresult nsSimpleZipper::AddToZip(nsIZipWriter *aZipWriter,
+ nsIFile *aFile,
+ const nsACString &aPath)
+{
+ // find out the path this file/dir should have in the zip
+ nsCString leafName;
+ aFile->GetNativeLeafName(leafName);
+ nsCString currentPath(aPath);
+ currentPath += leafName;
+
+ bool isDirectory;
+ aFile->IsDirectory(&isDirectory);
+ // append slash for a directory entry
+ if (isDirectory)
+ currentPath.Append('/');
+
+ // add the file or directory entry to the zip
+ nsresult rv = aZipWriter->AddEntryFile(currentPath, nsIZipWriter::COMPRESSION_DEFAULT, aFile, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if it's a directory, add all its contents too
+ if (isDirectory) {
+ nsCOMPtr<nsISimpleEnumerator> e;
+ nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator = do_QueryInterface(e, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> currentFile;
+ while (NS_SUCCEEDED(dirEnumerator->GetNextFile(getter_AddRefs(currentFile))) && currentFile) {
+ rv = AddToZip(aZipWriter, currentFile, currentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+#endif // XP_MACOSX
+
//
// Class implementation...
//
@@ -168,7 +239,12 @@ NS_IMETHODIMP nsMsgAttachmentHandler::GetAlreadyEncoded(bool* aAlreadyEncoded)
void
nsMsgAttachmentHandler::CleanupTempFile()
{
-/** Mac Stub **/
+#ifdef XP_MACOSX
+ if (mEncodedWorkingFile) {
+ mEncodedWorkingFile->Remove(false);
+ mEncodedWorkingFile = nullptr;
+ }
+#endif // XP_MACOSX
}
void
@@ -473,8 +549,13 @@ FetcherURLDoneCallback(nsresult aStatus,
ma->m_size = totalSize;
if (!aContentType.IsEmpty())
{
- // can't send appledouble on non-macs
+#ifdef XP_MACOSX
+ //Do not change the type if we are dealing with an encoded (e.g., appledouble or zip) file
+ if (!ma->mEncodedWorkingFile)
+#else
+ // can't send appledouble on non-macs
if (!aContentType.EqualsLiteral("multipart/appledouble"))
+#endif
ma->m_type = aContentType;
}
@@ -617,6 +698,15 @@ done:
return rv;
}
+#ifdef XP_MACOSX
+bool nsMsgAttachmentHandler::HasResourceFork(FSRef *fsRef)
+{
+ FSCatalogInfo catalogInfo;
+ OSErr err = FSGetCatalogInfo(fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes, &catalogInfo, nullptr, nullptr, nullptr);
+ return (err == noErr && catalogInfo.rsrcLogicalSize != 0);
+}
+#endif
+
nsresult
nsMsgAttachmentHandler::SnarfAttachment(nsMsgCompFields *compFields)
{
@@ -657,6 +747,33 @@ nsMsgAttachmentHandler::SnarfAttachment(nsMsgCompFields *compFields)
nsCString sourceURISpec;
rv = mURL->GetSpec(sourceURISpec);
NS_ENSURE_SUCCESS(rv, rv);
+#ifdef XP_MACOSX
+ if (!m_bogus_attachment && StringBeginsWith(sourceURISpec, NS_LITERAL_CSTRING("file://")))
+ {
+ // Unescape the path (i.e. un-URLify it) before making a FSSpec
+ nsAutoCString filePath;
+ filePath.Adopt(nsMsgGetLocalFileFromURL(sourceURISpec.get()));
+ nsAutoCString unescapedFilePath;
+ MsgUnescapeString(filePath, 0, unescapedFilePath);
+
+ nsCOMPtr<nsIFile> sourceFile;
+ NS_NewNativeLocalFile(unescapedFilePath, true, getter_AddRefs(sourceFile));
+ if (!sourceFile)
+ return NS_ERROR_FAILURE;
+
+ // check if it is a bundle. if it is, we'll zip it.
+ // if not, we'll apple encode it (applesingle or appledouble)
+ nsCOMPtr<nsILocalFileMac> macFile(do_QueryInterface(sourceFile));
+ bool isPackage;
+ macFile->IsPackage(&isPackage);
+ if (isPackage)
+ rv = ConvertToZipFile(macFile);
+ else
+ rv = ConvertToAppleEncoding(sourceURISpec, unescapedFilePath, macFile);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif /* XP_MACOSX */
//
// Ok, here we are, we need to fire the URL off and get the data
@@ -676,6 +793,236 @@ nsMsgAttachmentHandler::SnarfAttachment(nsMsgCompFields *compFields)
return fetcher->FireURLRequest(mURL, mTmpFile, mOutFile, FetcherURLDoneCallback, this);
}
+#ifdef XP_MACOSX
+nsresult
+nsMsgAttachmentHandler::ConvertToZipFile(nsILocalFileMac *aSourceFile)
+{
+ // append ".zip" to the real file name
+ nsAutoCString zippedName;
+ nsresult rv = aSourceFile->GetNativeLeafName(zippedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ zippedName.AppendLiteral(".zip");
+
+ // create a temporary file that we'll work on
+ nsCOMPtr <nsIFile> tmpFile;
+ rv = nsMsgCreateTempFile(zippedName.get(), getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mEncodedWorkingFile = do_QueryInterface(tmpFile);
+
+ // point our URL at the zipped temp file
+ NS_NewFileURI(getter_AddRefs(mURL), mEncodedWorkingFile);
+
+ // zip it!
+ rv = nsSimpleZipper::Zip(aSourceFile, mEncodedWorkingFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set some metadata for this attachment, that will affect the MIME headers.
+ m_type = APPLICATION_ZIP;
+ m_realName = zippedName.get();
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgAttachmentHandler::ConvertToAppleEncoding(const nsCString &aFileURI,
+ const nsCString &aFilePath,
+ nsILocalFileMac *aSourceFile)
+{
+ // convert the apple file to AppleDouble first, and then patch the
+ // address in the url.
+
+ //We need to retrieve the file type and creator...
+
+ char fileInfo[32];
+ OSType type, creator;
+
+ nsresult rv = aSourceFile->GetFileType(&type);
+ if (NS_FAILED(rv))
+ return rv;
+ PR_snprintf(fileInfo, sizeof(fileInfo), "%X", type);
+ m_xMacType = fileInfo;
+
+ rv = aSourceFile->GetFileCreator(&creator);
+ if (NS_FAILED(rv))
+ return rv;
+ PR_snprintf(fileInfo, sizeof(fileInfo), "%X", creator);
+ m_xMacCreator = fileInfo;
+
+ FSRef fsRef;
+ aSourceFile->GetFSRef(&fsRef);
+ bool sendResourceFork = HasResourceFork(&fsRef);
+
+ // if we have a resource fork, check the filename extension, maybe we don't need the resource fork!
+ if (sendResourceFork)
+ {
+ nsCOMPtr<nsIURL> fileUrl(do_CreateInstance(NS_STANDARDURL_CONTRACTID));
+ if (fileUrl)
+ {
+ rv = fileUrl->SetSpec(aFileURI);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString ext;
+ rv = fileUrl->GetFileExtension(ext);
+ if (NS_SUCCEEDED(rv) && !ext.IsEmpty())
+ {
+ sendResourceFork =
+ PL_strcasecmp(ext.get(), "TXT") &&
+ PL_strcasecmp(ext.get(), "JPG") &&
+ PL_strcasecmp(ext.get(), "GIF") &&
+ PL_strcasecmp(ext.get(), "TIF") &&
+ PL_strcasecmp(ext.get(), "HTM") &&
+ PL_strcasecmp(ext.get(), "HTML") &&
+ PL_strcasecmp(ext.get(), "ART") &&
+ PL_strcasecmp(ext.get(), "XUL") &&
+ PL_strcasecmp(ext.get(), "XML") &&
+ PL_strcasecmp(ext.get(), "CSS") &&
+ PL_strcasecmp(ext.get(), "JS");
+ }
+ }
+ }
+ }
+
+ // Only use appledouble if we aren't uuencoding.
+ if( sendResourceFork )
+ {
+ char *separator;
+
+ separator = mime_make_separator("ad");
+ if (!separator)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr <nsIFile> tmpFile;
+ nsresult rv = nsMsgCreateTempFile("appledouble", getter_AddRefs(tmpFile));
+ if (NS_SUCCEEDED(rv))
+ mEncodedWorkingFile = do_QueryInterface(tmpFile);
+ if (!mEncodedWorkingFile)
+ {
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ //
+ // RICHIE_MAC - ok, here's the deal, we have a file that we need
+ // to encode in appledouble encoding for the resource fork and put that
+ // into the mEncodedWorkingFile location. Then, we need to patch the new file
+ // spec into the array and send this as part of the 2 part appledouble/mime
+ // encoded mime part.
+ //
+ AppleDoubleEncodeObject *obj = new (AppleDoubleEncodeObject);
+ if (obj == NULL)
+ {
+ mEncodedWorkingFile = nullptr;
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = MsgGetFileStream(mEncodedWorkingFile, getter_AddRefs(obj->fileStream));
+ if (NS_FAILED(rv) || !obj->fileStream)
+ {
+ PR_FREEIF(separator);
+ delete obj;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char *working_buff = (char *) PR_Malloc(AD_WORKING_BUFF_SIZE);
+ if (!working_buff)
+ {
+ PR_FREEIF(separator);
+ delete obj;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ obj->buff = working_buff;
+ obj->s_buff = AD_WORKING_BUFF_SIZE;
+
+ //
+ // Setup all the need information on the apple double encoder.
+ //
+ ap_encode_init(&(obj->ap_encode_obj), aFilePath.get(), separator);
+
+ int32_t count;
+
+ OSErr status = noErr;
+ m_size = 0;
+ while (status == noErr)
+ {
+ status = ap_encode_next(&(obj->ap_encode_obj), obj->buff, obj->s_buff, &count);
+ if (status == noErr || status == errDone)
+ {
+ //
+ // we got the encode data, so call the next stream to write it to the disk.
+ //
+ uint32_t bytesWritten;
+ obj->fileStream->Write(obj->buff, count, &bytesWritten);
+ if (bytesWritten != (uint32_t) count)
+ status = errFileWrite;
+ }
+ }
+
+ ap_encode_end(&(obj->ap_encode_obj), (status >= 0)); // if this is true, ok, false abort
+ if (obj->fileStream)
+ obj->fileStream->Close();
+
+ PR_FREEIF(obj->buff); /* free the working buff. */
+ PR_FREEIF(obj);
+
+ nsCOMPtr <nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), mEncodedWorkingFile);
+
+ nsCOMPtr<nsIFileURL> theFileURL = do_QueryInterface(fileURI, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCString newURLSpec;
+ rv = fileURI->GetSpec(newURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newURLSpec.IsEmpty())
+ {
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_FAILED(nsMsgNewURL(getter_AddRefs(mURL), newURLSpec.get())))
+ {
+ PR_FREEIF(separator);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Now after conversion, also patch the types.
+ char tmp[128];
+ PR_snprintf(tmp, sizeof(tmp), MULTIPART_APPLEDOUBLE ";\r\n boundary=\"%s\"", separator);
+ PR_FREEIF(separator);
+ m_type = tmp;
+ }
+ else
+ {
+ if ( sendResourceFork )
+ {
+ // For now, just do the encoding, but in the old world we would ask the
+ // user about doing this conversion
+ printf("...we could ask the user about this conversion, but for now, nahh..\n");
+ }
+
+ bool useDefault;
+ char *macType, *macEncoding;
+ if (m_type.IsEmpty() || m_type.LowerCaseEqualsLiteral(TEXT_PLAIN))
+ {
+# define TEXT_TYPE 0x54455854 /* the characters 'T' 'E' 'X' 'T' */
+# define text_TYPE 0x74657874 /* the characters 't' 'e' 'x' 't' */
+
+ if (type != TEXT_TYPE && type != text_TYPE)
+ {
+ MacGetFileType(aSourceFile, &useDefault, &macType, &macEncoding);
+ m_type = macType;
+ }
+ }
+ // don't bother to set the types if we failed in getting the file info.
+ }
+
+ return NS_OK;
+}
+#endif // XP_MACOSX
+
nsresult
nsMsgAttachmentHandler::LoadDataFromFile(nsIFile *file, nsString &sigData, bool charsetConversion)
{
diff --git a/mailnews/compose/src/nsMsgAttachmentHandler.h b/mailnews/compose/src/nsMsgAttachmentHandler.h
index 67ac474abd..2034cba032 100644
--- a/mailnews/compose/src/nsMsgAttachmentHandler.h
+++ b/mailnews/compose/src/nsMsgAttachmentHandler.h
@@ -16,6 +16,42 @@
#include "nsAutoPtr.h"
#include "nsIMsgAttachmentHandler.h"
+#ifdef XP_MACOSX
+
+#include "nsMsgAppleDouble.h"
+#include "nsILocalFileMac.h"
+
+class AppleDoubleEncodeObject
+{
+public:
+ appledouble_encode_object ap_encode_obj;
+ char *buff; // the working buff
+ int32_t s_buff; // the working buff size
+ nsCOMPtr <nsIOutputStream> fileStream; // file to hold the encoding
+};
+
+class nsILocalFileMac;
+class nsIZipWriter;
+
+/* Simple utility class that will synchronously zip any file
+ (or folder hierarchy) you give it. */
+class nsSimpleZipper
+{
+ public:
+
+ // Synchronously zips the input file/folder and writes all
+ // data to the output file.
+ static nsresult Zip(nsIFile *aInputFile, nsIFile *aOutputFile);
+
+ private:
+
+ // Recursively adds the file or folder to aZipWriter.
+ static nsresult AddToZip(nsIZipWriter *aZipWriter,
+ nsIFile *aFile,
+ const nsACString &aPath);
+};
+#endif // XP_MACOSX
+
namespace mozilla {
namespace mailnews {
class MimeEncoder;
@@ -59,6 +95,14 @@ private:
bool UseUUEncode_p(void);
void AnalyzeDataChunk (const char *chunk, int32_t chunkSize);
nsresult LoadDataFromFile(nsIFile *file, nsString &sigData, bool charsetConversion); //A similar function already exist in nsMsgCompose!
+#ifdef XP_MACOSX
+ nsresult ConvertToAppleEncoding(const nsCString &aFileSpecURI,
+ const nsCString &aFilePath,
+ nsILocalFileMac *aSourceFile);
+ // zips this attachment and does the work to make this attachment handler handle it properly.
+ nsresult ConvertToZipFile(nsILocalFileMac *aSourceFile);
+ bool HasResourceFork(FSRef *fsRef);
+#endif
//
public:
@@ -69,6 +113,12 @@ public:
nsMsgCompFields *mCompFields; // Message composition fields for the sender
bool m_bogus_attachment; // This is to catch problem children...
+#ifdef XP_MACOSX
+ // if we need to encode this file into for example an appledouble, or zip file,
+ // this file is our working file. currently only needed on mac.
+ nsCOMPtr<nsIFile> mEncodedWorkingFile;
+#endif
+
nsCString m_xMacType; // Mac file type
nsCString m_xMacCreator; // Mac file creator
diff --git a/mailnews/compose/src/nsMsgSend.cpp b/mailnews/compose/src/nsMsgSend.cpp
index e328a9e21d..919d9bfc54 100644
--- a/mailnews/compose/src/nsMsgSend.cpp
+++ b/mailnews/compose/src/nsMsgSend.cpp
@@ -2088,7 +2088,9 @@ nsMsgComposeAndSend::AddCompFieldLocalAttachments()
if (NS_SUCCEEDED(rv) && !fileExt.IsEmpty()) {
nsAutoCString type;
mimeFinder->GetTypeFromExtension(fileExt, type);
+ #ifndef XP_MACOSX
if (!type.Equals("multipart/appledouble")) // can't do apple double on non-macs
+ #endif
m_attachments[newLoc]->m_type = type;
}
}
@@ -2103,7 +2105,9 @@ nsMsgComposeAndSend::AddCompFieldLocalAttachments()
if (NS_SUCCEEDED(rv) && !fileExt.IsEmpty()) {
nsAutoCString type;
mimeFinder->GetTypeFromExtension(fileExt, type);
+ #ifndef XP_MACOSX
if (!type.Equals("multipart/appledouble")) // can't do apple double on non-macs
+ #endif
m_attachments[newLoc]->m_type = type;
// rtf and vcs files may look like text to sniffers,
// but they're not human readable.
diff --git a/mailnews/imap/src/nsIMAPBodyShell.cpp b/mailnews/imap/src/nsIMAPBodyShell.cpp
index b81978cc7b..83e30add45 100644
--- a/mailnews/imap/src/nsIMAPBodyShell.cpp
+++ b/mailnews/imap/src/nsIMAPBodyShell.cpp
@@ -724,6 +724,25 @@ bool nsIMAPBodypartLeaf::ShouldFetchInline(nsIMAPBodyShell *aShell)
return false; // we can leave it on the server
}
+#ifdef XP_MACOSX
+ // If it is either applesingle, or a resource fork for appledouble
+ if (!PL_strcasecmp(m_contentType, "application/applefile"))
+ {
+ // if it is appledouble
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART &&
+ !PL_strcasecmp(m_parentPart->GetBodySubType(), "appledouble"))
+ {
+ // This is the resource fork of a multipart/appledouble.
+ // We inherit the inline attributes of the parent,
+ // which was derived from its OTHER child. (The data fork.)
+ return m_parentPart->ShouldFetchInline(aShell);
+ }
+ else // it is applesingle
+ {
+ return false; // we can leave it on the server
+ }
+ }
+#endif // XP_MACOSX
// Leave out parts with type application/(*)
if (!PL_strcasecmp(m_bodyType, "APPLICATION") && // If it is of type "application"
diff --git a/mailnews/import/applemail/src/moz.build b/mailnews/import/applemail/src/moz.build
new file mode 100644
index 0000000000..fafbebc990
--- /dev/null
+++ b/mailnews/import/applemail/src/moz.build
@@ -0,0 +1,14 @@
+# 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/.
+
+SOURCES += [
+ 'nsAppleMailImport.cpp',
+]
+
+SOURCES += [
+ 'nsEmlxHelperUtils.mm',
+]
+
+FINAL_LIBRARY = 'import'
+
diff --git a/mailnews/import/applemail/src/nsAppleMailImport.cpp b/mailnews/import/applemail/src/nsAppleMailImport.cpp
new file mode 100644
index 0000000000..3097b7df5e
--- /dev/null
+++ b/mailnews/import/applemail/src/nsAppleMailImport.cpp
@@ -0,0 +1,623 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsStringGlue.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIImportService.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMutableArray.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+#include "nsEmlxHelperUtils.h"
+#include "nsAppleMailImport.h"
+#include "nsIOutputStream.h"
+
+PRLogModuleInfo *APPLEMAILLOGMODULE = nullptr;
+
+// some hard-coded strings
+#define DEFAULT_MAIL_FOLDER "~/Library/Mail/"
+#define POP_MBOX_SUFFIX ".mbox"
+#define IMAP_MBOX_SUFFIX ".imapmbox"
+
+// stringbundle URI
+#define APPLEMAIL_MSGS_URL "chrome://messenger/locale/appleMailImportMsgs.properties"
+
+// magic constants
+#define kAccountMailboxID 1234
+
+nsAppleMailImportModule::nsAppleMailImportModule()
+{
+ // Init logging module.
+ if (!APPLEMAILLOGMODULE)
+ APPLEMAILLOGMODULE = PR_NewLogModule("APPLEMAILIMPORTLOG");
+
+ IMPORT_LOG0("nsAppleMailImportModule Created");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ if (bundleService)
+ bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle));
+}
+
+
+nsAppleMailImportModule::~nsAppleMailImportModule()
+{
+ IMPORT_LOG0("nsAppleMailImportModule Deleted");
+}
+
+
+NS_IMPL_ISUPPORTS(nsAppleMailImportModule, nsIImportModule)
+
+
+NS_IMETHODIMP nsAppleMailImportModule::GetName(char16_t **aName)
+{
+ return mBundle ?
+ mBundle->GetStringFromName(u"ApplemailImportName", aName) : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetDescription(char16_t **aName)
+{
+ return mBundle ?
+ mBundle->GetStringFromName(u"ApplemailImportDescription", aName) : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetSupports(char **aSupports)
+{
+ NS_ENSURE_ARG_POINTER(aSupports);
+ *aSupports = strdup(NS_IMPORT_MAIL_STR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetSupportsUpgrade(bool *aUpgrade)
+{
+ NS_ENSURE_ARG_POINTER(aUpgrade);
+ *aUpgrade = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetImportInterface(const char *aImportType, nsISupports **aInterface)
+{
+ NS_ENSURE_ARG_POINTER(aImportType);
+ NS_ENSURE_ARG_POINTER(aInterface);
+ *aInterface = nullptr;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ if (!strcmp(aImportType, "mail")) {
+ nsCOMPtr<nsIImportMail> mail(do_CreateInstance(NS_APPLEMAILIMPL_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportGeneric> generic;
+ rv = impSvc->CreateNewGenericMail(getter_AddRefs(generic));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString name;
+ rv = mBundle->GetStringFromName(u"ApplemailImportName", getter_Copies(name));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> nameString(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nameString->SetData(name);
+
+ generic->SetData("name", nameString);
+ generic->SetData("mailInterface", mail);
+
+ generic.forget(aInterface);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+#pragma mark -
+
+nsAppleMailImportMail::nsAppleMailImportMail() : mProgress(0), mCurDepth(0)
+{
+ IMPORT_LOG0("nsAppleMailImportMail created");
+}
+
+nsresult nsAppleMailImportMail::Initialize()
+{
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ return bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle));
+}
+
+nsAppleMailImportMail::~nsAppleMailImportMail()
+{
+ IMPORT_LOG0("nsAppleMailImportMail destroyed");
+}
+
+NS_IMPL_ISUPPORTS(nsAppleMailImportMail, nsIImportMail)
+
+NS_IMETHODIMP nsAppleMailImportMail::GetDefaultLocation(nsIFile **aLocation, bool *aFound, bool *aUserVerify)
+{
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aFound = false;
+ *aUserVerify = true;
+
+ // try to find current user's top-level Mail folder
+ nsCOMPtr<nsIFile> mailFolder(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ if (mailFolder) {
+ nsresult rv = mailFolder->InitWithNativePath(NS_LITERAL_CSTRING(DEFAULT_MAIL_FOLDER));
+ if (NS_SUCCEEDED(rv)) {
+ *aFound = true;
+ *aUserVerify = false;
+ mailFolder.forget(aLocation);
+ }
+ }
+
+ return NS_OK;
+}
+
+// this is the method that initiates all searching for mailboxes.
+// it will assume that it has a directory like ~/Library/Mail/
+NS_IMETHODIMP nsAppleMailImportMail::FindMailboxes(nsIFile *aMailboxFile, nsIArray **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aMailboxFile);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ IMPORT_LOG0("FindMailboxes for Apple mail invoked");
+
+ bool exists = false;
+ nsresult rv = aMailboxFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIImportService> importService(do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> resultsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (!resultsArray)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mCurDepth = 1;
+
+ // 1. look for accounts with mailboxes
+ FindAccountMailDirs(aMailboxFile, resultsArray, importService);
+ mCurDepth--;
+
+ if (NS_SUCCEEDED(rv)) {
+ // 2. look for "global" mailboxes, that don't belong to any specific account. they are inside the
+ // root's Mailboxes/ folder
+ nsCOMPtr<nsIFile> mailboxesDir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ mailboxesDir->InitWithFile(aMailboxFile);
+ rv = mailboxesDir->Append(NS_LITERAL_STRING("Mailboxes"));
+ if (NS_SUCCEEDED(rv)) {
+ IMPORT_LOG0("Looking for global Apple mailboxes");
+
+ mCurDepth++;
+ rv = FindMboxDirs(mailboxesDir, resultsArray, importService);
+ mCurDepth--;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && resultsArray)
+ resultsArray.forget(aResult);
+
+ return rv;
+}
+
+// operates on the Mail/ directory root, trying to find accounts (which are folders named something like "POP-hwaara@gmail.com")
+// and add their .mbox dirs
+void nsAppleMailImportMail::FindAccountMailDirs(nsIFile *aRoot, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService)
+{
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ nsresult rv = aRoot->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv))
+ return;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ {
+ nsCOMPtr<nsISupports> rawSupports;
+ directoryEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+ currentEntry = do_QueryInterface(rawSupports);
+ if (!currentEntry)
+ continue;
+ }
+
+ // make sure it's a directory
+ bool isDirectory = false;
+ currentEntry->IsDirectory(&isDirectory);
+
+ if (isDirectory) {
+ // now let's see if it's an account folder. if so, we want to traverse it for .mbox children
+ nsAutoString folderName;
+ currentEntry->GetLeafName(folderName);
+ bool isAccountFolder = false;
+
+ if (StringBeginsWith(folderName, NS_LITERAL_STRING("POP-"))) {
+ // cut off "POP-" prefix so we get a nice folder name
+ folderName.Cut(0, 4);
+ isAccountFolder = true;
+ }
+ else if (StringBeginsWith(folderName, NS_LITERAL_STRING("IMAP-"))) {
+ // cut off "IMAP-" prefix so we get a nice folder name
+ folderName.Cut(0, 5);
+ isAccountFolder = true;
+ }
+
+ if (isAccountFolder) {
+ IMPORT_LOG1("Found account: %s\n", NS_ConvertUTF16toUTF8(folderName).get());
+
+ // create a mailbox for this account, so we get a parent for "Inbox", "Sent Messages", etc.
+ nsCOMPtr<nsIImportMailboxDescriptor> desc;
+ nsresult rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc));
+ desc->SetSize(1);
+ desc->SetDepth(mCurDepth);
+ desc->SetDisplayName(folderName.get());
+ desc->SetIdentifier(kAccountMailboxID);
+
+ nsCOMPtr<nsIFile> mailboxDescFile;
+ rv = desc->GetFile(getter_AddRefs(mailboxDescFile));
+ if (!mailboxDescFile)
+ continue;
+
+ mailboxDescFile->InitWithFile(currentEntry);
+
+ // add this mailbox descriptor to the list
+ aMailboxDescs->AppendElement(desc, false);
+
+ // now add all the children mailboxes
+ mCurDepth++;
+ FindMboxDirs(currentEntry, aMailboxDescs, aImportService);
+ mCurDepth--;
+ }
+ }
+ }
+}
+
+// adds the specified file as a mailboxdescriptor to the array
+nsresult nsAppleMailImportMail::AddMboxDir(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService)
+{
+ nsAutoString folderName;
+ aFolder->GetLeafName(folderName);
+
+ // cut off the suffix, if any, or prefix if this is an account folder.
+ if (StringEndsWith(folderName, NS_LITERAL_STRING(POP_MBOX_SUFFIX)))
+ folderName.SetLength(folderName.Length()-5);
+ else if (StringEndsWith(folderName, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX)))
+ folderName.SetLength(folderName.Length()-9);
+ else if (StringBeginsWith(folderName, NS_LITERAL_STRING("POP-")))
+ folderName.Cut(4, folderName.Length());
+ else if (StringBeginsWith(folderName, NS_LITERAL_STRING("IMAP-")))
+ folderName.Cut(5, folderName.Length());
+
+ nsCOMPtr<nsIImportMailboxDescriptor> desc;
+ nsresult rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc));
+ if (NS_SUCCEEDED(rv)) {
+ // find out number of messages in this .mbox
+ uint32_t numMessages = 0;
+ {
+ // move to the .mbox's Messages folder
+ nsCOMPtr<nsIFile> messagesFolder;
+ aFolder->Clone(getter_AddRefs(messagesFolder));
+ nsresult rv = messagesFolder->Append(NS_LITERAL_STRING("Messages"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // count the number of messages in this folder. it sucks that we have to iterate through the folder
+ // but XPCOM doesn't give us any way to just get the file count, unfortunately. :-(
+ nsCOMPtr<nsISimpleEnumerator> dirEnumerator;
+ messagesFolder->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ if (dirEnumerator) {
+ bool hasMore = false;
+ while (NS_SUCCEEDED(dirEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> rawSupports;
+ dirEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+
+ nsCOMPtr<nsIFile> file(do_QueryInterface(rawSupports));
+ if (file) {
+ bool isFile = false;
+ file->IsFile(&isFile);
+ if (isFile)
+ numMessages++;
+ }
+ }
+ }
+ }
+
+ desc->SetSize(numMessages);
+ desc->SetDisplayName(folderName.get());
+ desc->SetDepth(mCurDepth);
+
+ IMPORT_LOG3("Will import %s with approx %d messages, depth is %d", NS_ConvertUTF16toUTF8(folderName).get(), numMessages, mCurDepth);
+
+ // XXX: this is silly. there's no setter for the mailbox descriptor's file, so we need to get it, and then modify it.
+ nsCOMPtr<nsIFile> mailboxDescFile;
+ rv = desc->GetFile(getter_AddRefs(mailboxDescFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mailboxDescFile)
+ mailboxDescFile->InitWithFile(aFolder);
+
+ // add this mailbox descriptor to the list
+ aMailboxDescs->AppendElement(desc, false);
+ }
+
+ return NS_OK;
+}
+
+// Starts looking for .mbox dirs in the specified dir. The .mbox dirs contain messages and can be considered leafs in a tree of
+// nested mailboxes (subfolders).
+//
+// If a mailbox has sub-mailboxes, they are contained in a sibling folder with the same name without the ".mbox" part.
+// example:
+// MyParentMailbox.mbox/
+// MyParentMailbox/
+// MyChildMailbox.mbox/
+// MyOtherChildMailbox.mbox/
+//
+nsresult nsAppleMailImportMail::FindMboxDirs(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMailboxDescs);
+ NS_ENSURE_ARG_POINTER(aImportService);
+
+ // make sure this is a directory.
+ bool isDir = false;
+ if (NS_FAILED(aFolder->IsDirectory(&isDir)) || !isDir)
+ return NS_ERROR_FAILURE;
+
+ // iterate through the folder contents
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ nsresult rv = aFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv) || !directoryEnumerator)
+ return rv;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ {
+ nsCOMPtr<nsISupports> rawSupports;
+ directoryEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+ currentEntry = do_QueryInterface(rawSupports);
+ if (!currentEntry)
+ continue;
+ }
+
+ // we only care about directories...
+ if (NS_FAILED(currentEntry->IsDirectory(&isDir)) || !isDir)
+ continue;
+
+ // now find out if this is a .mbox dir
+ nsAutoString currentFolderName;
+ if (NS_SUCCEEDED(currentEntry->GetLeafName(currentFolderName)) &&
+ (StringEndsWith(currentFolderName, NS_LITERAL_STRING(POP_MBOX_SUFFIX)) ||
+ StringEndsWith(currentFolderName, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX)))) {
+ IMPORT_LOG1("Adding .mbox dir: %s", NS_ConvertUTF16toUTF8(currentFolderName).get());
+
+ // add this .mbox
+ rv = AddMboxDir(currentEntry, aMailboxDescs, aImportService);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("Couldn't add .mbox for import: %s ... continuing anyway", NS_ConvertUTF16toUTF8(currentFolderName).get());
+ continue;
+ }
+
+ // see if this .mbox dir has any sub-mailboxes
+ nsAutoString siblingMailboxDirPath;
+ currentEntry->GetPath(siblingMailboxDirPath);
+
+ // cut off suffix
+ if (StringEndsWith(siblingMailboxDirPath, NS_LITERAL_STRING(IMAP_MBOX_SUFFIX)))
+ siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length()-9);
+ else if (StringEndsWith(siblingMailboxDirPath, NS_LITERAL_STRING(POP_MBOX_SUFFIX)))
+ siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length()-5);
+
+ IMPORT_LOG1("trying to locate a '%s'", NS_ConvertUTF16toUTF8(siblingMailboxDirPath).get());
+ nsCOMPtr<nsIFile> siblingMailboxDir(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = siblingMailboxDir->InitWithPath(siblingMailboxDirPath);
+ bool reallyExists = false;
+ siblingMailboxDir->Exists(&reallyExists);
+
+ if (NS_SUCCEEDED(rv) && reallyExists) {
+ IMPORT_LOG1("Found what looks like an .mbox container: %s", NS_ConvertUTF16toUTF8(currentFolderName).get());
+
+ // traverse this folder for other .mboxes
+ mCurDepth++;
+ FindMboxDirs(siblingMailboxDir, aMailboxDescs, aImportService);
+ mCurDepth--;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppleMailImportMail::ImportMailbox(nsIImportMailboxDescriptor *aMailbox,
+ nsIMsgFolder *aDstFolder,
+ char16_t **aErrorLog,
+ char16_t **aSuccessLog, bool *aFatalError)
+{
+ nsAutoString errorLog, successLog;
+
+ // reset progress
+ mProgress = 0;
+
+ nsAutoString mailboxName;
+ aMailbox->GetDisplayName(getter_Copies(mailboxName));
+
+ nsCOMPtr<nsIFile> mboxFolder;
+ nsresult rv = aMailbox->GetFile(getter_AddRefs(mboxFolder));
+ if (NS_FAILED(rv) || !mboxFolder) {
+ ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ // if we're an account mailbox, nothing do. if we're a real mbox
+ // then we've got some messages to import!
+ uint32_t mailboxIdentifier;
+ aMailbox->GetIdentifier(&mailboxIdentifier);
+
+ if (mailboxIdentifier != kAccountMailboxID) {
+ // move to the .mbox's Messages folder
+ nsCOMPtr<nsIFile> messagesFolder;
+ mboxFolder->Clone(getter_AddRefs(messagesFolder));
+ rv = messagesFolder->Append(NS_LITERAL_STRING("Messages"));
+ if (NS_FAILED(rv)) {
+ // even if there are no messages, it might still be a valid mailbox, or even
+ // a parent for other mailboxes.
+ //
+ // just indicate that we're done, using the same number that we used to estimate
+ // number of messages earlier.
+ uint32_t finalSize;
+ aMailbox->GetSize(&finalSize);
+ mProgress = finalSize;
+
+ // report that we successfully imported this mailbox
+ ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_OK;
+ }
+
+ // let's import the messages!
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ rv = messagesFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv)) {
+ ReportStatus(u"ApplemailImportMailboxConvertError", mailboxName, errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ // prepare an outstream to the destination file
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = aDstFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (!msgStore || NS_FAILED(rv)) {
+ ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ bool hasMore = false;
+ nsCOMPtr<nsIOutputStream> outStream;
+
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ {
+ nsCOMPtr<nsISupports> rawSupports;
+ directoryEnumerator->GetNext(getter_AddRefs(rawSupports));
+ if (!rawSupports)
+ continue;
+ currentEntry = do_QueryInterface(rawSupports);
+ if (!currentEntry)
+ continue;
+ }
+
+ // make sure it's an .emlx file
+ bool isFile = false;
+ currentEntry->IsFile(&isFile);
+ if (!isFile)
+ continue;
+
+ nsAutoString leafName;
+ currentEntry->GetLeafName(leafName);
+ if (!StringEndsWith(leafName, NS_LITERAL_STRING(".emlx")))
+ continue;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ bool reusable;
+ rv = msgStore->GetNewMsgOutputStream(aDstFolder, getter_AddRefs(msgHdr),
+ &reusable,
+ getter_AddRefs(outStream));
+ if (NS_FAILED(rv))
+ break;
+
+ // add the data to the mbox stream
+ if (NS_SUCCEEDED(nsEmlxHelperUtils::AddEmlxMessageToStream(currentEntry, outStream))) {
+ mProgress++;
+ msgStore->FinishNewMessage(outStream, msgHdr);
+ }
+ else {
+ msgStore->DiscardNewMessage(outStream, msgHdr);
+ break;
+ }
+ if (!reusable)
+ outStream->Close();
+ }
+ if (outStream)
+ outStream->Close();
+ }
+ // just indicate that we're done, using the same number that we used to estimate
+ // number of messages earlier.
+ uint32_t finalSize;
+ aMailbox->GetSize(&finalSize);
+ mProgress = finalSize;
+
+ // report that we successfully imported this mailbox
+ ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+
+ return NS_OK;
+}
+
+void nsAppleMailImportMail::ReportStatus(const char16_t* aErrorName, nsString &aName,
+ nsAString &aStream)
+{
+ // get (and format, if needed) the error string from the bundle
+ nsAutoString outString;
+ const char16_t *fmt = { aName.get() };
+ nsresult rv = mBundle->FormatStringFromName(aErrorName, &fmt, 1, getter_Copies(outString));
+ // write it out the stream
+ if (NS_SUCCEEDED(rv)) {
+ aStream.Append(outString);
+ aStream.Append(char16_t('\n'));
+ }
+}
+
+void nsAppleMailImportMail::SetLogs(const nsAString &aSuccess, const nsAString &aError, char16_t **aOutSuccess, char16_t **aOutError)
+{
+ if (aOutError && !*aOutError)
+ *aOutError = ToNewUnicode(aError);
+ if (aOutSuccess && !*aOutSuccess)
+ *aOutSuccess = ToNewUnicode(aSuccess);
+}
+
+NS_IMETHODIMP nsAppleMailImportMail::GetImportProgress(uint32_t *aDoneSoFar)
+{
+ NS_ENSURE_ARG_POINTER(aDoneSoFar);
+ *aDoneSoFar = mProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportMail::TranslateFolderName(const nsAString &aFolderName, nsAString &aResult)
+{
+ aResult = aFolderName;
+ return NS_OK;
+}
diff --git a/mailnews/import/applemail/src/nsAppleMailImport.h b/mailnews/import/applemail/src/nsAppleMailImport.h
new file mode 100644
index 0000000000..b906aecf56
--- /dev/null
+++ b/mailnews/import/applemail/src/nsAppleMailImport.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsAppleMailImport_h___
+#define nsAppleMailImport_h___
+
+#include "mozilla/Logging.h"
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIImportMail.h"
+
+// logging facilities
+extern PRLogModuleInfo *APPLEMAILLOGMODULE;
+
+#define IMPORT_LOG0(x) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) MOZ_LOG(APPLEMAILLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+#define NS_APPLEMAILIMPL_CID \
+{ 0x9117a1ea, 0xe012, 0x43b5, { 0xa0, 0x20, 0xcb, 0x8a, 0x66, 0xcc, 0x09, 0xe1 } }
+
+#define NS_APPLEMAILIMPORT_CID \
+{ 0x6d3f101c, 0x70ec, 0x4e04, { 0xb6, 0x8d, 0x99, 0x08, 0xd1, 0xae, 0xdd, 0xf3 } }
+
+#define NS_APPLEMAILIMPL_CONTRACTID "@mozilla.org/import/import-appleMailImpl;1"
+
+#define kAppleMailSupportsString "mail"
+
+class nsIImportService;
+class nsIMutableArray;
+
+class nsAppleMailImportModule : public nsIImportModule
+{
+ public:
+
+ nsAppleMailImportModule();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTMODULE
+
+ private:
+ virtual ~nsAppleMailImportModule();
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+};
+
+class nsAppleMailImportMail : public nsIImportMail
+{
+ public:
+
+ nsAppleMailImportMail();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTMAIL
+
+ nsresult Initialize();
+
+ private:
+ virtual ~nsAppleMailImportMail();
+
+ void FindAccountMailDirs(nsIFile *aRoot, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService);
+ nsresult FindMboxDirs(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService);
+ nsresult AddMboxDir(nsIFile *aFolder, nsIMutableArray *aMailboxDescs, nsIImportService *aImportService);
+
+ // aInfoString is the format to a "foo %s" string. It may be NULL if the error string needs no such format.
+ void ReportStatus(const char16_t* aErrorName, nsString &aName, nsAString &aStream);
+ static void SetLogs(const nsAString& success, const nsAString& error, char16_t **aOutErrorLog, char16_t **aSuccessLog);
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+ uint32_t mProgress;
+ uint16_t mCurDepth;
+};
+
+#endif /* nsAppleMailImport_h___ */
diff --git a/mailnews/import/applemail/src/nsEmlxHelperUtils.h b/mailnews/import/applemail/src/nsEmlxHelperUtils.h
new file mode 100644
index 0000000000..728b725b60
--- /dev/null
+++ b/mailnews/import/applemail/src/nsEmlxHelperUtils.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsEmlxHelperUtils_h___
+#define nsEmlxHelperUtils_h___
+
+#include "nscore.h"
+#include "nsStringGlue.h"
+
+class nsIOutputStream;
+class nsIFile;
+
+class nsEmlxHelperUtils {
+ /* All emlx messages have a "flags" number in the metadata.
+ These are the masks to decode that, found via http://jwz.livejournal.com/505711.html */
+ enum EmlxMetadataMask {
+ kRead = 1 << 0, // read
+ // 1 << 1, // deleted
+ kAnswered = 1 << 2, // answered
+ // 1 << 3, // encrypted
+ kFlagged = 1 << 4, // flagged
+ // 1 << 5, // recent
+ // 1 << 6, // draft
+ // 1 << 7, // initial (no longer used)
+ kForwarded = 1 << 8, // forwarded
+ // 1 << 9, // redirected
+ // 3F << 10, // attachment count (6 bits)
+ // 7F << 16, // priority level (7 bits)
+ // 1 << 23, // signed
+ // 1 << 24, // is junk
+ // 1 << 25, // is not junk
+ // 1 << 26, // font size delta 7 (3 bits)
+ // 1 << 29, // junk mail level recorded
+ // 1 << 30, // highlight text in toc
+ // 1 << 31 // (unused)
+ };
+
+ // This method will scan the raw EMLX message buffer for "dangerous" so-called "From-lines" that we need to escape.
+ // If it needs to modify any lines, it will return a non-NULL aOutBuffer. If aOutBuffer is NULL, no modification needed
+ // to be made.
+ static nsresult ConvertToMboxRD(const char *aMessageBufferStart, const char *aMessageBufferEnd, nsCString &aOutBuffer);
+
+ // returns an int representing the X-Mozilla-Status flags set (e.g. "read", "flagged") converted from EMLX flags.
+ static nsresult ConvertToMozillaStatusFlags(const char *aXMLBufferStart, const char *aXMLBufferEnd, uint32_t *aMozillaStatusFlags);
+
+ public:
+
+ // add an .emlx message to the mbox output
+ static nsresult AddEmlxMessageToStream(nsIFile *aEmlxFile, nsIOutputStream *aOutoutStream);
+
+};
+
+#endif // nsEmlxHelperUtils_h___
diff --git a/mailnews/import/applemail/src/nsEmlxHelperUtils.mm b/mailnews/import/applemail/src/nsEmlxHelperUtils.mm
new file mode 100644
index 0000000000..d2feb166e6
--- /dev/null
+++ b/mailnews/import/applemail/src/nsEmlxHelperUtils.mm
@@ -0,0 +1,240 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsEmlxHelperUtils.h"
+#include "nsIFileStreams.h"
+#include "nsIBufferedStreams.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsObjCExceptions.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "msgCore.h"
+#include "nsTArray.h"
+#include "nsAppleMailImport.h"
+#include "prprf.h"
+#include "nsIFile.h"
+
+#import <Cocoa/Cocoa.h>
+
+
+nsresult nsEmlxHelperUtils::ConvertToMozillaStatusFlags(const char *aXMLBufferStart,
+ const char *aXMLBufferEnd,
+ uint32_t *aMozillaStatusFlags)
+{
+ // create a NSData wrapper around the buffer, so we can use the Cocoa call below
+ NSData *metadata =
+ [[[NSData alloc] initWithBytesNoCopy:(void *)aXMLBufferStart length:(aXMLBufferEnd-aXMLBufferStart) freeWhenDone:NO] autorelease];
+
+ // get the XML data as a dictionary
+ NSPropertyListFormat format;
+ id plist = [NSPropertyListSerialization propertyListWithData:metadata
+ options:NSPropertyListImmutable
+ format:&format
+ error:NULL];
+
+ if (!plist)
+ return NS_ERROR_FAILURE;
+
+ // find the <flags>...</flags> value and convert to int
+ const uint32_t emlxMessageFlags = [[(NSDictionary *)plist objectForKey:@"flags"] intValue];
+
+ if (emlxMessageFlags == 0)
+ return NS_ERROR_FAILURE;
+
+ if (emlxMessageFlags & nsEmlxHelperUtils::kRead)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Read;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kForwarded)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Forwarded;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kAnswered)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Replied;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kFlagged)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Marked;
+
+ return NS_OK;
+}
+
+nsresult nsEmlxHelperUtils::ConvertToMboxRD(const char *aMessageBufferStart, const char *aMessageBufferEnd, nsCString &aOutBuffer)
+{
+ nsTArray<const char *> foundFromLines;
+
+ const char *cur = aMessageBufferStart;
+ while (cur < aMessageBufferEnd) {
+
+ const char *foundFromStr = strnstr(cur, "From ", aMessageBufferEnd-cur);
+
+ if (foundFromStr) {
+ // skip all prepending '>' chars
+ const char *fromLineStart = foundFromStr;
+ while (fromLineStart-- >= aMessageBufferStart) {
+ if (*fromLineStart == '\n' || fromLineStart == aMessageBufferStart) {
+ if (fromLineStart > aMessageBufferStart)
+ fromLineStart++;
+ foundFromLines.AppendElement(fromLineStart);
+ break;
+ }
+ else if (*fromLineStart != '>')
+ break;
+ }
+
+ // advance past the last found From string.
+ cur = foundFromStr + 5;
+
+ // look for more From lines.
+ continue;
+ }
+
+ break;
+ }
+
+ // go through foundFromLines
+ if (foundFromLines.Length()) {
+
+ const char *chunkStart = aMessageBufferStart;
+ for (unsigned i=0; i<foundFromLines.Length(); ++i) {
+ aOutBuffer.Append(chunkStart, (foundFromLines[i]-chunkStart));
+ aOutBuffer.Append(NS_LITERAL_CSTRING(">"));
+
+ chunkStart = foundFromLines[i];
+ }
+ aOutBuffer.Append(chunkStart, (aMessageBufferEnd - chunkStart));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsEmlxHelperUtils::AddEmlxMessageToStream(nsIFile *aMessage, nsIOutputStream *aOut)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // needed to be sure autoreleased objects are released too, which they might not
+ // in a C++ environment where the main event loop has no autorelease pool (e.g on a XPCOM thread)
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsAutoCString path;
+ aMessage->GetNativePath(path);
+
+ NSData *data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:path.get()]];
+ if (!data) {
+ [pool release];
+ return NS_ERROR_FAILURE;
+ }
+
+ char *startOfMessageData = NULL;
+ uint32_t actualBytesWritten = 0;
+
+ // The anatomy of an EMLX file:
+ //
+ // -------------------------------
+ // < A number describing how many bytes ahead there is message data >
+ // < Message data >
+ // < XML metadata for this message >
+ // -------------------------------
+
+ // read the first line of the emlx file, which is a number of how many bytes ahead the actual
+ // message data is.
+ uint64_t numberOfBytesToRead = strtol((char *)[data bytes], &startOfMessageData, 10);
+ if (numberOfBytesToRead <= 0 || !startOfMessageData) {
+ [pool release];
+ return NS_ERROR_FAILURE;
+ }
+
+ // skip whitespace
+ while (*startOfMessageData == ' ' ||
+ *startOfMessageData == '\n' ||
+ *startOfMessageData == '\r' ||
+ *startOfMessageData == '\t')
+ ++startOfMessageData;
+
+ NS_NAMED_LITERAL_CSTRING(kBogusFromLine, "From \n");
+ NS_NAMED_LITERAL_CSTRING(kEndOfMessage, "\n\n");
+
+ // write the bogus "From " line which is a magic separator in the mbox format
+ rv = aOut->Write(kBogusFromLine.get(), kBogusFromLine.Length(), &actualBytesWritten);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // now read the XML metadata, so we can extract info like which flags (read? replied? flagged? etc) this message has.
+ const char *startOfXMLMetadata = startOfMessageData + numberOfBytesToRead;
+ const char *endOfXMLMetadata = (char *)[data bytes] + [data length];
+
+ uint32_t x_mozilla_flags = 0;
+ ConvertToMozillaStatusFlags(startOfXMLMetadata, endOfXMLMetadata, &x_mozilla_flags);
+
+ // write the X-Mozilla-Status header according to which flags we've gathered above.
+ uint32_t dummyRv;
+ nsAutoCString buf(PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, x_mozilla_flags));
+ NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status header");
+ if (buf.IsEmpty()) {
+ [pool release];
+ return rv;
+ }
+
+ rv = aOut->Write(buf.get(), buf.Length(), &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write out X-Mozilla-Keywords header as well to reserve some space for it
+ // in the mbox file.
+ rv = aOut->Write(X_MOZILLA_KEYWORDS, X_MOZILLA_KEYWORDS_LEN, &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write out empty X-Mozilla_status2 header
+ buf.Adopt(PR_smprintf(X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, 0));
+ NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status2 header");
+ if (buf.IsEmpty()) {
+ [pool release];
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = aOut->Write(buf.get(), buf.Length(), &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // do any conversion needed for the mbox data to be valid mboxrd.
+ nsCString convertedData;
+ rv = ConvertToMboxRD(startOfMessageData, (startOfMessageData + numberOfBytesToRead), convertedData);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write the actual message data.
+ if (convertedData.IsEmpty())
+ rv = aOut->Write(startOfMessageData, (uint32_t)numberOfBytesToRead, &actualBytesWritten);
+ else {
+ IMPORT_LOG1("Escaped From-lines in %s!", path.get());
+ rv = aOut->Write(convertedData.get(), convertedData.Length(), &actualBytesWritten);
+ }
+
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ NS_ASSERTION(actualBytesWritten == (convertedData.IsEmpty() ? numberOfBytesToRead : convertedData.Length()),
+ "Didn't write as many bytes as expected for .emlx file?");
+
+ // add newlines to denote the end of this message in the mbox
+ rv = aOut->Write(kEndOfMessage.get(), kEndOfMessage.Length(), &actualBytesWritten);
+
+ [pool release];
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/mailnews/import/build/moz.build b/mailnews/import/build/moz.build
index 58814a6940..e8ac0751f5 100644
--- a/mailnews/import/build/moz.build
+++ b/mailnews/import/build/moz.build
@@ -35,6 +35,13 @@ LOCAL_INCLUDES += [
'../vcard/src',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '../applemail/src',
+ ]
+ OS_LIBS += CONFIG['TK_LIBS']
+ OS_LIBS += ['-framework Cocoa']
+
if CONFIG['OS_ARCH'] == 'WINNT':
LOCAL_INCLUDES += [
]
diff --git a/mailnews/import/build/nsImportModule.cpp b/mailnews/import/build/nsImportModule.cpp
index 813271b08f..f8f3814e6e 100644
--- a/mailnews/import/build/nsImportModule.cpp
+++ b/mailnews/import/build/nsImportModule.cpp
@@ -33,6 +33,16 @@ NS_DEFINE_NAMED_CID(NS_TEXTIMPORT_CID);
NS_DEFINE_NAMED_CID(NS_VCARDIMPORT_CID);
////////////////////////////////////////////////////////////////////////////////
+// Apple Mail import Include Files
+////////////////////////////////////////////////////////////////////////////////
+#if defined(XP_MACOSX)
+#include "nsAppleMailImport.h"
+
+NS_DEFINE_NAMED_CID(NS_APPLEMAILIMPORT_CID);
+NS_DEFINE_NAMED_CID(NS_APPLEMAILIMPL_CID);
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
// outlook import Include Files
////////////////////////////////////////////////////////////////////////////////
#ifdef XP_WIN
@@ -76,6 +86,14 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsTextImport)
NS_GENERIC_FACTORY_CONSTRUCTOR(nsVCardImport)
////////////////////////////////////////////////////////////////////////////////
+// apple mail import factories
+////////////////////////////////////////////////////////////////////////////////
+#if defined(XP_MACOSX)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppleMailImportModule)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppleMailImportMail, Initialize)
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
// outlook import factories
////////////////////////////////////////////////////////////////////////////////
#ifdef XP_WIN
@@ -103,6 +121,9 @@ static const mozilla::Module::CategoryEntry kMailNewsImportCategories[] = {
{ "mailnewsimport", "{1DB469A0-8B00-11d3-A206-00A0CC26DA63}", kOutlookSupportsString },
#endif
#endif
+#if defined(XP_MACOSX)
+ { "mailnewsimport", "{6d3f101c-70ec-4e04-b68d-9908d1aeddf3}", kAppleMailSupportsString },
+#endif
{ NULL }
};
@@ -111,6 +132,11 @@ const mozilla::Module::CIDEntry kMailNewsImportCIDs[] = {
{ &kNS_IMPORTMIMEENCODE_CID, false, NULL, nsIImportMimeEncodeImplConstructor },
{ &kNS_TEXTIMPORT_CID, false, NULL, nsTextImportConstructor },
{ &kNS_VCARDIMPORT_CID, false, NULL, nsVCardImportConstructor },
+#if defined(XP_MACOSX)
+ { &kNS_APPLEMAILIMPORT_CID, false, NULL, nsAppleMailImportModuleConstructor },
+ { &kNS_APPLEMAILIMPL_CID, false, NULL, nsAppleMailImportMailConstructor },
+#endif
+
#ifdef XP_WIN
{ &kNS_WMIMPORT_CID, false, NULL, nsWMImportConstructor },
{ &kNS_BECKYIMPORT_CID, false, NULL, nsBeckyImportConstructor },
@@ -126,6 +152,11 @@ const mozilla::Module::ContractIDEntry kMailNewsImportContracts[] = {
{ "@mozilla.org/import/import-mimeencode;1", &kNS_IMPORTMIMEENCODE_CID },
{ "@mozilla.org/import/import-text;1", &kNS_TEXTIMPORT_CID },
{ "@mozilla.org/import/import-vcard;1", &kNS_VCARDIMPORT_CID },
+#if defined(XP_MACOSX)
+ { "@mozilla.org/import/import-applemail;1", &kNS_APPLEMAILIMPORT_CID },
+ { NS_APPLEMAILIMPL_CONTRACTID, &kNS_APPLEMAILIMPL_CID },
+#endif
+
#ifdef XP_WIN
{ "@mozilla.org/import/import-wm;1", &kNS_WMIMPORT_CID },
{ "@mozilla.org/import/import-becky;1", &kNS_BECKYIMPORT_CID },
diff --git a/mailnews/import/content/importDialog.xul b/mailnews/import/content/importDialog.xul
index 1223a12635..383585f83e 100644
--- a/mailnews/import/content/importDialog.xul
+++ b/mailnews/import/content/importDialog.xul
@@ -15,7 +15,11 @@
<window xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="OnLoadImportDialog()"
+#ifdef XP_MACOSX
+ style="width: &window.macWidth; !important;"
+#else
style="width: &window.width; !important;"
+#endif
title="&importDialog.windowTitle;">
<stringbundle id="bundle_importMsgs" src="chrome://messenger/locale/importMsgs.properties"/>
diff --git a/mailnews/jar.mn b/mailnews/jar.mn
index 8b850ecb91..71539e9aa0 100644
--- a/mailnews/jar.mn
+++ b/mailnews/jar.mn
@@ -131,6 +131,8 @@ messenger.jar:
content/messenger/dateFormat.js (base/content/dateFormat.js)
content/messenger/shutdownWindow.xul (base/content/shutdownWindow.xul)
content/messenger/shutdownWindow.js (base/content/shutdownWindow.js)
+#ifndef XP_MACOSX
content/messenger/newmailalert.css (base/content/newmailalert.css)
content/messenger/newmailalert.js (base/content/newmailalert.js)
content/messenger/newmailalert.xul (base/content/newmailalert.xul)
+#endif
diff --git a/mailnews/mailnews.js b/mailnews/mailnews.js
index ae172305d2..49ac33827e 100644
--- a/mailnews/mailnews.js
+++ b/mailnews/mailnews.js
@@ -696,7 +696,11 @@ pref("mail.biff.alert.show_subject", true);
pref("mail.biff.alert.show_sender", true);
pref("mail.biff.alert.preview_length", 40);
+#ifdef XP_MACOSX
+pref("mail.biff.play_sound", false);
+#else
pref("mail.biff.play_sound", true);
+#endif
// 0 == default system sound, 1 == user specified wav
pref("mail.biff.play_sound.type", 0);
// _moz_mailbeep is a magic key, for the default sound.
@@ -706,6 +710,8 @@ pref("mail.biff.show_alert", true);
#ifdef XP_WIN
pref("mail.biff.show_tray_icon", true);
pref("mail.biff.show_balloon", false);
+#elifdef XP_MACOSX
+pref("mail.biff.animate_dock_icon", false);
#elifdef XP_UNIX
pref("mail.biff.use_system_alert", false);
#endif
@@ -718,6 +724,13 @@ pref("mail.biff.add_interval_jitter", true);
pref("mail.biff.on_new_window", true);
#endif
+#ifdef XP_MACOSX
+// If true, the number used in the Mac OS X dock notification will be the
+// the number of "new" messages, as per the classic Thunderbird definition.
+// Defaults to false, which notifies about the number of unread messages.
+pref("mail.biff.use_new_count_in_mac_dock", false);
+#endif
+
// For feed account serverType=rss sound on biff; if true, mail.biff.play_sound.* settings are used.
pref("mail.feed.play_sound", false);
@@ -834,6 +847,15 @@ pref("ldap_2.servers.oe.description", "chrome://messenger/locale/addressbook/add
pref("ldap_2.servers.oe.dirType", 3);
#endif
#endif
+#ifdef XP_MACOSX
+pref("ldap_2.servers.osx.uri", "moz-abosxdirectory:///");
+pref("ldap_2.servers.osx.description", "chrome://messenger/locale/addressbook/addressBook.properties");
+pref("ldap_2.servers.osx.dirType", 3);
+pref("mail.notification.sound", "");
+pref("mail.notification.count.inbox_only", true);
+// Work around bug 482811 by disabling slow script warning for chrome scripts on Mac
+pref("dom.max_chrome_script_run_time", 0);
+#endif
// gtk2 (*nix) lacks transparent/translucent drag support (bug 376238), so we
// want to disable it so people can see where they are dragging things.
diff --git a/mailnews/mime/src/mimeebod.cpp b/mailnews/mime/src/mimeebod.cpp
index ee66c40ca2..2ca056feb8 100644
--- a/mailnews/mime/src/mimeebod.cpp
+++ b/mailnews/mime/src/mimeebod.cpp
@@ -21,6 +21,10 @@
MimeDefClass(MimeExternalBody, MimeExternalBodyClass,
mimeExternalBodyClass, &MIME_SUPERCLASS);
+#ifdef XP_MACOSX
+extern MimeObjectClass mimeMultipartAppleDoubleClass;
+#endif
+
static int MimeExternalBody_initialize (MimeObject *);
static void MimeExternalBody_finalize (MimeObject *);
static int MimeExternalBody_parse_line (const char *, int32_t, MimeObject *);
@@ -248,6 +252,12 @@ MimeExternalBody_parse_eof (MimeObject *obj, bool abort_p)
status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p);
if (status < 0) return status;
+#ifdef XP_MACOSX
+ if (obj->parent && mime_typep(obj->parent,
+ (MimeObjectClass*) &mimeMultipartAppleDoubleClass))
+ goto done;
+#endif /* XP_MACOSX */
+
if (!abort_p &&
obj->output_p &&
obj->options &&
@@ -415,6 +425,10 @@ FAIL:
PR_FREEIF(subj);
}
+#ifdef XP_MACOSX
+done:
+#endif
+
return status;
}
diff --git a/mailnews/mime/src/mimemapl.cpp b/mailnews/mime/src/mimemapl.cpp
index c7363ceff1..449d80ac08 100644
--- a/mailnews/mime/src/mimemapl.cpp
+++ b/mailnews/mime/src/mimemapl.cpp
@@ -55,6 +55,19 @@ MimeMultipartAppleDouble_parse_begin (MimeObject *obj)
NS_ASSERTION(obj->options->state->first_data_written_p, "first data not written");
}
+#ifdef XP_MACOSX
+ if (obj->options && obj->options->state)
+ {
+// obj->options->state->separator_suppressed_p = true;
+ goto done;
+ }
+ /*
+ * It would be nice to not showing the resource fork links
+ * if we are displaying inline. But, there is no way we could
+ * know ahead of time that we could display the data fork and
+ * the data fork is always hidden on MAC platform.
+ */
+#endif
/* If we're writing this object as HTML, then emit a link for the
multipart/appledouble part (both links) that looks just like the
links that MimeExternalObject emits for external leaf parts.
@@ -143,6 +156,10 @@ GOTTA STILL DO THIS FOR QUOTING!
if (status < 0) return status;
}
+#ifdef XP_MACOSX
+done:
+#endif
+
return 0;
}
@@ -159,8 +176,13 @@ MimeMultipartAppleDouble_output_child_p(MimeObject *obj, MimeObject *child)
if (cont->nchildren >= 1 && cont->children[0] == child && child->content_type &&
!PL_strcasecmp(child->content_type, APPLICATION_APPLEFILE))
{
- /* Don't emit the resources fork. */
+#ifdef XP_MACOSX
+ if (obj->output_p && obj->options && obj->options->write_html_p) //output HTML
+ return false;
+#else
+ /* if we are not on a Macintosh, don't emitte the resources fork at all. */
return false;
+#endif
}
return true;
diff --git a/mailnews/mime/src/mimemult.cpp b/mailnews/mime/src/mimemult.cpp
index c92cc44dcf..4695ba9910 100644
--- a/mailnews/mime/src/mimemult.cpp
+++ b/mailnews/mime/src/mimemult.cpp
@@ -16,6 +16,10 @@
#include "nsMimeTypes.h"
#include <ctype.h>
+#ifdef XP_MACOSX
+ extern MimeObjectClass mimeMultipartAppleDoubleClass;
+#endif
+
#define MIME_SUPERCLASS mimeContainerClass
MimeDefClass(MimeMultipart, MimeMultipartClass,
mimeMultipartClass, &MIME_SUPERCLASS);
@@ -488,6 +492,19 @@ MimeMultipart_create_child(MimeObject *obj)
{
status = body->clazz->parse_begin(body);
+#ifdef XP_MACOSX
+ /* if we are saving an apple double attachment, we need to set correctly the conten type of the channel */
+ if (mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass))
+ {
+ mime_stream_data *msd = (mime_stream_data *)body->options->stream_closure;
+ if (!body->options->write_html_p && body->content_type && !PL_strcasecmp(body->content_type, APPLICATION_APPLEFILE))
+ {
+ if (msd && msd->channel)
+ msd->channel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_APPLEFILE));
+ }
+ }
+#endif
+
if (status < 0) return status;
}
diff --git a/mailnews/moz.build b/mailnews/moz.build
index 55b80c443a..6e75c8012e 100644
--- a/mailnews/moz.build
+++ b/mailnews/moz.build
@@ -23,6 +23,11 @@ DIRS += [
'news',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += [
+ 'import/applemail/src',
+ ]
+
if CONFIG['OS_ARCH'] == 'WINNT':
DIRS += [ 'import/becky/src' ]
diff --git a/media/ffvpx/config.h b/media/ffvpx/config.h
index f43da8f284..db2f7b42e1 100644
--- a/media/ffvpx/config.h
+++ b/media/ffvpx/config.h
@@ -26,6 +26,8 @@
#undef HAVE_LIBC_MSVCRT
#define HAVE_LIBC_MSVCRT 0
#endif
+#elif defined(XP_DARWIN)
+#include "config_darwin64.h"
#elif defined(XP_UNIX)
#if defined(HAVE_64BIT_BUILD)
#include "config_unix64.h"
diff --git a/media/ffvpx/config_darwin64.asm b/media/ffvpx/config_darwin64.asm
new file mode 100644
index 0000000000..bfaa6f05f5
--- /dev/null
+++ b/media/ffvpx/config_darwin64.asm
@@ -0,0 +1,642 @@
+; Automatically generated by configure - do not modify!
+%define ARCH_AARCH64 0
+%define ARCH_ALPHA 0
+%define ARCH_ARM 0
+%define ARCH_AVR32 0
+%define ARCH_AVR32_AP 0
+%define ARCH_AVR32_UC 0
+%define ARCH_BFIN 0
+%define ARCH_IA64 0
+%define ARCH_M68K 0
+%define ARCH_MIPS 0
+%define ARCH_MIPS64 0
+%define ARCH_PARISC 0
+%define ARCH_PPC 0
+%define ARCH_PPC64 0
+%define ARCH_S390 0
+%define ARCH_SH4 0
+%define ARCH_SPARC 0
+%define ARCH_SPARC64 0
+%define ARCH_TILEGX 0
+%define ARCH_TILEPRO 0
+%define ARCH_TOMI 0
+%define ARCH_X86 1
+%define ARCH_X86_32 0
+%define ARCH_X86_64 1
+%define HAVE_ARMV5TE 0
+%define HAVE_ARMV6 0
+%define HAVE_ARMV6T2 0
+%define HAVE_ARMV8 0
+%define HAVE_NEON 0
+%define HAVE_VFP 0
+%define HAVE_VFPV3 0
+%define HAVE_SETEND 0
+%define HAVE_ALTIVEC 0
+%define HAVE_DCBZL 0
+%define HAVE_LDBRX 0
+%define HAVE_POWER8 0
+%define HAVE_PPC4XX 0
+%define HAVE_VSX 0
+%define HAVE_AESNI 1
+%define HAVE_AMD3DNOW 1
+%define HAVE_AMD3DNOWEXT 1
+%define HAVE_AVX 1
+%define HAVE_AVX2 1
+%define HAVE_AVX512 1
+%define HAVE_FMA3 1
+%define HAVE_FMA4 1
+%define HAVE_MMX 1
+%define HAVE_MMXEXT 1
+%define HAVE_SSE 1
+%define HAVE_SSE2 1
+%define HAVE_SSE3 1
+%define HAVE_SSE4 1
+%define HAVE_SSE42 1
+%define HAVE_SSSE3 1
+%define HAVE_XOP 1
+%define HAVE_CPUNOP 0
+%define HAVE_I686 1
+%define HAVE_MIPSFPU 0
+%define HAVE_MIPS32R2 0
+%define HAVE_MIPS32R5 0
+%define HAVE_MIPS64R2 0
+%define HAVE_MIPS32R6 0
+%define HAVE_MIPS64R6 0
+%define HAVE_MIPSDSP 0
+%define HAVE_MIPSDSPR2 0
+%define HAVE_MSA 0
+%define HAVE_LOONGSON2 0
+%define HAVE_LOONGSON3 0
+%define HAVE_MMI 0
+%define HAVE_ARMV5TE_EXTERNAL 0
+%define HAVE_ARMV6_EXTERNAL 0
+%define HAVE_ARMV6T2_EXTERNAL 0
+%define HAVE_ARMV8_EXTERNAL 0
+%define HAVE_NEON_EXTERNAL 0
+%define HAVE_VFP_EXTERNAL 0
+%define HAVE_VFPV3_EXTERNAL 0
+%define HAVE_SETEND_EXTERNAL 0
+%define HAVE_ALTIVEC_EXTERNAL 0
+%define HAVE_DCBZL_EXTERNAL 0
+%define HAVE_LDBRX_EXTERNAL 0
+%define HAVE_POWER8_EXTERNAL 0
+%define HAVE_PPC4XX_EXTERNAL 0
+%define HAVE_VSX_EXTERNAL 0
+%define HAVE_AESNI_EXTERNAL 1
+%define HAVE_AMD3DNOW_EXTERNAL 1
+%define HAVE_AMD3DNOWEXT_EXTERNAL 1
+%define HAVE_AVX_EXTERNAL 1
+%define HAVE_AVX2_EXTERNAL 1
+%define HAVE_AVX512_EXTERNAL 1
+%define HAVE_FMA3_EXTERNAL 1
+%define HAVE_FMA4_EXTERNAL 1
+%define HAVE_MMX_EXTERNAL 1
+%define HAVE_MMXEXT_EXTERNAL 1
+%define HAVE_SSE_EXTERNAL 1
+%define HAVE_SSE2_EXTERNAL 1
+%define HAVE_SSE3_EXTERNAL 1
+%define HAVE_SSE4_EXTERNAL 1
+%define HAVE_SSE42_EXTERNAL 1
+%define HAVE_SSSE3_EXTERNAL 1
+%define HAVE_XOP_EXTERNAL 1
+%define HAVE_CPUNOP_EXTERNAL 0
+%define HAVE_I686_EXTERNAL 0
+%define HAVE_MIPSFPU_EXTERNAL 0
+%define HAVE_MIPS32R2_EXTERNAL 0
+%define HAVE_MIPS32R5_EXTERNAL 0
+%define HAVE_MIPS64R2_EXTERNAL 0
+%define HAVE_MIPS32R6_EXTERNAL 0
+%define HAVE_MIPS64R6_EXTERNAL 0
+%define HAVE_MIPSDSP_EXTERNAL 0
+%define HAVE_MIPSDSPR2_EXTERNAL 0
+%define HAVE_MSA_EXTERNAL 0
+%define HAVE_LOONGSON2_EXTERNAL 0
+%define HAVE_LOONGSON3_EXTERNAL 0
+%define HAVE_MMI_EXTERNAL 0
+%define HAVE_ARMV5TE_INLINE 0
+%define HAVE_ARMV6_INLINE 0
+%define HAVE_ARMV6T2_INLINE 0
+%define HAVE_ARMV8_INLINE 0
+%define HAVE_NEON_INLINE 0
+%define HAVE_VFP_INLINE 0
+%define HAVE_VFPV3_INLINE 0
+%define HAVE_SETEND_INLINE 0
+%define HAVE_ALTIVEC_INLINE 0
+%define HAVE_DCBZL_INLINE 0
+%define HAVE_LDBRX_INLINE 0
+%define HAVE_POWER8_INLINE 0
+%define HAVE_PPC4XX_INLINE 0
+%define HAVE_VSX_INLINE 0
+%define HAVE_AESNI_INLINE 1
+%define HAVE_AMD3DNOW_INLINE 1
+%define HAVE_AMD3DNOWEXT_INLINE 1
+%define HAVE_AVX_INLINE 1
+%define HAVE_AVX2_INLINE 1
+%define HAVE_AVX512_INLINE 1
+%define HAVE_FMA3_INLINE 1
+%define HAVE_FMA4_INLINE 1
+%define HAVE_MMX_INLINE 1
+%define HAVE_MMXEXT_INLINE 1
+%define HAVE_SSE_INLINE 1
+%define HAVE_SSE2_INLINE 1
+%define HAVE_SSE3_INLINE 1
+%define HAVE_SSE4_INLINE 1
+%define HAVE_SSE42_INLINE 1
+%define HAVE_SSSE3_INLINE 1
+%define HAVE_XOP_INLINE 1
+%define HAVE_CPUNOP_INLINE 0
+%define HAVE_I686_INLINE 0
+%define HAVE_MIPSFPU_INLINE 0
+%define HAVE_MIPS32R2_INLINE 0
+%define HAVE_MIPS32R5_INLINE 0
+%define HAVE_MIPS64R2_INLINE 0
+%define HAVE_MIPS32R6_INLINE 0
+%define HAVE_MIPS64R6_INLINE 0
+%define HAVE_MIPSDSP_INLINE 0
+%define HAVE_MIPSDSPR2_INLINE 0
+%define HAVE_MSA_INLINE 0
+%define HAVE_LOONGSON2_INLINE 0
+%define HAVE_LOONGSON3_INLINE 0
+%define HAVE_MMI_INLINE 0
+%define HAVE_ALIGNED_STACK 1
+%define HAVE_FAST_64BIT 1
+%define HAVE_FAST_CLZ 1
+%define HAVE_FAST_CMOV 1
+%define HAVE_LOCAL_ALIGNED 1
+%define HAVE_SIMD_ALIGN_16 1
+%define HAVE_SIMD_ALIGN_32 1
+%define HAVE_SIMD_ALIGN_64 1
+%define HAVE_ATOMIC_CAS_PTR 0
+%define HAVE_MACHINE_RW_BARRIER 0
+%define HAVE_MEMORYBARRIER 0
+%define HAVE_MM_EMPTY 1
+%define HAVE_RDTSC 0
+%define HAVE_SEM_TIMEDWAIT 0
+%define HAVE_SYNC_VAL_COMPARE_AND_SWAP 1
+%define HAVE_CABS 1
+%define HAVE_CEXP 1
+%define HAVE_INLINE_ASM 1
+%define HAVE_SYMVER 1
+%define HAVE_X86ASM 1
+%define HAVE_BIGENDIAN 0
+%define HAVE_FAST_UNALIGNED 1
+%define HAVE_ARPA_INET_H 1
+%define HAVE_ASM_TYPES_H 0
+%define HAVE_CDIO_PARANOIA_H 0
+%define HAVE_CDIO_PARANOIA_PARANOIA_H 0
+%define HAVE_CUDA_H 0
+%define HAVE_DISPATCH_DISPATCH_H 1
+%define HAVE_DEV_BKTR_IOCTL_BT848_H 0
+%define HAVE_DEV_BKTR_IOCTL_METEOR_H 0
+%define HAVE_DEV_IC_BT8XX_H 0
+%define HAVE_DEV_VIDEO_BKTR_IOCTL_BT848_H 0
+%define HAVE_DEV_VIDEO_METEOR_IOCTL_METEOR_H 0
+%define HAVE_DIRECT_H 0
+%define HAVE_DIRENT_H 1
+%define HAVE_DXGIDEBUG_H 0
+%define HAVE_DXVA_H 0
+%define HAVE_ES2_GL_H 0
+%define HAVE_GSM_H 0
+%define HAVE_IO_H 0
+%define HAVE_LINUX_PERF_EVENT_H 0
+%define HAVE_MACHINE_IOCTL_BT848_H 0
+%define HAVE_MACHINE_IOCTL_METEOR_H 0
+%define HAVE_OPENCV2_CORE_CORE_C_H 0
+%define HAVE_OPENGL_GL3_H 0
+%define HAVE_POLL_H 1
+%define HAVE_SYS_PARAM_H 1
+%define HAVE_SYS_RESOURCE_H 1
+%define HAVE_SYS_SELECT_H 1
+%define HAVE_SYS_SOUNDCARD_H 0
+%define HAVE_SYS_TIME_H 1
+%define HAVE_SYS_UN_H 1
+%define HAVE_SYS_VIDEOIO_H 0
+%define HAVE_TERMIOS_H 1
+%define HAVE_UDPLITE_H 0
+%define HAVE_UNISTD_H 1
+%define HAVE_VALGRIND_VALGRIND_H 0
+%define HAVE_WINDOWS_H 0
+%define HAVE_WINSOCK2_H 0
+%define HAVE_INTRINSICS_NEON 0
+%define HAVE_ATANF 1
+%define HAVE_ATAN2F 1
+%define HAVE_CBRT 1
+%define HAVE_CBRTF 1
+%define HAVE_COPYSIGN 1
+%define HAVE_COSF 1
+%define HAVE_ERF 1
+%define HAVE_EXP2 1
+%define HAVE_EXP2F 1
+%define HAVE_EXPF 1
+%define HAVE_HYPOT 1
+%define HAVE_ISFINITE 1
+%define HAVE_ISINF 1
+%define HAVE_ISNAN 1
+%define HAVE_LDEXPF 1
+%define HAVE_LLRINT 1
+%define HAVE_LLRINTF 1
+%define HAVE_LOG2 1
+%define HAVE_LOG2F 1
+%define HAVE_LOG10F 1
+%define HAVE_LRINT 1
+%define HAVE_LRINTF 1
+%define HAVE_POWF 1
+%define HAVE_RINT 1
+%define HAVE_ROUND 1
+%define HAVE_ROUNDF 1
+%define HAVE_SINF 1
+%define HAVE_TRUNC 1
+%define HAVE_TRUNCF 1
+%define HAVE_DOS_PATHS 0
+%define HAVE_LIBC_MSVCRT 0
+%define HAVE_MMAL_PARAMETER_VIDEO_MAX_NUM_CALLBACKS 0
+%define HAVE_SECTION_DATA_REL_RO 0
+%define HAVE_THREADS 1
+%define HAVE_UWP 0
+%define HAVE_WINRT 0
+%define HAVE_ACCESS 1
+%define HAVE_ALIGNED_MALLOC 0
+%define HAVE_CLOCK_GETTIME 1
+%define HAVE_CLOSESOCKET 0
+%define HAVE_COMMANDLINETOARGVW 0
+%define HAVE_FCNTL 1
+%define HAVE_GETADDRINFO 1
+%define HAVE_GETHRTIME 0
+%define HAVE_GETOPT 1
+%define HAVE_GETPROCESSAFFINITYMASK 0
+%define HAVE_GETPROCESSMEMORYINFO 0
+%define HAVE_GETPROCESSTIMES 0
+%define HAVE_GETRUSAGE 1
+%define HAVE_GETSYSTEMTIMEASFILETIME 0
+%define HAVE_GETTIMEOFDAY 1
+%define HAVE_GLOB 1
+%define HAVE_GLXGETPROCADDRESS 0
+%define HAVE_GMTIME_R 1
+%define HAVE_INET_ATON 1
+%define HAVE_ISATTY 1
+%define HAVE_KBHIT 0
+%define HAVE_LSTAT 1
+%define HAVE_LZO1X_999_COMPRESS 0
+%define HAVE_MACH_ABSOLUTE_TIME 1
+%define HAVE_MAPVIEWOFFILE 0
+%define HAVE_MKSTEMP 1
+%define HAVE_MMAP 1
+%define HAVE_MPROTECT 1
+%define HAVE_NANOSLEEP 1
+%define HAVE_PEEKNAMEDPIPE 0
+%define HAVE_PTHREAD_CANCEL 1
+%define HAVE_SCHED_GETAFFINITY 0
+%define HAVE_SECITEMIMPORT 0
+%define HAVE_SETCONSOLETEXTATTRIBUTE 0
+%define HAVE_SETCONSOLECTRLHANDLER 0
+%define HAVE_SETMODE 0
+%define HAVE_SETRLIMIT 1
+%define HAVE_SLEEP 0
+%define HAVE_STRERROR_R 1
+%define HAVE_SYSCONF 1
+%define HAVE_SYSCTL 1
+%define HAVE_USLEEP 1
+%define HAVE_UTGETOSTYPEFROMSTRING 0
+%define HAVE_VIRTUALALLOC 0
+%define HAVE_WGLGETPROCADDRESS 0
+%define HAVE_BCRYPT 0
+%define HAVE_VAAPI_DRM 0
+%define HAVE_VAAPI_X11 0
+%define HAVE_VDPAU_X11 0
+%define HAVE_PTHREADS 1
+%define HAVE_OS2THREADS 0
+%define HAVE_W32THREADS 0
+%define HAVE_AS_ARCH_DIRECTIVE 0
+%define HAVE_AS_DN_DIRECTIVE 0
+%define HAVE_AS_FPU_DIRECTIVE 0
+%define HAVE_AS_FUNC 0
+%define HAVE_AS_OBJECT_ARCH 0
+%define HAVE_ASM_MOD_Q 0
+%define HAVE_BLOCKS_EXTENSION 1
+%define HAVE_EBP_AVAILABLE 1
+%define HAVE_EBX_AVAILABLE 1
+%define HAVE_GNU_AS 0
+%define HAVE_GNU_WINDRES 0
+%define HAVE_IBM_ASM 0
+%define HAVE_INLINE_ASM_DIRECT_SYMBOL_REFS 1
+%define HAVE_INLINE_ASM_LABELS 1
+%define HAVE_INLINE_ASM_NONLOCAL_LABELS 1
+%define HAVE_PRAGMA_DEPRECATED 1
+%define HAVE_RSYNC_CONTIMEOUT 0
+%define HAVE_SYMVER_ASM_LABEL 1
+%define HAVE_SYMVER_GNU_ASM 0
+%define HAVE_VFP_ARGS 0
+%define HAVE_XFORM_ASM 0
+%define HAVE_XMM_CLOBBERS 1
+%define HAVE_KCMVIDEOCODECTYPE_HEVC 0
+%define HAVE_SOCKLEN_T 1
+%define HAVE_STRUCT_ADDRINFO 1
+%define HAVE_STRUCT_GROUP_SOURCE_REQ 1
+%define HAVE_STRUCT_IP_MREQ_SOURCE 1
+%define HAVE_STRUCT_IPV6_MREQ 1
+%define HAVE_STRUCT_MSGHDR_MSG_FLAGS 1
+%define HAVE_STRUCT_POLLFD 1
+%define HAVE_STRUCT_RUSAGE_RU_MAXRSS 1
+%define HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE 0
+%define HAVE_STRUCT_SOCKADDR_IN6 1
+%define HAVE_STRUCT_SOCKADDR_SA_LEN 1
+%define HAVE_STRUCT_SOCKADDR_STORAGE 1
+%define HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 0
+%define HAVE_STRUCT_V4L2_FRMIVALENUM_DISCRETE 0
+%define HAVE_MAKEINFO 1
+%define HAVE_MAKEINFO_HTML 0
+%define HAVE_OPENCL_D3D11 0
+%define HAVE_OPENCL_DRM_ARM 0
+%define HAVE_OPENCL_DRM_BEIGNET 0
+%define HAVE_OPENCL_DXVA2 0
+%define HAVE_OPENCL_VAAPI_BEIGNET 0
+%define HAVE_OPENCL_VAAPI_INTEL_MEDIA 0
+%define HAVE_PERL 1
+%define HAVE_POD2MAN 1
+%define HAVE_TEXI2HTML 1
+%define CONFIG_DOC 0
+%define CONFIG_HTMLPAGES 1
+%define CONFIG_MANPAGES 1
+%define CONFIG_PODPAGES 1
+%define CONFIG_TXTPAGES 1
+%define CONFIG_AVIO_DIR_CMD_EXAMPLE 1
+%define CONFIG_AVIO_READING_EXAMPLE 1
+%define CONFIG_DECODE_AUDIO_EXAMPLE 1
+%define CONFIG_DECODE_VIDEO_EXAMPLE 1
+%define CONFIG_DEMUXING_DECODING_EXAMPLE 0
+%define CONFIG_ENCODE_AUDIO_EXAMPLE 1
+%define CONFIG_ENCODE_VIDEO_EXAMPLE 1
+%define CONFIG_EXTRACT_MVS_EXAMPLE 0
+%define CONFIG_FILTER_AUDIO_EXAMPLE 0
+%define CONFIG_FILTERING_AUDIO_EXAMPLE 0
+%define CONFIG_FILTERING_VIDEO_EXAMPLE 0
+%define CONFIG_HTTP_MULTICLIENT_EXAMPLE 0
+%define CONFIG_HW_DECODE_EXAMPLE 0
+%define CONFIG_METADATA_EXAMPLE 0
+%define CONFIG_MUXING_EXAMPLE 0
+%define CONFIG_QSVDEC_EXAMPLE 0
+%define CONFIG_REMUXING_EXAMPLE 0
+%define CONFIG_RESAMPLING_AUDIO_EXAMPLE 0
+%define CONFIG_SCALING_VIDEO_EXAMPLE 0
+%define CONFIG_TRANSCODE_AAC_EXAMPLE 0
+%define CONFIG_TRANSCODING_EXAMPLE 0
+%define CONFIG_VAAPI_ENCODE_EXAMPLE 0
+%define CONFIG_VAAPI_TRANSCODE_EXAMPLE 0
+%define CONFIG_AVISYNTH 0
+%define CONFIG_FREI0R 0
+%define CONFIG_LIBCDIO 0
+%define CONFIG_LIBRUBBERBAND 0
+%define CONFIG_LIBVIDSTAB 0
+%define CONFIG_LIBX264 0
+%define CONFIG_LIBX265 0
+%define CONFIG_LIBXAVS 0
+%define CONFIG_LIBXVID 0
+%define CONFIG_DECKLINK 0
+%define CONFIG_LIBNDI_NEWTEK 0
+%define CONFIG_LIBFDK_AAC 0
+%define CONFIG_OPENSSL 0
+%define CONFIG_LIBTLS 0
+%define CONFIG_GMP 0
+%define CONFIG_LIBOPENCORE_AMRNB 0
+%define CONFIG_LIBOPENCORE_AMRWB 0
+%define CONFIG_LIBVMAF 0
+%define CONFIG_LIBVO_AMRWBENC 0
+%define CONFIG_RKMPP 0
+%define CONFIG_LIBSMBCLIENT 0
+%define CONFIG_CHROMAPRINT 0
+%define CONFIG_GCRYPT 0
+%define CONFIG_GNUTLS 0
+%define CONFIG_JNI 0
+%define CONFIG_LADSPA 0
+%define CONFIG_LIBAOM 0
+%define CONFIG_LIBASS 0
+%define CONFIG_LIBBLURAY 0
+%define CONFIG_LIBBS2B 0
+%define CONFIG_LIBCACA 0
+%define CONFIG_LIBCELT 0
+%define CONFIG_LIBCODEC2 0
+%define CONFIG_LIBDC1394 0
+%define CONFIG_LIBDRM 0
+%define CONFIG_LIBFLITE 0
+%define CONFIG_LIBFONTCONFIG 0
+%define CONFIG_LIBFREETYPE 0
+%define CONFIG_LIBFRIBIDI 0
+%define CONFIG_LIBGME 0
+%define CONFIG_LIBGSM 0
+%define CONFIG_LIBIEC61883 0
+%define CONFIG_LIBILBC 0
+%define CONFIG_LIBJACK 0
+%define CONFIG_LIBKVAZAAR 0
+%define CONFIG_LIBMODPLUG 0
+%define CONFIG_LIBMP3LAME 0
+%define CONFIG_LIBMYSOFA 0
+%define CONFIG_LIBOPENCV 0
+%define CONFIG_LIBOPENH264 0
+%define CONFIG_LIBOPENJPEG 0
+%define CONFIG_LIBOPENMPT 0
+%define CONFIG_LIBOPUS 0
+%define CONFIG_LIBPULSE 0
+%define CONFIG_LIBRSVG 0
+%define CONFIG_LIBRTMP 0
+%define CONFIG_LIBSHINE 0
+%define CONFIG_LIBSMBCLIENT 0
+%define CONFIG_LIBSNAPPY 0
+%define CONFIG_LIBSOXR 0
+%define CONFIG_LIBSPEEX 0
+%define CONFIG_LIBSRT 0
+%define CONFIG_LIBSSH 0
+%define CONFIG_LIBTESSERACT 0
+%define CONFIG_LIBTHEORA 0
+%define CONFIG_LIBTWOLAME 0
+%define CONFIG_LIBV4L2 0
+%define CONFIG_LIBVORBIS 0
+%define CONFIG_LIBVPX 0
+%define CONFIG_LIBWAVPACK 0
+%define CONFIG_LIBWEBP 0
+%define CONFIG_LIBXML2 0
+%define CONFIG_LIBZIMG 0
+%define CONFIG_LIBZMQ 0
+%define CONFIG_LIBZVBI 0
+%define CONFIG_LV2 0
+%define CONFIG_MEDIACODEC 0
+%define CONFIG_OPENAL 0
+%define CONFIG_OPENGL 0
+%define CONFIG_ALSA 0
+%define CONFIG_APPKIT 1
+%define CONFIG_AVFOUNDATION 1
+%define CONFIG_BZLIB 1
+%define CONFIG_COREIMAGE 1
+%define CONFIG_ICONV 0
+%define CONFIG_LIBXCB 0
+%define CONFIG_LIBXCB_SHM 0
+%define CONFIG_LIBXCB_SHAPE 0
+%define CONFIG_LIBXCB_XFIXES 0
+%define CONFIG_LZMA 1
+%define CONFIG_SCHANNEL 0
+%define CONFIG_SDL2 0
+%define CONFIG_SECURETRANSPORT 0
+%define CONFIG_SNDIO 0
+%define CONFIG_XLIB 1
+%define CONFIG_ZLIB 1
+%define CONFIG_CUDA_SDK 0
+%define CONFIG_LIBNPP 0
+%define CONFIG_LIBMFX 0
+%define CONFIG_MMAL 0
+%define CONFIG_OMX 0
+%define CONFIG_OPENCL 0
+%define CONFIG_AMF 0
+%define CONFIG_AUDIOTOOLBOX 1
+%define CONFIG_CRYSTALHD 0
+%define CONFIG_CUDA 0
+%define CONFIG_CUVID 0
+%define CONFIG_D3D11VA 0
+%define CONFIG_DXVA2 0
+%define CONFIG_FFNVCODEC 0
+%define CONFIG_NVDEC 0
+%define CONFIG_NVENC 0
+%define CONFIG_VAAPI 0
+%define CONFIG_VDPAU 0
+%define CONFIG_VIDEOTOOLBOX 0
+%define CONFIG_V4L2_M2M 0
+%define CONFIG_XVMC 0
+%define CONFIG_FTRAPV 0
+%define CONFIG_GRAY 0
+%define CONFIG_HARDCODED_TABLES 0
+%define CONFIG_OMX_RPI 0
+%define CONFIG_RUNTIME_CPUDETECT 1
+%define CONFIG_SAFE_BITSTREAM_READER 1
+%define CONFIG_SHARED 1
+%define CONFIG_SMALL 0
+%define CONFIG_STATIC 0
+%define CONFIG_SWSCALE_ALPHA 1
+%define CONFIG_GPL 0
+%define CONFIG_NONFREE 0
+%define CONFIG_VERSION3 0
+%define CONFIG_AVCODEC 1
+%define CONFIG_AVDEVICE 0
+%define CONFIG_AVFILTER 0
+%define CONFIG_AVFORMAT 0
+%define CONFIG_AVRESAMPLE 0
+%define CONFIG_AVUTIL 1
+%define CONFIG_POSTPROC 0
+%define CONFIG_SWRESAMPLE 0
+%define CONFIG_SWSCALE 0
+%define CONFIG_FFPLAY 0
+%define CONFIG_FFPROBE 0
+%define CONFIG_FFMPEG 0
+%define CONFIG_DCT 0
+%define CONFIG_DWT 0
+%define CONFIG_ERROR_RESILIENCE 0
+%define CONFIG_FAAN 1
+%define CONFIG_FAST_UNALIGNED 1
+%define CONFIG_FFT 0
+%define CONFIG_LSP 0
+%define CONFIG_LZO 0
+%define CONFIG_MDCT 0
+%define CONFIG_PIXELUTILS 0
+%define CONFIG_NETWORK 0
+%define CONFIG_RDFT 0
+%define CONFIG_AUTODETECT 0
+%define CONFIG_FONTCONFIG 0
+%define CONFIG_LINUX_PERF 0
+%define CONFIG_MEMORY_POISONING 0
+%define CONFIG_NEON_CLOBBER_TEST 0
+%define CONFIG_OSSFUZZ 0
+%define CONFIG_PIC 1
+%define CONFIG_THUMB 0
+%define CONFIG_VALGRIND_BACKTRACE 0
+%define CONFIG_XMM_CLOBBER_TEST 0
+%define CONFIG_BSFS 1
+%define CONFIG_DECODERS 1
+%define CONFIG_PARSERS 1
+%define CONFIG_AANDCTTABLES 0
+%define CONFIG_AC3DSP 0
+%define CONFIG_ADTS_HEADER 0
+%define CONFIG_AUDIO_FRAME_QUEUE 0
+%define CONFIG_AUDIODSP 0
+%define CONFIG_BLOCKDSP 0
+%define CONFIG_BSWAPDSP 0
+%define CONFIG_CABAC 0
+%define CONFIG_CBS 0
+%define CONFIG_CBS_H264 0
+%define CONFIG_CBS_H265 0
+%define CONFIG_CBS_MPEG2 0
+%define CONFIG_DIRAC_PARSE 0
+%define CONFIG_DVPROFILE 0
+%define CONFIG_EXIF 0
+%define CONFIG_FAANDCT 1
+%define CONFIG_FAANIDCT 1
+%define CONFIG_FDCTDSP 1
+%define CONFIG_FLACDSP 1
+%define CONFIG_FMTCONVERT 0
+%define CONFIG_G722DSP 0
+%define CONFIG_GOLOMB 0
+%define CONFIG_GPLV3 0
+%define CONFIG_H263DSP 0
+%define CONFIG_H264CHROMA 0
+%define CONFIG_H264DSP 0
+%define CONFIG_H264PARSE 0
+%define CONFIG_H264PRED 1
+%define CONFIG_H264QPEL 0
+%define CONFIG_HEVCPARSE 0
+%define CONFIG_HPELDSP 0
+%define CONFIG_HUFFMAN 0
+%define CONFIG_HUFFYUVDSP 0
+%define CONFIG_HUFFYUVENCDSP 0
+%define CONFIG_IDCTDSP 1
+%define CONFIG_IIRFILTER 0
+%define CONFIG_MDCT15 0
+%define CONFIG_INTRAX8 0
+%define CONFIG_ISO_MEDIA 0
+%define CONFIG_IVIDSP 0
+%define CONFIG_JPEGTABLES 0
+%define CONFIG_LGPLV3 0
+%define CONFIG_LIBX262 0
+%define CONFIG_LLAUDDSP 0
+%define CONFIG_LLVIDDSP 0
+%define CONFIG_LLVIDENCDSP 0
+%define CONFIG_LPC 0
+%define CONFIG_LZF 0
+%define CONFIG_ME_CMP 0
+%define CONFIG_MPEG_ER 0
+%define CONFIG_MPEGAUDIO 0
+%define CONFIG_MPEGAUDIODSP 0
+%define CONFIG_MPEGAUDIOHEADER 0
+%define CONFIG_MPEGVIDEO 0
+%define CONFIG_MPEGVIDEOENC 0
+%define CONFIG_MSS34DSP 0
+%define CONFIG_PIXBLOCKDSP 0
+%define CONFIG_QPELDSP 0
+%define CONFIG_QSV 0
+%define CONFIG_QSVDEC 0
+%define CONFIG_QSVENC 0
+%define CONFIG_QSVVPP 0
+%define CONFIG_RANGECODER 0
+%define CONFIG_RIFFDEC 0
+%define CONFIG_RIFFENC 0
+%define CONFIG_RTPDEC 0
+%define CONFIG_RTPENC_CHAIN 0
+%define CONFIG_RV34DSP 0
+%define CONFIG_SINEWIN 0
+%define CONFIG_SNAPPY 0
+%define CONFIG_SRTP 0
+%define CONFIG_STARTCODE 0
+%define CONFIG_TEXTUREDSP 0
+%define CONFIG_TEXTUREDSPENC 0
+%define CONFIG_TPELDSP 0
+%define CONFIG_VAAPI_1 0
+%define CONFIG_VAAPI_ENCODE 0
+%define CONFIG_VC1DSP 0
+%define CONFIG_VIDEODSP 1
+%define CONFIG_VP3DSP 0
+%define CONFIG_VP56DSP 0
+%define CONFIG_VP8DSP 1
+%define CONFIG_WMA_FREQS 0
+%define CONFIG_WMV2DSP 0
+%define CONFIG_NULL_BSF 1
+%define CONFIG_VP9_SUPERFRAME_SPLIT_BSF 1
+%define CONFIG_VP8_DECODER 1
+%define CONFIG_VP9_DECODER 1
+%define CONFIG_FLAC_DECODER 1
+%define CONFIG_VP8_PARSER 1
+%define CONFIG_VP9_PARSER 1
diff --git a/media/ffvpx/config_darwin64.h b/media/ffvpx/config_darwin64.h
new file mode 100644
index 0000000000..03f19b3c25
--- /dev/null
+++ b/media/ffvpx/config_darwin64.h
@@ -0,0 +1,658 @@
+/* Automatically generated by configure - do not modify! */
+#ifndef FFMPEG_CONFIG_H
+#define FFMPEG_CONFIG_H
+#define FFMPEG_CONFIGURATION "--disable-everything --disable-protocols --disable-demuxers --disable-muxers --disable-filters --disable-programs --disable-doc --disable-parsers --enable-parser=vp8 --enable-parser=vp9 --enable-decoder=vp8 --enable-decoder=vp9 --disable-static --enable-shared --disable-debug --disable-sdl2 --disable-libxcb --disable-securetransport --disable-iconv --disable-swresample --disable-swscale --disable-avdevice --disable-avfilter --disable-avformat --disable-d3d11va --disable-dxva2 --disable-vaapi --disable-vdpau --disable-videotoolbox --enable-decoder=flac --enable-asm --enable-x86asm --disable-cuda --disable-cuvid"
+#define FFMPEG_LICENSE "LGPL version 2.1 or later"
+#define CONFIG_THIS_YEAR 2018
+#define FFMPEG_DATADIR "/usr/local/share/ffmpeg"
+#define AVCONV_DATADIR "/usr/local/share/ffmpeg"
+#define CC_IDENT "Apple LLVM version 9.1.0 (clang-902.0.39.2)"
+#define av_restrict restrict
+#define EXTERN_PREFIX "_"
+#define EXTERN_ASM _
+#define BUILDSUF ""
+#define SLIBSUF ".dylib"
+#define HAVE_MMX2 HAVE_MMXEXT
+#define SWS_MAX_FILTER_SIZE 256
+#define ARCH_AARCH64 0
+#define ARCH_ALPHA 0
+#define ARCH_ARM 0
+#define ARCH_AVR32 0
+#define ARCH_AVR32_AP 0
+#define ARCH_AVR32_UC 0
+#define ARCH_BFIN 0
+#define ARCH_IA64 0
+#define ARCH_M68K 0
+#define ARCH_MIPS 0
+#define ARCH_MIPS64 0
+#define ARCH_PARISC 0
+#define ARCH_PPC 0
+#define ARCH_PPC64 0
+#define ARCH_S390 0
+#define ARCH_SH4 0
+#define ARCH_SPARC 0
+#define ARCH_SPARC64 0
+#define ARCH_TILEGX 0
+#define ARCH_TILEPRO 0
+#define ARCH_TOMI 0
+#define ARCH_X86 1
+#define ARCH_X86_32 0
+#define ARCH_X86_64 1
+#define HAVE_ARMV5TE 0
+#define HAVE_ARMV6 0
+#define HAVE_ARMV6T2 0
+#define HAVE_ARMV8 0
+#define HAVE_NEON 0
+#define HAVE_VFP 0
+#define HAVE_VFPV3 0
+#define HAVE_SETEND 0
+#define HAVE_ALTIVEC 0
+#define HAVE_DCBZL 0
+#define HAVE_LDBRX 0
+#define HAVE_POWER8 0
+#define HAVE_PPC4XX 0
+#define HAVE_VSX 0
+#define HAVE_AESNI 1
+#define HAVE_AMD3DNOW 1
+#define HAVE_AMD3DNOWEXT 1
+#define HAVE_AVX 1
+#define HAVE_AVX2 1
+#define HAVE_AVX512 1
+#define HAVE_FMA3 1
+#define HAVE_FMA4 1
+#define HAVE_MMX 1
+#define HAVE_MMXEXT 1
+#define HAVE_SSE 1
+#define HAVE_SSE2 1
+#define HAVE_SSE3 1
+#define HAVE_SSE4 1
+#define HAVE_SSE42 1
+#define HAVE_SSSE3 1
+#define HAVE_XOP 1
+#define HAVE_CPUNOP 0
+#define HAVE_I686 1
+#define HAVE_MIPSFPU 0
+#define HAVE_MIPS32R2 0
+#define HAVE_MIPS32R5 0
+#define HAVE_MIPS64R2 0
+#define HAVE_MIPS32R6 0
+#define HAVE_MIPS64R6 0
+#define HAVE_MIPSDSP 0
+#define HAVE_MIPSDSPR2 0
+#define HAVE_MSA 0
+#define HAVE_LOONGSON2 0
+#define HAVE_LOONGSON3 0
+#define HAVE_MMI 0
+#define HAVE_ARMV5TE_EXTERNAL 0
+#define HAVE_ARMV6_EXTERNAL 0
+#define HAVE_ARMV6T2_EXTERNAL 0
+#define HAVE_ARMV8_EXTERNAL 0
+#define HAVE_NEON_EXTERNAL 0
+#define HAVE_VFP_EXTERNAL 0
+#define HAVE_VFPV3_EXTERNAL 0
+#define HAVE_SETEND_EXTERNAL 0
+#define HAVE_ALTIVEC_EXTERNAL 0
+#define HAVE_DCBZL_EXTERNAL 0
+#define HAVE_LDBRX_EXTERNAL 0
+#define HAVE_POWER8_EXTERNAL 0
+#define HAVE_PPC4XX_EXTERNAL 0
+#define HAVE_VSX_EXTERNAL 0
+#define HAVE_AESNI_EXTERNAL 1
+#define HAVE_AMD3DNOW_EXTERNAL 1
+#define HAVE_AMD3DNOWEXT_EXTERNAL 1
+#define HAVE_AVX_EXTERNAL 1
+#define HAVE_AVX2_EXTERNAL 1
+#define HAVE_AVX512_EXTERNAL 1
+#define HAVE_FMA3_EXTERNAL 1
+#define HAVE_FMA4_EXTERNAL 1
+#define HAVE_MMX_EXTERNAL 1
+#define HAVE_MMXEXT_EXTERNAL 1
+#define HAVE_SSE_EXTERNAL 1
+#define HAVE_SSE2_EXTERNAL 1
+#define HAVE_SSE3_EXTERNAL 1
+#define HAVE_SSE4_EXTERNAL 1
+#define HAVE_SSE42_EXTERNAL 1
+#define HAVE_SSSE3_EXTERNAL 1
+#define HAVE_XOP_EXTERNAL 1
+#define HAVE_CPUNOP_EXTERNAL 0
+#define HAVE_I686_EXTERNAL 0
+#define HAVE_MIPSFPU_EXTERNAL 0
+#define HAVE_MIPS32R2_EXTERNAL 0
+#define HAVE_MIPS32R5_EXTERNAL 0
+#define HAVE_MIPS64R2_EXTERNAL 0
+#define HAVE_MIPS32R6_EXTERNAL 0
+#define HAVE_MIPS64R6_EXTERNAL 0
+#define HAVE_MIPSDSP_EXTERNAL 0
+#define HAVE_MIPSDSPR2_EXTERNAL 0
+#define HAVE_MSA_EXTERNAL 0
+#define HAVE_LOONGSON2_EXTERNAL 0
+#define HAVE_LOONGSON3_EXTERNAL 0
+#define HAVE_MMI_EXTERNAL 0
+#define HAVE_ARMV5TE_INLINE 0
+#define HAVE_ARMV6_INLINE 0
+#define HAVE_ARMV6T2_INLINE 0
+#define HAVE_ARMV8_INLINE 0
+#define HAVE_NEON_INLINE 0
+#define HAVE_VFP_INLINE 0
+#define HAVE_VFPV3_INLINE 0
+#define HAVE_SETEND_INLINE 0
+#define HAVE_ALTIVEC_INLINE 0
+#define HAVE_DCBZL_INLINE 0
+#define HAVE_LDBRX_INLINE 0
+#define HAVE_POWER8_INLINE 0
+#define HAVE_PPC4XX_INLINE 0
+#define HAVE_VSX_INLINE 0
+#define HAVE_AESNI_INLINE 1
+#define HAVE_AMD3DNOW_INLINE 1
+#define HAVE_AMD3DNOWEXT_INLINE 1
+#define HAVE_AVX_INLINE 1
+#define HAVE_AVX2_INLINE 1
+#define HAVE_AVX512_INLINE 1
+#define HAVE_FMA3_INLINE 1
+#define HAVE_FMA4_INLINE 1
+#define HAVE_MMX_INLINE 1
+#define HAVE_MMXEXT_INLINE 1
+#define HAVE_SSE_INLINE 1
+#define HAVE_SSE2_INLINE 1
+#define HAVE_SSE3_INLINE 1
+#define HAVE_SSE4_INLINE 1
+#define HAVE_SSE42_INLINE 1
+#define HAVE_SSSE3_INLINE 1
+#define HAVE_XOP_INLINE 1
+#define HAVE_CPUNOP_INLINE 0
+#define HAVE_I686_INLINE 0
+#define HAVE_MIPSFPU_INLINE 0
+#define HAVE_MIPS32R2_INLINE 0
+#define HAVE_MIPS32R5_INLINE 0
+#define HAVE_MIPS64R2_INLINE 0
+#define HAVE_MIPS32R6_INLINE 0
+#define HAVE_MIPS64R6_INLINE 0
+#define HAVE_MIPSDSP_INLINE 0
+#define HAVE_MIPSDSPR2_INLINE 0
+#define HAVE_MSA_INLINE 0
+#define HAVE_LOONGSON2_INLINE 0
+#define HAVE_LOONGSON3_INLINE 0
+#define HAVE_MMI_INLINE 0
+#define HAVE_ALIGNED_STACK 1
+#define HAVE_FAST_64BIT 1
+#define HAVE_FAST_CLZ 1
+#define HAVE_FAST_CMOV 1
+#define HAVE_LOCAL_ALIGNED 1
+#define HAVE_SIMD_ALIGN_16 1
+#define HAVE_SIMD_ALIGN_32 1
+#define HAVE_SIMD_ALIGN_64 1
+#define HAVE_ATOMIC_CAS_PTR 0
+#define HAVE_MACHINE_RW_BARRIER 0
+#define HAVE_MEMORYBARRIER 0
+#define HAVE_MM_EMPTY 1
+#define HAVE_RDTSC 0
+#define HAVE_SEM_TIMEDWAIT 0
+#define HAVE_SYNC_VAL_COMPARE_AND_SWAP 1
+#define HAVE_CABS 1
+#define HAVE_CEXP 1
+#define HAVE_INLINE_ASM 1
+#define HAVE_SYMVER 1
+#define HAVE_X86ASM 1
+#define HAVE_BIGENDIAN 0
+#define HAVE_FAST_UNALIGNED 1
+#define HAVE_ARPA_INET_H 1
+#define HAVE_ASM_TYPES_H 0
+#define HAVE_CDIO_PARANOIA_H 0
+#define HAVE_CDIO_PARANOIA_PARANOIA_H 0
+#define HAVE_CUDA_H 0
+#define HAVE_DISPATCH_DISPATCH_H 1
+#define HAVE_DEV_BKTR_IOCTL_BT848_H 0
+#define HAVE_DEV_BKTR_IOCTL_METEOR_H 0
+#define HAVE_DEV_IC_BT8XX_H 0
+#define HAVE_DEV_VIDEO_BKTR_IOCTL_BT848_H 0
+#define HAVE_DEV_VIDEO_METEOR_IOCTL_METEOR_H 0
+#define HAVE_DIRECT_H 0
+#define HAVE_DIRENT_H 1
+#define HAVE_DXGIDEBUG_H 0
+#define HAVE_DXVA_H 0
+#define HAVE_ES2_GL_H 0
+#define HAVE_GSM_H 0
+#define HAVE_IO_H 0
+#define HAVE_LINUX_PERF_EVENT_H 0
+#define HAVE_MACHINE_IOCTL_BT848_H 0
+#define HAVE_MACHINE_IOCTL_METEOR_H 0
+#define HAVE_OPENCV2_CORE_CORE_C_H 0
+#define HAVE_OPENGL_GL3_H 0
+#define HAVE_POLL_H 1
+#define HAVE_SYS_PARAM_H 1
+#define HAVE_SYS_RESOURCE_H 1
+#define HAVE_SYS_SELECT_H 1
+#define HAVE_SYS_SOUNDCARD_H 0
+#define HAVE_SYS_TIME_H 1
+#define HAVE_SYS_UN_H 1
+#define HAVE_SYS_VIDEOIO_H 0
+#define HAVE_TERMIOS_H 1
+#define HAVE_UDPLITE_H 0
+#define HAVE_UNISTD_H 1
+#define HAVE_VALGRIND_VALGRIND_H 0
+#define HAVE_WINDOWS_H 0
+#define HAVE_WINSOCK2_H 0
+#define HAVE_INTRINSICS_NEON 0
+#define HAVE_ATANF 1
+#define HAVE_ATAN2F 1
+#define HAVE_CBRT 1
+#define HAVE_CBRTF 1
+#define HAVE_COPYSIGN 1
+#define HAVE_COSF 1
+#define HAVE_ERF 1
+#define HAVE_EXP2 1
+#define HAVE_EXP2F 1
+#define HAVE_EXPF 1
+#define HAVE_HYPOT 1
+#define HAVE_ISFINITE 1
+#define HAVE_ISINF 1
+#define HAVE_ISNAN 1
+#define HAVE_LDEXPF 1
+#define HAVE_LLRINT 1
+#define HAVE_LLRINTF 1
+#define HAVE_LOG2 1
+#define HAVE_LOG2F 1
+#define HAVE_LOG10F 1
+#define HAVE_LRINT 1
+#define HAVE_LRINTF 1
+#define HAVE_POWF 1
+#define HAVE_RINT 1
+#define HAVE_ROUND 1
+#define HAVE_ROUNDF 1
+#define HAVE_SINF 1
+#define HAVE_TRUNC 1
+#define HAVE_TRUNCF 1
+#define HAVE_DOS_PATHS 0
+#define HAVE_LIBC_MSVCRT 0
+#define HAVE_MMAL_PARAMETER_VIDEO_MAX_NUM_CALLBACKS 0
+#define HAVE_SECTION_DATA_REL_RO 0
+#define HAVE_THREADS 1
+#define HAVE_UWP 0
+#define HAVE_WINRT 0
+#define HAVE_ACCESS 1
+#define HAVE_ALIGNED_MALLOC 0
+#define HAVE_CLOCK_GETTIME 1
+#define HAVE_CLOSESOCKET 0
+#define HAVE_COMMANDLINETOARGVW 0
+#define HAVE_FCNTL 1
+#define HAVE_GETADDRINFO 1
+#define HAVE_GETHRTIME 0
+#define HAVE_GETOPT 1
+#define HAVE_GETPROCESSAFFINITYMASK 0
+#define HAVE_GETPROCESSMEMORYINFO 0
+#define HAVE_GETPROCESSTIMES 0
+#define HAVE_GETRUSAGE 1
+#define HAVE_GETSYSTEMTIMEASFILETIME 0
+#define HAVE_GETTIMEOFDAY 1
+#define HAVE_GLOB 1
+#define HAVE_GLXGETPROCADDRESS 0
+#define HAVE_GMTIME_R 1
+#define HAVE_INET_ATON 1
+#define HAVE_ISATTY 1
+#define HAVE_KBHIT 0
+#define HAVE_LSTAT 1
+#define HAVE_LZO1X_999_COMPRESS 0
+#define HAVE_MACH_ABSOLUTE_TIME 1
+#define HAVE_MAPVIEWOFFILE 0
+#define HAVE_MKSTEMP 1
+#define HAVE_MMAP 1
+#define HAVE_MPROTECT 1
+#define HAVE_NANOSLEEP 1
+#define HAVE_PEEKNAMEDPIPE 0
+#define HAVE_PTHREAD_CANCEL 1
+#define HAVE_SCHED_GETAFFINITY 0
+#define HAVE_SECITEMIMPORT 0
+#define HAVE_SETCONSOLETEXTATTRIBUTE 0
+#define HAVE_SETCONSOLECTRLHANDLER 0
+#define HAVE_SETMODE 0
+#define HAVE_SETRLIMIT 1
+#define HAVE_SLEEP 0
+#define HAVE_STRERROR_R 1
+#define HAVE_SYSCONF 1
+#define HAVE_SYSCTL 1
+#define HAVE_USLEEP 1
+#define HAVE_UTGETOSTYPEFROMSTRING 0
+#define HAVE_VIRTUALALLOC 0
+#define HAVE_WGLGETPROCADDRESS 0
+#define HAVE_BCRYPT 0
+#define HAVE_VAAPI_DRM 0
+#define HAVE_VAAPI_X11 0
+#define HAVE_VDPAU_X11 0
+#define HAVE_PTHREADS 1
+#define HAVE_OS2THREADS 0
+#define HAVE_W32THREADS 0
+#define HAVE_AS_ARCH_DIRECTIVE 0
+#define HAVE_AS_DN_DIRECTIVE 0
+#define HAVE_AS_FPU_DIRECTIVE 0
+#define HAVE_AS_FUNC 0
+#define HAVE_AS_OBJECT_ARCH 0
+#define HAVE_ASM_MOD_Q 0
+#define HAVE_BLOCKS_EXTENSION 1
+#define HAVE_EBP_AVAILABLE 1
+#define HAVE_EBX_AVAILABLE 1
+#define HAVE_GNU_AS 0
+#define HAVE_GNU_WINDRES 0
+#define HAVE_IBM_ASM 0
+#define HAVE_INLINE_ASM_DIRECT_SYMBOL_REFS 1
+#define HAVE_INLINE_ASM_LABELS 1
+#define HAVE_INLINE_ASM_NONLOCAL_LABELS 1
+#define HAVE_PRAGMA_DEPRECATED 1
+#define HAVE_RSYNC_CONTIMEOUT 0
+#define HAVE_SYMVER_ASM_LABEL 1
+#define HAVE_SYMVER_GNU_ASM 0
+#define HAVE_VFP_ARGS 0
+#define HAVE_XFORM_ASM 0
+#define HAVE_XMM_CLOBBERS 1
+#define HAVE_KCMVIDEOCODECTYPE_HEVC 0
+#define HAVE_SOCKLEN_T 1
+#define HAVE_STRUCT_ADDRINFO 1
+#define HAVE_STRUCT_GROUP_SOURCE_REQ 1
+#define HAVE_STRUCT_IP_MREQ_SOURCE 1
+#define HAVE_STRUCT_IPV6_MREQ 1
+#define HAVE_STRUCT_MSGHDR_MSG_FLAGS 1
+#define HAVE_STRUCT_POLLFD 1
+#define HAVE_STRUCT_RUSAGE_RU_MAXRSS 1
+#define HAVE_STRUCT_SCTP_EVENT_SUBSCRIBE 0
+#define HAVE_STRUCT_SOCKADDR_IN6 1
+#define HAVE_STRUCT_SOCKADDR_SA_LEN 1
+#define HAVE_STRUCT_SOCKADDR_STORAGE 1
+#define HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 0
+#define HAVE_STRUCT_V4L2_FRMIVALENUM_DISCRETE 0
+#define HAVE_MAKEINFO 1
+#define HAVE_MAKEINFO_HTML 0
+#define HAVE_OPENCL_D3D11 0
+#define HAVE_OPENCL_DRM_ARM 0
+#define HAVE_OPENCL_DRM_BEIGNET 0
+#define HAVE_OPENCL_DXVA2 0
+#define HAVE_OPENCL_VAAPI_BEIGNET 0
+#define HAVE_OPENCL_VAAPI_INTEL_MEDIA 0
+#define HAVE_PERL 1
+#define HAVE_POD2MAN 1
+#define HAVE_TEXI2HTML 1
+#define CONFIG_DOC 0
+#define CONFIG_HTMLPAGES 1
+#define CONFIG_MANPAGES 1
+#define CONFIG_PODPAGES 1
+#define CONFIG_TXTPAGES 1
+#define CONFIG_AVIO_DIR_CMD_EXAMPLE 1
+#define CONFIG_AVIO_READING_EXAMPLE 1
+#define CONFIG_DECODE_AUDIO_EXAMPLE 1
+#define CONFIG_DECODE_VIDEO_EXAMPLE 1
+#define CONFIG_DEMUXING_DECODING_EXAMPLE 0
+#define CONFIG_ENCODE_AUDIO_EXAMPLE 1
+#define CONFIG_ENCODE_VIDEO_EXAMPLE 1
+#define CONFIG_EXTRACT_MVS_EXAMPLE 0
+#define CONFIG_FILTER_AUDIO_EXAMPLE 0
+#define CONFIG_FILTERING_AUDIO_EXAMPLE 0
+#define CONFIG_FILTERING_VIDEO_EXAMPLE 0
+#define CONFIG_HTTP_MULTICLIENT_EXAMPLE 0
+#define CONFIG_HW_DECODE_EXAMPLE 0
+#define CONFIG_METADATA_EXAMPLE 0
+#define CONFIG_MUXING_EXAMPLE 0
+#define CONFIG_QSVDEC_EXAMPLE 0
+#define CONFIG_REMUXING_EXAMPLE 0
+#define CONFIG_RESAMPLING_AUDIO_EXAMPLE 0
+#define CONFIG_SCALING_VIDEO_EXAMPLE 0
+#define CONFIG_TRANSCODE_AAC_EXAMPLE 0
+#define CONFIG_TRANSCODING_EXAMPLE 0
+#define CONFIG_VAAPI_ENCODE_EXAMPLE 0
+#define CONFIG_VAAPI_TRANSCODE_EXAMPLE 0
+#define CONFIG_AVISYNTH 0
+#define CONFIG_FREI0R 0
+#define CONFIG_LIBCDIO 0
+#define CONFIG_LIBRUBBERBAND 0
+#define CONFIG_LIBVIDSTAB 0
+#define CONFIG_LIBX264 0
+#define CONFIG_LIBX265 0
+#define CONFIG_LIBXAVS 0
+#define CONFIG_LIBXVID 0
+#define CONFIG_DECKLINK 0
+#define CONFIG_LIBNDI_NEWTEK 0
+#define CONFIG_LIBFDK_AAC 0
+#define CONFIG_OPENSSL 0
+#define CONFIG_LIBTLS 0
+#define CONFIG_GMP 0
+#define CONFIG_LIBOPENCORE_AMRNB 0
+#define CONFIG_LIBOPENCORE_AMRWB 0
+#define CONFIG_LIBVMAF 0
+#define CONFIG_LIBVO_AMRWBENC 0
+#define CONFIG_RKMPP 0
+#define CONFIG_LIBSMBCLIENT 0
+#define CONFIG_CHROMAPRINT 0
+#define CONFIG_GCRYPT 0
+#define CONFIG_GNUTLS 0
+#define CONFIG_JNI 0
+#define CONFIG_LADSPA 0
+#define CONFIG_LIBAOM 0
+#define CONFIG_LIBASS 0
+#define CONFIG_LIBBLURAY 0
+#define CONFIG_LIBBS2B 0
+#define CONFIG_LIBCACA 0
+#define CONFIG_LIBCELT 0
+#define CONFIG_LIBCODEC2 0
+#define CONFIG_LIBDC1394 0
+#define CONFIG_LIBDRM 0
+#define CONFIG_LIBFLITE 0
+#define CONFIG_LIBFONTCONFIG 0
+#define CONFIG_LIBFREETYPE 0
+#define CONFIG_LIBFRIBIDI 0
+#define CONFIG_LIBGME 0
+#define CONFIG_LIBGSM 0
+#define CONFIG_LIBIEC61883 0
+#define CONFIG_LIBILBC 0
+#define CONFIG_LIBJACK 0
+#define CONFIG_LIBKVAZAAR 0
+#define CONFIG_LIBMODPLUG 0
+#define CONFIG_LIBMP3LAME 0
+#define CONFIG_LIBMYSOFA 0
+#define CONFIG_LIBOPENCV 0
+#define CONFIG_LIBOPENH264 0
+#define CONFIG_LIBOPENJPEG 0
+#define CONFIG_LIBOPENMPT 0
+#define CONFIG_LIBOPUS 0
+#define CONFIG_LIBPULSE 0
+#define CONFIG_LIBRSVG 0
+#define CONFIG_LIBRTMP 0
+#define CONFIG_LIBSHINE 0
+#define CONFIG_LIBSMBCLIENT 0
+#define CONFIG_LIBSNAPPY 0
+#define CONFIG_LIBSOXR 0
+#define CONFIG_LIBSPEEX 0
+#define CONFIG_LIBSRT 0
+#define CONFIG_LIBSSH 0
+#define CONFIG_LIBTESSERACT 0
+#define CONFIG_LIBTHEORA 0
+#define CONFIG_LIBTWOLAME 0
+#define CONFIG_LIBV4L2 0
+#define CONFIG_LIBVORBIS 0
+#define CONFIG_LIBVPX 0
+#define CONFIG_LIBWAVPACK 0
+#define CONFIG_LIBWEBP 0
+#define CONFIG_LIBXML2 0
+#define CONFIG_LIBZIMG 0
+#define CONFIG_LIBZMQ 0
+#define CONFIG_LIBZVBI 0
+#define CONFIG_LV2 0
+#define CONFIG_MEDIACODEC 0
+#define CONFIG_OPENAL 0
+#define CONFIG_OPENGL 0
+#define CONFIG_ALSA 0
+#define CONFIG_APPKIT 1
+#define CONFIG_AVFOUNDATION 1
+#define CONFIG_BZLIB 1
+#define CONFIG_COREIMAGE 1
+#define CONFIG_ICONV 0
+#define CONFIG_LIBXCB 0
+#define CONFIG_LIBXCB_SHM 0
+#define CONFIG_LIBXCB_SHAPE 0
+#define CONFIG_LIBXCB_XFIXES 0
+#define CONFIG_LZMA 1
+#define CONFIG_SCHANNEL 0
+#define CONFIG_SDL2 0
+#define CONFIG_SECURETRANSPORT 0
+#define CONFIG_SNDIO 0
+#define CONFIG_XLIB 1
+#define CONFIG_ZLIB 1
+#define CONFIG_CUDA_SDK 0
+#define CONFIG_LIBNPP 0
+#define CONFIG_LIBMFX 0
+#define CONFIG_MMAL 0
+#define CONFIG_OMX 0
+#define CONFIG_OPENCL 0
+#define CONFIG_AMF 0
+#define CONFIG_AUDIOTOOLBOX 1
+#define CONFIG_CRYSTALHD 0
+#define CONFIG_CUDA 0
+#define CONFIG_CUVID 0
+#define CONFIG_D3D11VA 0
+#define CONFIG_DXVA2 0
+#define CONFIG_FFNVCODEC 0
+#define CONFIG_NVDEC 0
+#define CONFIG_NVENC 0
+#define CONFIG_VAAPI 0
+#define CONFIG_VDPAU 0
+#define CONFIG_VIDEOTOOLBOX 0
+#define CONFIG_V4L2_M2M 0
+#define CONFIG_XVMC 0
+#define CONFIG_FTRAPV 0
+#define CONFIG_GRAY 0
+#define CONFIG_HARDCODED_TABLES 0
+#define CONFIG_OMX_RPI 0
+#define CONFIG_RUNTIME_CPUDETECT 1
+#define CONFIG_SAFE_BITSTREAM_READER 1
+#define CONFIG_SHARED 1
+#define CONFIG_SMALL 0
+#define CONFIG_STATIC 0
+#define CONFIG_SWSCALE_ALPHA 1
+#define CONFIG_GPL 0
+#define CONFIG_NONFREE 0
+#define CONFIG_VERSION3 0
+#define CONFIG_AVCODEC 1
+#define CONFIG_AVDEVICE 0
+#define CONFIG_AVFILTER 0
+#define CONFIG_AVFORMAT 0
+#define CONFIG_AVRESAMPLE 0
+#define CONFIG_AVUTIL 1
+#define CONFIG_POSTPROC 0
+#define CONFIG_SWRESAMPLE 0
+#define CONFIG_SWSCALE 0
+#define CONFIG_FFPLAY 0
+#define CONFIG_FFPROBE 0
+#define CONFIG_FFMPEG 0
+#define CONFIG_DCT 0
+#define CONFIG_DWT 0
+#define CONFIG_ERROR_RESILIENCE 0
+#define CONFIG_FAAN 1
+#define CONFIG_FAST_UNALIGNED 1
+#define CONFIG_FFT 0
+#define CONFIG_LSP 0
+#define CONFIG_LZO 0
+#define CONFIG_MDCT 0
+#define CONFIG_PIXELUTILS 0
+#define CONFIG_NETWORK 0
+#define CONFIG_RDFT 0
+#define CONFIG_AUTODETECT 0
+#define CONFIG_FONTCONFIG 0
+#define CONFIG_LINUX_PERF 0
+#define CONFIG_MEMORY_POISONING 0
+#define CONFIG_NEON_CLOBBER_TEST 0
+#define CONFIG_OSSFUZZ 0
+#define CONFIG_PIC 1
+#define CONFIG_THUMB 0
+#define CONFIG_VALGRIND_BACKTRACE 0
+#define CONFIG_XMM_CLOBBER_TEST 0
+#define CONFIG_BSFS 1
+#define CONFIG_DECODERS 1
+#define CONFIG_PARSERS 1
+#define CONFIG_AANDCTTABLES 0
+#define CONFIG_AC3DSP 0
+#define CONFIG_ADTS_HEADER 0
+#define CONFIG_AUDIO_FRAME_QUEUE 0
+#define CONFIG_AUDIODSP 0
+#define CONFIG_BLOCKDSP 0
+#define CONFIG_BSWAPDSP 0
+#define CONFIG_CABAC 0
+#define CONFIG_CBS 0
+#define CONFIG_CBS_H264 0
+#define CONFIG_CBS_H265 0
+#define CONFIG_CBS_MPEG2 0
+#define CONFIG_DIRAC_PARSE 0
+#define CONFIG_DVPROFILE 0
+#define CONFIG_EXIF 0
+#define CONFIG_FAANDCT 1
+#define CONFIG_FAANIDCT 1
+#define CONFIG_FDCTDSP 1
+#define CONFIG_FLACDSP 1
+#define CONFIG_FMTCONVERT 0
+#define CONFIG_G722DSP 0
+#define CONFIG_GOLOMB 0
+#define CONFIG_GPLV3 0
+#define CONFIG_H263DSP 0
+#define CONFIG_H264CHROMA 0
+#define CONFIG_H264DSP 0
+#define CONFIG_H264PARSE 0
+#define CONFIG_H264PRED 1
+#define CONFIG_H264QPEL 0
+#define CONFIG_HEVCPARSE 0
+#define CONFIG_HPELDSP 0
+#define CONFIG_HUFFMAN 0
+#define CONFIG_HUFFYUVDSP 0
+#define CONFIG_HUFFYUVENCDSP 0
+#define CONFIG_IDCTDSP 1
+#define CONFIG_IIRFILTER 0
+#define CONFIG_MDCT15 0
+#define CONFIG_INTRAX8 0
+#define CONFIG_ISO_MEDIA 0
+#define CONFIG_IVIDSP 0
+#define CONFIG_JPEGTABLES 0
+#define CONFIG_LGPLV3 0
+#define CONFIG_LIBX262 0
+#define CONFIG_LLAUDDSP 0
+#define CONFIG_LLVIDDSP 0
+#define CONFIG_LLVIDENCDSP 0
+#define CONFIG_LPC 0
+#define CONFIG_LZF 0
+#define CONFIG_ME_CMP 0
+#define CONFIG_MPEG_ER 0
+#define CONFIG_MPEGAUDIO 0
+#define CONFIG_MPEGAUDIODSP 0
+#define CONFIG_MPEGAUDIOHEADER 0
+#define CONFIG_MPEGVIDEO 0
+#define CONFIG_MPEGVIDEOENC 0
+#define CONFIG_MSS34DSP 0
+#define CONFIG_PIXBLOCKDSP 0
+#define CONFIG_QPELDSP 0
+#define CONFIG_QSV 0
+#define CONFIG_QSVDEC 0
+#define CONFIG_QSVENC 0
+#define CONFIG_QSVVPP 0
+#define CONFIG_RANGECODER 0
+#define CONFIG_RIFFDEC 0
+#define CONFIG_RIFFENC 0
+#define CONFIG_RTPDEC 0
+#define CONFIG_RTPENC_CHAIN 0
+#define CONFIG_RV34DSP 0
+#define CONFIG_SINEWIN 0
+#define CONFIG_SNAPPY 0
+#define CONFIG_SRTP 0
+#define CONFIG_STARTCODE 0
+#define CONFIG_TEXTUREDSP 0
+#define CONFIG_TEXTUREDSPENC 0
+#define CONFIG_TPELDSP 0
+#define CONFIG_VAAPI_1 0
+#define CONFIG_VAAPI_ENCODE 0
+#define CONFIG_VC1DSP 0
+#define CONFIG_VIDEODSP 1
+#define CONFIG_VP3DSP 0
+#define CONFIG_VP56DSP 0
+#define CONFIG_VP8DSP 1
+#define CONFIG_WMA_FREQS 0
+#define CONFIG_WMV2DSP 0
+#define CONFIG_NULL_BSF 1
+#define CONFIG_VP9_SUPERFRAME_SPLIT_BSF 1
+#define CONFIG_VP8_DECODER 1
+#define CONFIG_VP9_DECODER 1
+#define CONFIG_FLAC_DECODER 1
+#define CONFIG_VP8_PARSER 1
+#define CONFIG_VP9_PARSER 1
+#endif /* FFMPEG_CONFIG_H */
diff --git a/media/libav/config.h b/media/libav/config.h
index 4abd411041..96c5ea9506 100644
--- a/media/libav/config.h
+++ b/media/libav/config.h
@@ -17,6 +17,8 @@
#define MOZ_LIBAV_CONFIG_H
#if defined(XP_WIN)
#include "config_win.h"
+#elif defined(XP_DARWIN)
+#include "config_darwin.h"
#elif defined(XP_UNIX)
#include "config_unix.h"
#endif
diff --git a/media/libav/config_darwin.asm b/media/libav/config_darwin.asm
new file mode 100644
index 0000000000..89413c594e
--- /dev/null
+++ b/media/libav/config_darwin.asm
@@ -0,0 +1,249 @@
+%include "config_common.asm"
+%define HAVE_ARMV5TE 0
+%define HAVE_ARMV6 0
+%define HAVE_ARMV6T2 0
+%define HAVE_ARMV8 0
+%define HAVE_NEON 0
+%define HAVE_VFP 0
+%define HAVE_VFPV3 0
+%define HAVE_ALTIVEC 0
+%define HAVE_DCBZL 1
+%define HAVE_LDBRX 1
+%define HAVE_PPC4XX 0
+%define HAVE_AMD3DNOW 1
+%define HAVE_AMD3DNOWEXT 1
+%define HAVE_AVX 1
+%define HAVE_AVX2 1
+%define HAVE_FMA3 1
+%define HAVE_FMA4 1
+%define HAVE_MMX 1
+%define HAVE_MMXEXT 1
+%define HAVE_SSE 1
+%define HAVE_SSE2 1
+%define HAVE_SSE3 1
+%define HAVE_SSE4 1
+%define HAVE_SSE42 1
+%define HAVE_SSSE3 1
+%define HAVE_XOP 1
+%define HAVE_CPUNOP 1
+%define HAVE_I686 1
+%define HAVE_LOONGSON 1
+%define HAVE_VIS 1
+%define HAVE_ARMV5TE_EXTERNAL 0
+%define HAVE_ARMV6_EXTERNAL 0
+%define HAVE_ARMV6T2_EXTERNAL 0
+%define HAVE_ARMV8_EXTERNAL 0
+%define HAVE_NEON_EXTERNAL 0
+%define HAVE_VFP_EXTERNAL 0
+%define HAVE_VFPV3_EXTERNAL 0
+%define HAVE_ALTIVEC_EXTERNAL 0
+%define HAVE_DCBZL_EXTERNAL 0
+%define HAVE_LDBRX_EXTERNAL 0
+%define HAVE_PPC4XX_EXTERNAL 0
+%define HAVE_AMD3DNOW_EXTERNAL 1
+%define HAVE_AMD3DNOWEXT_EXTERNAL 1
+%define HAVE_AVX_EXTERNAL 1
+%define HAVE_AVX2_EXTERNAL 1
+%define HAVE_FMA3_EXTERNAL 1
+%define HAVE_FMA4_EXTERNAL 1
+%define HAVE_MMX_EXTERNAL 1
+%define HAVE_MMXEXT_EXTERNAL 1
+%define HAVE_SSE_EXTERNAL 1
+%define HAVE_SSE2_EXTERNAL 1
+%define HAVE_SSE3_EXTERNAL 1
+%define HAVE_SSE4_EXTERNAL 1
+%define HAVE_SSE42_EXTERNAL 1
+%define HAVE_SSSE3_EXTERNAL 1
+%define HAVE_XOP_EXTERNAL 1
+%define HAVE_CPUNOP_EXTERNAL 0
+%define HAVE_I686_EXTERNAL 0
+%define HAVE_LOONGSON_EXTERNAL 0
+%define HAVE_VIS_EXTERNAL 0
+%define HAVE_ARMV5TE_INLINE 0
+%define HAVE_ARMV6_INLINE 0
+%define HAVE_ARMV6T2_INLINE 0
+%define HAVE_ARMV8_INLINE 0
+%define HAVE_NEON_INLINE 0
+%define HAVE_VFP_INLINE 0
+%define HAVE_VFPV3_INLINE 0
+%define HAVE_ALTIVEC_INLINE 0
+%define HAVE_DCBZL_INLINE 0
+%define HAVE_LDBRX_INLINE 0
+%define HAVE_PPC4XX_INLINE 0
+%define HAVE_AMD3DNOW_INLINE 1
+%define HAVE_AMD3DNOWEXT_INLINE 1
+%define HAVE_AVX_INLINE 1
+%define HAVE_AVX2_INLINE 1
+%define HAVE_FMA3_INLINE 1
+%define HAVE_FMA4_INLINE 1
+%define HAVE_MMX_INLINE 1
+%define HAVE_MMXEXT_INLINE 1
+%define HAVE_SSE_INLINE 1
+%define HAVE_SSE2_INLINE 1
+%define HAVE_SSE3_INLINE 1
+%define HAVE_SSE4_INLINE 1
+%define HAVE_SSE42_INLINE 1
+%define HAVE_SSSE3_INLINE 1
+%define HAVE_XOP_INLINE 1
+%define HAVE_CPUNOP_INLINE 0
+%define HAVE_I686_INLINE 0
+%define HAVE_LOONGSON_INLINE 0
+%define HAVE_VIS_INLINE 0
+%define HAVE_ALIGNED_STACK 1
+%define HAVE_FAST_64BIT 0
+%define HAVE_FAST_CLZ 0
+%define HAVE_FAST_CMOV 0
+%define HAVE_LOCAL_ALIGNED_8 1
+%define HAVE_LOCAL_ALIGNED_16 1
+%define HAVE_SIMD_ALIGN_16 1
+%define HAVE_ATOMICS_GCC 1
+%define HAVE_ATOMICS_SUNCC 0
+%define HAVE_ATOMICS_WIN32 0
+%define HAVE_ATOMIC_CAS_PTR 0
+%define HAVE_MACHINE_RW_BARRIER 0
+%define HAVE_MEMORYBARRIER 0
+%define HAVE_MM_EMPTY 1
+%define HAVE_RDTSC 0
+%define HAVE_SYNC_VAL_COMPARE_AND_SWAP 1
+%define HAVE_INLINE_ASM 1
+%define HAVE_SYMVER 0
+%define HAVE_YASM 1
+%define HAVE_BIGENDIAN 0
+%define HAVE_FAST_UNALIGNED 1
+%define HAVE_ALSA_ASOUNDLIB_H 0
+%define HAVE_ALTIVEC_H 0
+%define HAVE_ARPA_INET_H 0
+%define HAVE_CDIO_PARANOIA_H 0
+%define HAVE_CDIO_PARANOIA_PARANOIA_H 0
+%define HAVE_DEV_BKTR_IOCTL_BT848_H 0
+%define HAVE_DEV_BKTR_IOCTL_METEOR_H 0
+%define HAVE_DEV_IC_BT8XX_H 0
+%define HAVE_DEV_VIDEO_BKTR_IOCTL_BT848_H 0
+%define HAVE_DEV_VIDEO_METEOR_IOCTL_METEOR_H 0
+%define HAVE_DIRECT_H 0
+%define HAVE_DLFCN_H 1
+%define HAVE_DXVA_H 0
+%define HAVE_GSM_H 0
+%define HAVE_IO_H 0
+%define HAVE_MACH_MACH_TIME_H 1
+%define HAVE_MACHINE_IOCTL_BT848_H 0
+%define HAVE_MACHINE_IOCTL_METEOR_H 0
+%define HAVE_POLL_H 1
+%define HAVE_SNDIO_H 0
+%define HAVE_SOUNDCARD_H 0
+%define HAVE_SYS_MMAN_H 1
+%define HAVE_SYS_PARAM_H 1
+%define HAVE_SYS_RESOURCE_H 1
+%define HAVE_SYS_SELECT_H 1
+%define HAVE_SYS_SOUNDCARD_H 0
+%define HAVE_SYS_TIME_H 1
+%define HAVE_SYS_UN_H 1
+%define HAVE_SYS_VIDEOIO_H 0
+%define HAVE_UNISTD_H 1
+%define HAVE_WINDOWS_H 0
+%define HAVE_WINSOCK2_H 0
+%define HAVE_INTRINSICS_NEON 0
+%define HAVE_ATANF 1
+%define HAVE_ATAN2F 1
+%define HAVE_CBRTF 1
+%define HAVE_COSF 1
+%define HAVE_EXP2 1
+%define HAVE_EXP2F 1
+%define HAVE_EXPF 1
+%define HAVE_ISINF 1
+%define HAVE_ISNAN 1
+%define HAVE_LDEXPF 1
+%define HAVE_LLRINT 1
+%define HAVE_LLRINTF 1
+%define HAVE_LOG2 1
+%define HAVE_LOG2F 1
+%define HAVE_LOG10F 1
+%define HAVE_LRINT 1
+%define HAVE_LRINTF 1
+%define HAVE_POWF 1
+%define HAVE_RINT 1
+%define HAVE_ROUND 1
+%define HAVE_ROUNDF 1
+%define HAVE_SINF 1
+%define HAVE_TRUNC 1
+%define HAVE_TRUNCF 1
+%define HAVE_ALIGNED_MALLOC 0
+%define HAVE_CLOSESOCKET 0
+%define HAVE_COMMANDLINETOARGVW 0
+%define HAVE_COTASKMEMFREE 0
+%define HAVE_CRYPTGENRANDOM 0
+%define HAVE_DLOPEN 1
+%define HAVE_FCNTL 1
+%define HAVE_FLT_LIM 1
+%define HAVE_FORK 1
+%define HAVE_GETADDRINFO 0
+%define HAVE_GETHRTIME 0
+%define HAVE_GETOPT 1
+%define HAVE_GETPROCESSAFFINITYMASK 0
+%define HAVE_GETPROCESSMEMORYINFO 0
+%define HAVE_GETPROCESSTIMES 0
+%define HAVE_GETRUSAGE 1
+%define HAVE_GETSERVBYPORT 0
+%define HAVE_GETSYSTEMTIMEASFILETIME 0
+%define HAVE_GETTIMEOFDAY 1
+%define HAVE_INET_ATON 0
+%define HAVE_ISATTY 1
+%define HAVE_JACK_PORT_GET_LATENCY_RANGE 0
+%define HAVE_MACH_ABSOLUTE_TIME 1
+%define HAVE_MAPVIEWOFFILE 0
+%define HAVE_MKSTEMP 1
+%define HAVE_MMAP 1
+%define HAVE_MPROTECT 1
+%define HAVE_NANOSLEEP 1
+%define HAVE_SCHED_GETAFFINITY 0
+%define HAVE_SETCONSOLETEXTATTRIBUTE 0
+%define HAVE_SETMODE 0
+%define HAVE_SETRLIMIT 1
+%define HAVE_SLEEP 0
+%define HAVE_STRERROR_R 1
+%define HAVE_STRPTIME 1
+%define HAVE_SYSCONF 1
+%define HAVE_SYSCTL 1
+%define HAVE_USLEEP 1
+%define HAVE_VIRTUALALLOC 0
+%define HAVE_PTHREADS 0
+%define HAVE_W32THREADS 0
+%define HAVE_AS_DN_DIRECTIVE 0
+%define HAVE_AS_FUNC 0
+%define HAVE_AS_OBJECT_ARCH 0
+%define HAVE_ASM_MOD_Q 0
+%define HAVE_ATTRIBUTE_MAY_ALIAS 1
+%define HAVE_ATTRIBUTE_PACKED 1
+%define HAVE_EBP_AVAILABLE 1
+%define HAVE_EBX_AVAILABLE 1
+%define HAVE_GNU_AS 0
+%define HAVE_IBM_ASM 0
+%define HAVE_INLINE_ASM_LABELS 1
+%define HAVE_PRAGMA_DEPRECATED 1
+%define HAVE_SYMVER_ASM_LABEL 0
+%define HAVE_SYMVER_GNU_ASM 0
+%define HAVE_VFP_ARGS 0
+%define HAVE_XFORM_ASM 0
+%define HAVE_XMM_CLOBBERS 1
+%define HAVE_SOCKLEN_T 0
+%define HAVE_STRUCT_ADDRINFO 0
+%define HAVE_STRUCT_GROUP_SOURCE_REQ 0
+%define HAVE_STRUCT_IP_MREQ_SOURCE 0
+%define HAVE_STRUCT_IPV6_MREQ 0
+%define HAVE_STRUCT_POLLFD 0
+%define HAVE_STRUCT_RUSAGE_RU_MAXRSS 1
+%define HAVE_STRUCT_SOCKADDR_IN6 0
+%define HAVE_STRUCT_SOCKADDR_SA_LEN 0
+%define HAVE_STRUCT_SOCKADDR_STORAGE 0
+%define HAVE_STRUCT_V4L2_FRMIVALENUM_DISCRETE 0
+%define HAVE_ATOMICS_NATIVE 1
+%define HAVE_DOS_PATHS 0
+%define HAVE_DXVA2_LIB 0
+%define HAVE_LIBC_MSVCRT 0
+%define HAVE_LIBDC1394_1 0
+%define HAVE_LIBDC1394_2 0
+%define HAVE_SDL 0
+%define HAVE_THREADS 0
+%define HAVE_VDPAU_X11 0
+%define HAVE_XLIB 0
+
diff --git a/media/libav/config_darwin.h b/media/libav/config_darwin.h
new file mode 100644
index 0000000000..ae7844f3aa
--- /dev/null
+++ b/media/libav/config_darwin.h
@@ -0,0 +1,259 @@
+/* Automatically generated by configure - do not modify! */
+#ifndef LIBAV_CONFIG_H
+#define LIBAV_CONFIG_H
+#define LIBAV_CONFIGURATION "--disable-programs --disable-everything"
+#define LIBAV_LICENSE "LGPL version 2.1 or later"
+#define AVCONV_DATADIR "/usr/local/share/avconv"
+#define CC_IDENT "Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)"
+#define restrict restrict
+#define EXTERN_PREFIX ""
+#define EXTERN_ASM
+#define SLIBSUF ".dylib"
+#define HAVE_ARMV5TE 0
+#define HAVE_ARMV6 0
+#define HAVE_ARMV6T2 0
+#define HAVE_ARMV8 0
+#define HAVE_NEON 0
+#define HAVE_VFP 0
+#define HAVE_VFPV3 0
+#define HAVE_ALTIVEC 0
+#define HAVE_DCBZL 1
+#define HAVE_LDBRX 1
+#define HAVE_PPC4XX 0
+#define HAVE_AMD3DNOW 1
+#define HAVE_AMD3DNOWEXT 1
+#define HAVE_AVX 1
+#define HAVE_AVX2 1
+#define HAVE_FMA3 1
+#define HAVE_FMA4 1
+#define HAVE_MMX 1
+#define HAVE_MMXEXT 1
+#define HAVE_SSE 1
+#define HAVE_SSE2 1
+#define HAVE_SSE3 1
+#define HAVE_SSE4 1
+#define HAVE_SSE42 1
+#define HAVE_SSSE3 1
+#define HAVE_XOP 1
+#define HAVE_CPUNOP 1
+#define HAVE_I686 1
+#define HAVE_LOONGSON 1
+#define HAVE_VIS 1
+#define HAVE_ARMV5TE_EXTERNAL 0
+#define HAVE_ARMV6_EXTERNAL 0
+#define HAVE_ARMV6T2_EXTERNAL 0
+#define HAVE_ARMV8_EXTERNAL 0
+#define HAVE_NEON_EXTERNAL 0
+#define HAVE_VFP_EXTERNAL 0
+#define HAVE_VFPV3_EXTERNAL 0
+#define HAVE_ALTIVEC_EXTERNAL 0
+#define HAVE_DCBZL_EXTERNAL 0
+#define HAVE_LDBRX_EXTERNAL 0
+#define HAVE_PPC4XX_EXTERNAL 0
+#define HAVE_AMD3DNOW_EXTERNAL 1
+#define HAVE_AMD3DNOWEXT_EXTERNAL 1
+#define HAVE_AVX_EXTERNAL 1
+#define HAVE_AVX2_EXTERNAL 1
+#define HAVE_FMA3_EXTERNAL 1
+#define HAVE_FMA4_EXTERNAL 1
+#define HAVE_MMX_EXTERNAL 1
+#define HAVE_MMXEXT_EXTERNAL 1
+#define HAVE_SSE_EXTERNAL 1
+#define HAVE_SSE2_EXTERNAL 1
+#define HAVE_SSE3_EXTERNAL 1
+#define HAVE_SSE4_EXTERNAL 1
+#define HAVE_SSE42_EXTERNAL 1
+#define HAVE_SSSE3_EXTERNAL 1
+#define HAVE_XOP_EXTERNAL 1
+#define HAVE_CPUNOP_EXTERNAL 0
+#define HAVE_I686_EXTERNAL 0
+#define HAVE_LOONGSON_EXTERNAL 0
+#define HAVE_VIS_EXTERNAL 0
+#define HAVE_ARMV5TE_INLINE 0
+#define HAVE_ARMV6_INLINE 0
+#define HAVE_ARMV6T2_INLINE 0
+#define HAVE_ARMV8_INLINE 0
+#define HAVE_NEON_INLINE 0
+#define HAVE_VFP_INLINE 0
+#define HAVE_VFPV3_INLINE 0
+#define HAVE_ALTIVEC_INLINE 0
+#define HAVE_DCBZL_INLINE 0
+#define HAVE_LDBRX_INLINE 0
+#define HAVE_PPC4XX_INLINE 0
+#define HAVE_AMD3DNOW_INLINE 1
+#define HAVE_AMD3DNOWEXT_INLINE 1
+#define HAVE_AVX_INLINE 1
+#define HAVE_AVX2_INLINE 1
+#define HAVE_FMA3_INLINE 1
+#define HAVE_FMA4_INLINE 1
+#define HAVE_MMX_INLINE 1
+#define HAVE_MMXEXT_INLINE 1
+#define HAVE_SSE_INLINE 1
+#define HAVE_SSE2_INLINE 1
+#define HAVE_SSE3_INLINE 1
+#define HAVE_SSE4_INLINE 1
+#define HAVE_SSE42_INLINE 1
+#define HAVE_SSSE3_INLINE 1
+#define HAVE_XOP_INLINE 1
+#define HAVE_CPUNOP_INLINE 0
+#define HAVE_I686_INLINE 0
+#define HAVE_LOONGSON_INLINE 0
+#define HAVE_VIS_INLINE 0
+#define HAVE_ALIGNED_STACK 1
+#define HAVE_FAST_64BIT 0
+#define HAVE_FAST_CLZ 0
+#define HAVE_FAST_CMOV 0
+#define HAVE_LOCAL_ALIGNED_8 1
+#define HAVE_LOCAL_ALIGNED_16 1
+#define HAVE_SIMD_ALIGN_16 1
+#define HAVE_ATOMICS_GCC 1
+#define HAVE_ATOMICS_SUNCC 0
+#define HAVE_ATOMICS_WIN32 0
+#define HAVE_ATOMIC_CAS_PTR 0
+#define HAVE_MACHINE_RW_BARRIER 0
+#define HAVE_MEMORYBARRIER 0
+#define HAVE_MM_EMPTY 1
+#define HAVE_RDTSC 0
+#define HAVE_SYNC_VAL_COMPARE_AND_SWAP 1
+#define HAVE_INLINE_ASM 1
+#define HAVE_SYMVER 0
+#define HAVE_YASM 1
+#define HAVE_BIGENDIAN 0
+#define HAVE_FAST_UNALIGNED 1
+#define HAVE_ALSA_ASOUNDLIB_H 0
+#define HAVE_ALTIVEC_H 0
+#define HAVE_ARPA_INET_H 0
+#define HAVE_CDIO_PARANOIA_H 0
+#define HAVE_CDIO_PARANOIA_PARANOIA_H 0
+#define HAVE_DEV_BKTR_IOCTL_BT848_H 0
+#define HAVE_DEV_BKTR_IOCTL_METEOR_H 0
+#define HAVE_DEV_IC_BT8XX_H 0
+#define HAVE_DEV_VIDEO_BKTR_IOCTL_BT848_H 0
+#define HAVE_DEV_VIDEO_METEOR_IOCTL_METEOR_H 0
+#define HAVE_DIRECT_H 0
+#define HAVE_DLFCN_H 1
+#define HAVE_DXVA_H 0
+#define HAVE_GSM_H 0
+#define HAVE_IO_H 0
+#define HAVE_MACH_MACH_TIME_H 1
+#define HAVE_MACHINE_IOCTL_BT848_H 0
+#define HAVE_MACHINE_IOCTL_METEOR_H 0
+#define HAVE_POLL_H 1
+#define HAVE_SNDIO_H 0
+#define HAVE_SOUNDCARD_H 0
+#define HAVE_SYS_MMAN_H 1
+#define HAVE_SYS_PARAM_H 1
+#define HAVE_SYS_RESOURCE_H 1
+#define HAVE_SYS_SELECT_H 1
+#define HAVE_SYS_SOUNDCARD_H 0
+#define HAVE_SYS_TIME_H 1
+#define HAVE_SYS_UN_H 1
+#define HAVE_SYS_VIDEOIO_H 0
+#define HAVE_UNISTD_H 1
+#define HAVE_WINDOWS_H 0
+#define HAVE_WINSOCK2_H 0
+#define HAVE_INTRINSICS_NEON 0
+#define HAVE_ATANF 1
+#define HAVE_ATAN2F 1
+#define HAVE_CBRTF 1
+#define HAVE_COSF 1
+#define HAVE_EXP2 1
+#define HAVE_EXP2F 1
+#define HAVE_EXPF 1
+#define HAVE_ISINF 1
+#define HAVE_ISNAN 1
+#define HAVE_LDEXPF 1
+#define HAVE_LLRINT 1
+#define HAVE_LLRINTF 1
+#define HAVE_LOG2 1
+#define HAVE_LOG2F 1
+#define HAVE_LOG10F 1
+#define HAVE_LRINT 1
+#define HAVE_LRINTF 1
+#define HAVE_POWF 1
+#define HAVE_RINT 1
+#define HAVE_ROUND 1
+#define HAVE_ROUNDF 1
+#define HAVE_SINF 1
+#define HAVE_TRUNC 1
+#define HAVE_TRUNCF 1
+#define HAVE_ALIGNED_MALLOC 0
+#define HAVE_CLOSESOCKET 0
+#define HAVE_COMMANDLINETOARGVW 0
+#define HAVE_COTASKMEMFREE 0
+#define HAVE_CRYPTGENRANDOM 0
+#define HAVE_DLOPEN 1
+#define HAVE_FCNTL 1
+#define HAVE_FLT_LIM 1
+#define HAVE_FORK 1
+#define HAVE_GETADDRINFO 0
+#define HAVE_GETHRTIME 0
+#define HAVE_GETOPT 1
+#define HAVE_GETPROCESSAFFINITYMASK 0
+#define HAVE_GETPROCESSMEMORYINFO 0
+#define HAVE_GETPROCESSTIMES 0
+#define HAVE_GETRUSAGE 1
+#define HAVE_GETSERVBYPORT 0
+#define HAVE_GETSYSTEMTIMEASFILETIME 0
+#define HAVE_GETTIMEOFDAY 1
+#define HAVE_INET_ATON 0
+#define HAVE_ISATTY 1
+#define HAVE_JACK_PORT_GET_LATENCY_RANGE 0
+#define HAVE_MACH_ABSOLUTE_TIME 1
+#define HAVE_MAPVIEWOFFILE 0
+#define HAVE_MKSTEMP 1
+#define HAVE_MMAP 1
+#define HAVE_MPROTECT 1
+#define HAVE_NANOSLEEP 1
+#define HAVE_SCHED_GETAFFINITY 0
+#define HAVE_SETCONSOLETEXTATTRIBUTE 0
+#define HAVE_SETMODE 0
+#define HAVE_SETRLIMIT 1
+#define HAVE_SLEEP 0
+#define HAVE_STRERROR_R 1
+#define HAVE_STRPTIME 1
+#define HAVE_SYSCONF 1
+#define HAVE_SYSCTL 1
+#define HAVE_USLEEP 1
+#define HAVE_VIRTUALALLOC 0
+#define HAVE_PTHREADS 0
+#define HAVE_W32THREADS 0
+#define HAVE_AS_DN_DIRECTIVE 0
+#define HAVE_AS_FUNC 0
+#define HAVE_AS_OBJECT_ARCH 0
+#define HAVE_ASM_MOD_Q 0
+#define HAVE_ATTRIBUTE_MAY_ALIAS 1
+#define HAVE_ATTRIBUTE_PACKED 1
+#define HAVE_EBP_AVAILABLE 1
+#define HAVE_EBX_AVAILABLE 1
+#define HAVE_GNU_AS 0
+#define HAVE_IBM_ASM 0
+#define HAVE_INLINE_ASM_LABELS 1
+#define HAVE_PRAGMA_DEPRECATED 1
+#define HAVE_SYMVER_ASM_LABEL 0
+#define HAVE_SYMVER_GNU_ASM 0
+#define HAVE_VFP_ARGS 0
+#define HAVE_XFORM_ASM 0
+#define HAVE_XMM_CLOBBERS 1
+#define HAVE_SOCKLEN_T 0
+#define HAVE_STRUCT_ADDRINFO 0
+#define HAVE_STRUCT_GROUP_SOURCE_REQ 0
+#define HAVE_STRUCT_IP_MREQ_SOURCE 0
+#define HAVE_STRUCT_IPV6_MREQ 0
+#define HAVE_STRUCT_POLLFD 0
+#define HAVE_STRUCT_RUSAGE_RU_MAXRSS 1
+#define HAVE_STRUCT_SOCKADDR_IN6 0
+#define HAVE_STRUCT_SOCKADDR_SA_LEN 0
+#define HAVE_STRUCT_SOCKADDR_STORAGE 0
+#define HAVE_STRUCT_V4L2_FRMIVALENUM_DISCRETE 0
+#define HAVE_ATOMICS_NATIVE 1
+#define HAVE_DOS_PATHS 0
+#define HAVE_DXVA2_LIB 0
+#define HAVE_LIBC_MSVCRT 0
+#define HAVE_LIBDC1394_1 0
+#define HAVE_LIBDC1394_2 0
+#define HAVE_SDL 0
+#define HAVE_THREADS 0
+#define HAVE_VDPAU_X11 0
+#define HAVE_XLIB 0
+#endif /* LIBAV_CONFIG_H */
diff --git a/media/libcubeb/src/cubeb_osx_run_loop.c b/media/libcubeb/src/cubeb_osx_run_loop.c
new file mode 100644
index 0000000000..0ba9536560
--- /dev/null
+++ b/media/libcubeb/src/cubeb_osx_run_loop.c
@@ -0,0 +1,11 @@
+/* -*- Mode: C++; tab-width: 2; 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 "OSXRunLoopSingleton.h"
+
+void cubeb_set_coreaudio_notification_runloop()
+{
+ mozilla_set_coreaudio_notification_runloop_if_needed();
+}
diff --git a/media/libcubeb/src/cubeb_osx_run_loop.h b/media/libcubeb/src/cubeb_osx_run_loop.h
new file mode 100644
index 0000000000..78cd68d09b
--- /dev/null
+++ b/media/libcubeb/src/cubeb_osx_run_loop.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright © 2014 Mozilla Foundation
+ *
+ * This program is made available under an ISC-style license. See the
+ * accompanying file LICENSE for details.
+ */
+
+/* On OSX 10.6 and after, the notification callbacks from the audio hardware are
+ * called on the main thread. Setting the kAudioHardwarePropertyRunLoop property
+ * to null tells the OSX to use a separate thread for that.
+ *
+ * This has to be called only once per process, so it is in a separate header
+ * for easy integration in other code bases. */
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void cubeb_set_coreaudio_notification_runloop();
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/media/libcubeb/src/moz.build b/media/libcubeb/src/moz.build
index c21cc873db..65aaf7256a 100644
--- a/media/libcubeb/src/moz.build
+++ b/media/libcubeb/src/moz.build
@@ -55,6 +55,10 @@ if CONFIG['OS_TARGET'] == 'Darwin':
'cubeb_audiounit.cpp',
'cubeb_resampler.cpp'
]
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'cubeb_osx_run_loop.c',
+ ]
DEFINES['USE_AUDIOUNIT'] = True
if CONFIG['OS_TARGET'] == 'WINNT':
diff --git a/memory/build/mozmemory.h b/memory/build/mozmemory.h
index 2ed63b9e1f..84007fffb2 100644
--- a/memory/build/mozmemory.h
+++ b/memory/build/mozmemory.h
@@ -25,6 +25,13 @@
MOZ_BEGIN_EXTERN_C
+/*
+ * On OSX, malloc/malloc.h contains the declaration for malloc_good_size,
+ * which will call back in jemalloc, through the zone allocator so just use it.
+ */
+#ifdef XP_DARWIN
+# include <malloc/malloc.h>
+#else
MOZ_MEMORY_API size_t malloc_good_size_impl(size_t size);
/* Note: the MOZ_GLUE_IN_PROGRAM ifdef below is there to avoid -Werror turning
@@ -32,15 +39,15 @@ MOZ_MEMORY_API size_t malloc_good_size_impl(size_t size);
* to use weak imports. */
static inline size_t _malloc_good_size(size_t size) {
-#if defined(MOZ_GLUE_IN_PROGRAM) && !defined(IMPL_MFBT)
+# if defined(MOZ_GLUE_IN_PROGRAM) && !defined(IMPL_MFBT)
if (!malloc_good_size)
return size;
-#endif
+# endif
return malloc_good_size_impl(size);
}
-#define malloc_good_size _malloc_good_size
-
+# define malloc_good_size _malloc_good_size
+#endif
MOZ_JEMALLOC_API void jemalloc_stats(jemalloc_stats_t *stats);
diff --git a/memory/build/mozmemory_wrap.c b/memory/build/mozmemory_wrap.c
index 409b39da23..fdb8447d33 100644
--- a/memory/build/mozmemory_wrap.c
+++ b/memory/build/mozmemory_wrap.c
@@ -68,6 +68,7 @@ mozmem_malloc_impl(_ZdaPvRKSt9nothrow_t)(void *ptr)
#undef strndup
#undef strdup
+#ifndef XP_DARWIN
MOZ_MEMORY_API char *
strndup_impl(const char *src, size_t len)
{
@@ -85,6 +86,7 @@ strdup_impl(const char *src)
size_t len = strlen(src);
return strndup_impl(src, len);
}
+#endif /* XP_DARWIN */
#ifdef XP_WIN
/*
diff --git a/memory/build/mozmemory_wrap.h b/memory/build/mozmemory_wrap.h
index aa305588d4..da4fd27bb1 100644
--- a/memory/build/mozmemory_wrap.h
+++ b/memory/build/mozmemory_wrap.h
@@ -112,7 +112,7 @@
# define mozmem_jemalloc_impl(a) je_ ## a
# else
# define MOZ_JEMALLOC_API MFBT_API
-# if defined(XP_WIN)
+# if (defined(XP_WIN) || defined(XP_DARWIN))
# if defined(MOZ_REPLACE_MALLOC)
# define mozmem_malloc_impl(a) a ## _impl
# else
diff --git a/memory/build/replace_malloc.c b/memory/build/replace_malloc.c
index 135b566630..91f86497c5 100644
--- a/memory/build/replace_malloc.c
+++ b/memory/build/replace_malloc.c
@@ -26,7 +26,9 @@
* function resolved with GetProcAddress() instead of weak definitions
* of functions.
*/
-#if defined(XP_WIN)
+#ifdef XP_DARWIN
+# define MOZ_REPLACE_WEAK __attribute__((weak_import))
+#elif defined(XP_WIN)
# define MOZ_NO_REPLACE_FUNC_DECL
#elif defined(__GNUC__)
# define MOZ_REPLACE_WEAK __attribute__((weak))
@@ -282,6 +284,111 @@ MOZ_MEMORY_API __memalign_hook_type __memalign_hook = memalign_impl;
* owned by the allocator.
*/
+#ifdef XP_DARWIN
+#include <stdlib.h>
+#include <malloc/malloc.h>
+#include "mozilla/Assertions.h"
+
+static size_t
+zone_size(malloc_zone_t *zone, void *ptr)
+{
+ return malloc_usable_size_impl(ptr);
+}
+
+static void *
+zone_malloc(malloc_zone_t *zone, size_t size)
+{
+ return malloc_impl(size);
+}
+
+static void *
+zone_calloc(malloc_zone_t *zone, size_t num, size_t size)
+{
+ return calloc_impl(num, size);
+}
+
+static void *
+zone_realloc(malloc_zone_t *zone, void *ptr, size_t size)
+{
+ if (malloc_usable_size_impl(ptr))
+ return realloc_impl(ptr, size);
+ return realloc(ptr, size);
+}
+
+static void
+zone_free(malloc_zone_t *zone, void *ptr)
+{
+ if (malloc_usable_size_impl(ptr)) {
+ free_impl(ptr);
+ return;
+ }
+ free(ptr);
+}
+
+static void
+zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size)
+{
+ size_t current_size = malloc_usable_size_impl(ptr);
+ if (current_size) {
+ MOZ_ASSERT(current_size == size);
+ free_impl(ptr);
+ return;
+ }
+ free(ptr);
+}
+
+static void *
+zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size)
+{
+ void *ptr;
+ if (posix_memalign_impl(&ptr, alignment, size) == 0)
+ return ptr;
+ return NULL;
+}
+
+static void *
+zone_valloc(malloc_zone_t *zone, size_t size)
+{
+ return valloc_impl(size);
+}
+
+static void *
+zone_destroy(malloc_zone_t *zone)
+{
+ /* This function should never be called. */
+ MOZ_CRASH();
+}
+
+static size_t
+zone_good_size(malloc_zone_t *zone, size_t size)
+{
+ return malloc_good_size_impl(size);
+}
+
+#ifdef MOZ_JEMALLOC
+
+#include "jemalloc/internal/jemalloc_internal.h"
+
+static void
+zone_force_lock(malloc_zone_t *zone)
+{
+ /* /!\ This calls into jemalloc. It works because we're linked in the
+ * same library. Stolen from jemalloc's zone.c. */
+ if (isthreaded)
+ jemalloc_prefork();
+}
+
+static void
+zone_force_unlock(malloc_zone_t *zone)
+{
+ /* /!\ This calls into jemalloc. It works because we're linked in the
+ * same library. Stolen from jemalloc's zone.c. */
+ if (isthreaded)
+ jemalloc_postfork_parent();
+}
+
+#else
+
#define JEMALLOC_ZONE_VERSION 6
/* Empty implementations are needed, because fork() calls zone->force_(un)lock
@@ -296,7 +403,7 @@ zone_force_unlock(malloc_zone_t *zone)
{
}
-/* --- */
+#endif
static malloc_zone_t zone;
static struct malloc_introspection_t zone_introspect;
diff --git a/memory/mozalloc/mozalloc.cpp b/memory/mozalloc/mozalloc.cpp
index 1ae071ea04..55d36d80be 100644
--- a/memory/mozalloc/mozalloc.cpp
+++ b/memory/mozalloc/mozalloc.cpp
@@ -19,6 +19,10 @@
#define MOZ_MEMORY_IMPL
#include "mozmemory_wrap.h"
+#if defined(XP_DARWIN)
+#include <malloc/malloc.h> // for malloc_size
+#endif
+
// See mozmemory_wrap.h for more details. This file is part of libmozglue, so
// it needs to use _impl suffixes. However, with libmozglue growing, this is
// becoming cumbersome, so we will likely use a malloc.h wrapper of some sort
@@ -149,6 +153,17 @@ moz_posix_memalign(void **ptr, size_t alignment, size_t size)
if (code)
return code;
+#if defined(XP_DARWIN)
+ // Workaround faulty OSX posix_memalign, which provides memory with the
+ // incorrect alignment sometimes, but returns 0 as if nothing was wrong.
+ size_t mask = alignment - 1;
+ if (((size_t)(*ptr) & mask) != 0) {
+ void* old = *ptr;
+ code = moz_posix_memalign(ptr, alignment, size);
+ free(old);
+ }
+#endif
+
return code;
}
@@ -188,7 +203,9 @@ moz_malloc_usable_size(void *ptr)
if (!ptr)
return 0;
-#if defined(HAVE_MALLOC_USABLE_SIZE) || defined(MOZ_MEMORY)
+#if defined(XP_DARWIN)
+ return malloc_size(ptr);
+#elif defined(HAVE_MALLOC_USABLE_SIZE) || defined(MOZ_MEMORY)
return malloc_usable_size_impl(ptr);
#elif defined(XP_WIN)
return _msize(ptr);
diff --git a/memory/mozalloc/mozalloc.h b/memory/mozalloc/mozalloc.h
index 50dc53e5ce..d481335f94 100644
--- a/memory/mozalloc/mozalloc.h
+++ b/memory/mozalloc/mozalloc.h
@@ -150,9 +150,13 @@ MFBT_API void* moz_xvalloc(size_t size)
*/
/* NB: This is defined just to silence vacuous warnings about symbol
- * visibility on gcc. These symbols are force-inline and not exported.
- */
-#define MOZALLOC_EXPORT_NEW
+ * visibility on OS X/gcc. These symbols are force-inline and not
+ * exported. */
+#if defined(XP_MACOSX)
+# define MOZALLOC_EXPORT_NEW MFBT_API
+#else
+# define MOZALLOC_EXPORT_NEW
+#endif
#if defined(_MSC_VER)
/*
diff --git a/memory/volatile/VolatileBuffer.h b/memory/volatile/VolatileBuffer.h
index 19aeb9df2b..b6bcdfcb74 100644
--- a/memory/volatile/VolatileBuffer.h
+++ b/memory/volatile/VolatileBuffer.h
@@ -76,7 +76,9 @@ private:
void* mBuf;
size_t mSize;
int mLockCount;
-#if defined(XP_WIN)
+#ifdef XP_DARWIN
+ bool mHeap;
+#elif defined(XP_WIN)
bool mHeap;
bool mFirstLock;
#endif
diff --git a/mfbt/ThreadLocal.h b/mfbt/ThreadLocal.h
index 9466630c30..7acfa46548 100644
--- a/mfbt/ThreadLocal.h
+++ b/mfbt/ThreadLocal.h
@@ -30,7 +30,7 @@ typedef sig_atomic_t sig_safe_t;
namespace detail {
-#if defined(HAVE_THREAD_TLS_KEYWORD) || defined(XP_WIN)
+#if defined(HAVE_THREAD_TLS_KEYWORD) || defined(XP_WIN) || defined(XP_MACOSX)
#define MOZ_HAS_THREAD_LOCAL
#endif
@@ -180,7 +180,7 @@ ThreadLocal<T>::set(const T aValue)
}
#ifdef MOZ_HAS_THREAD_LOCAL
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
#define MOZ_THREAD_LOCAL(TYPE) thread_local mozilla::detail::ThreadLocal<TYPE>
#else
#define MOZ_THREAD_LOCAL(TYPE) __thread mozilla::detail::ThreadLocal<TYPE>
diff --git a/modules/libmar/tool/mar.c b/modules/libmar/tool/mar.c
index 8c9a05ec4d..cabd9b2802 100644
--- a/modules/libmar/tool/mar.c
+++ b/modules/libmar/tool/mar.c
@@ -61,7 +61,7 @@ static void print_usage() {
"signed_input_archive.mar base_64_encoded_signature_file "
"changed_signed_output.mar\n");
printf("(i) is the index of the certificate to extract\n");
-#if (defined(XP_WIN) && !defined(MAR_NSS))
+#if defined(XP_MACOSX) || (defined(XP_WIN) && !defined(MAR_NSS))
printf("Verify a MAR file:\n");
printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n");
printf("At most %d signature certificate DER files are specified by "
@@ -125,10 +125,11 @@ int main(int argc, char **argv) {
#if !defined(NO_SIGN_VERIFY)
uint32_t fileSizes[MAX_SIGNATURES];
const uint8_t* certBuffers[MAX_SIGNATURES];
-#if defined(XP_WIN) && !defined(MAR_NSS)
+#if ((!defined(MAR_NSS) && defined(XP_WIN)) || defined(XP_MACOSX)) || \
+ ((defined(XP_WIN) || defined(XP_MACOSX)) && !defined(MAR_NSS))
char* DERFilePaths[MAX_SIGNATURES];
#endif
-#if !defined(XP_WIN) || defined(MAR_NSS)
+#if (!defined(XP_WIN) && !defined(XP_MACOSX)) || defined(MAR_NSS)
CERTCertificate* certs[MAX_SIGNATURES];
#endif
#endif
@@ -137,7 +138,8 @@ int main(int argc, char **argv) {
#if defined(XP_WIN) && !defined(MAR_NSS) && !defined(NO_SIGN_VERIFY)
memset((void*)certBuffers, 0, sizeof(certBuffers));
#endif
-#if !defined(NO_SIGN_VERIFY) && (!defined(MAR_NSS) && defined(XP_WIN))
+#if !defined(NO_SIGN_VERIFY) && ((!defined(MAR_NSS) && defined(XP_WIN)) || \
+ defined(XP_MACOSX))
memset(DERFilePaths, 0, sizeof(DERFilePaths));
memset(fileSizes, 0, sizeof(fileSizes));
#endif
@@ -168,7 +170,8 @@ int main(int argc, char **argv) {
argv += 2;
argc -= 2;
}
-#if !defined(NO_SIGN_VERIFY) && (!defined(MAR_NSS) && defined(XP_WIN))
+#if !defined(NO_SIGN_VERIFY) && ((!defined(MAR_NSS) && defined(XP_WIN)) || \
+ defined(XP_MACOSX))
/* -D DERFilePath, also matches -D[index] DERFilePath
We allow an index for verifying to be symmetric
with the import and export command line arguments. */
@@ -326,7 +329,7 @@ int main(int argc, char **argv) {
return -1;
}
-#if !defined(XP_WIN) || defined(MAR_NSS)
+#if (!defined(XP_WIN) && !defined(XP_MACOSX)) || defined(MAR_NSS)
if (!NSSConfigDir || certCount == 0) {
print_usage();
return -1;
@@ -340,7 +343,7 @@ int main(int argc, char **argv) {
rv = 0;
for (k = 0; k < certCount; ++k) {
-#if defined(XP_WIN) && !defined(MAR_NSS)
+#if (defined(XP_WIN) || defined(XP_MACOSX)) && !defined(MAR_NSS)
rv = mar_read_entire_file(DERFilePaths[k], MAR_MAX_CERT_SIZE,
&certBuffers[k], &fileSizes[k]);
@@ -380,7 +383,7 @@ int main(int argc, char **argv) {
}
}
for (k = 0; k < certCount; ++k) {
-#if defined(XP_WIN) && !defined(MAR_NSS)
+#if (defined(XP_WIN) || defined(XP_MACOSX)) && !defined(MAR_NSS)
free((void*)certBuffers[k]);
#else
/* certBuffers[k] is owned by certs[k] so don't free it */
@@ -398,7 +401,7 @@ int main(int argc, char **argv) {
" no signature to verify.\n");
}
}
-#if !defined(XP_WIN) || defined(MAR_NSS)
+#if (!defined(XP_WIN) && !defined(XP_MACOSX)) || defined(MAR_NSS)
(void) NSS_Shutdown();
#endif
return rv ? -1 : 0;
diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp
index 5f17125da9..53540bdf85 100644
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -1291,7 +1291,9 @@ static nsresult pref_InitInitialObjects()
/* these pref file names should not be used: we process them after all other application pref files for backwards compatibility */
static const char* specialFiles[] = {
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+ "macprefs.js"
+#elif defined(XP_WIN)
"winpref.js"
#elif defined(XP_UNIX)
"unix.js"
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 03dec83bac..26df1c6ae5 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -382,7 +382,11 @@ pref("media.wmf.disable-d3d11-for-dlls", "igd11dxva64.dll: 20.19.15.4463, 20.19.
pref("media.wmf.disable-d3d9-for-dlls", "igdumd64.dll: 8.15.10.2189, 8.15.10.2119, 8.15.10.2104, 8.15.10.2102, 8.771.1.0; atiumd64.dll: 7.14.10.833, 7.14.10.867, 7.14.10.885, 7.14.10.903, 7.14.10.911, 8.14.10.768, 9.14.10.1001, 9.14.10.1017, 9.14.10.1080, 9.14.10.1128, 9.14.10.1162, 9.14.10.1171, 9.14.10.1183, 9.14.10.1197, 9.14.10.945, 9.14.10.972, 9.14.10.984, 9.14.10.996");
#endif
#if defined(MOZ_FFMPEG)
+#if defined(XP_MACOSX)
+pref("media.ffmpeg.enabled", false);
+#else
pref("media.ffmpeg.enabled", true);
+#endif
pref("media.libavcodec.allow-obsolete", false);
#endif
#if defined(MOZ_FFVPX)
@@ -511,7 +515,11 @@ pref("media.getusermedia.agc", 1);
// capture_delay: Adjustments for OS-specific input delay (lower bound)
// playout_delay: Adjustments for OS-specific AudioStream+cubeb+output delay (lower bound)
// full_duplex: enable cubeb full-duplex capture/playback
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+pref("media.peerconnection.capture_delay", 50);
+pref("media.getusermedia.playout_delay", 10);
+pref("media.navigator.audio.full_duplex", false);
+#elif defined(XP_WIN)
pref("media.peerconnection.capture_delay", 50);
pref("media.getusermedia.playout_delay", 40);
pref("media.navigator.audio.full_duplex", false);
@@ -548,7 +556,7 @@ pref("media.mediasource.enabled", true);
pref("media.mediasource.mp4.enabled", true);
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
pref("media.mediasource.webm.enabled", false);
#else
pref("media.mediasource.webm.enabled", true);
@@ -672,6 +680,14 @@ pref("apz.scale_repaint_delay_ms", 500);
pref("apz.desktop.enabled", false);
#endif
+#ifdef XP_MACOSX
+// Whether to run in native HiDPI mode on machines with "Retina"/HiDPI display;
+// <= 0 : hidpi mode disabled, display will just use pixel-based upscaling
+// == 1 : hidpi supported if all screens share the same backingScaleFactor
+// >= 2 : hidpi supported even with mixed backingScaleFactors (somewhat broken)
+pref("gfx.hidpi.enabled", 2);
+#endif
+
// Use containerless scrolling for now.
pref("layout.scroll.root-frame-containers", false);
@@ -686,8 +702,8 @@ pref("gfx.perf-warnings.enabled", false);
// Color Management System
// 0 = Off, 1 = All Images, 2 = Tagged Images Only.
// See eCMSMode in gfx/thebes/gfxPlatform.h
-// Enabled by default on Windows, disabled elsewhere
-#if defined(XP_WIN)
+// Enabled by default on Windows and Mac, disabled elsewhere
+#if defined(XP_WIN) || defined(XP_MACOSX)
pref("gfx.color_management.mode", 2);
#else
pref("gfx.color_management.mode", 0);
@@ -745,10 +761,17 @@ pref("gfx.font_rendering.opentype_svg.enabled", true);
pref("gfx.canvas.azure.backends", "direct2d1.1,skia,cairo");
pref("gfx.content.azure.backends", "direct2d1.1,cairo");
#else
+#ifdef XP_MACOSX
+pref("gfx.content.azure.backends", "cg");
+pref("gfx.canvas.azure.backends", "skia,cg");
+// Accelerated cg canvas where available (10.7+)
+pref("gfx.canvas.azure.accelerated", true);
+#else
// Linux etc.
pref("gfx.canvas.azure.backends", "skia,cairo");
pref("gfx.content.azure.backends", "cairo");
#endif
+#endif
pref("gfx.canvas.skiagl.dynamic-cache", true);
@@ -770,6 +793,7 @@ pref("accessibility.warn_on_browsewithcaret", true);
pref("accessibility.browsewithcaret_shortcut.enabled", true);
+#ifndef XP_MACOSX
// Tab focus model bit field:
// 1 focuses text controls, 2 focuses other form elements, 4 adds links.
// Most users will want 1, 3, or 7.
@@ -777,11 +801,15 @@ pref("accessibility.browsewithcaret_shortcut.enabled", true);
// unless accessibility.tabfocus is set by the user.
pref("accessibility.tabfocus", 7);
pref("accessibility.tabfocus_applies_to_xul", false);
+#else
+// Only on mac tabfocus is expected to handle UI widgets as well as web content
+pref("accessibility.tabfocus_applies_to_xul", true);
+#endif
// We follow the "Click in the scrollbar to:" system preference on OS X and
// "gtk-primary-button-warps-slider" property with GTK (since 2.24 / 3.6),
// unless this preference is explicitly set.
-#if !defined(MOZ_WIDGET_GTK)
+#if !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GTK)
pref("ui.scrollToClick", 0);
#endif
@@ -845,7 +873,11 @@ pref("accessibility.typeaheadfind.timeout", 4000);
pref("accessibility.typeaheadfind.enabletimeout", true);
pref("accessibility.typeaheadfind.soundURL", "beep");
pref("accessibility.typeaheadfind.enablesound", true);
+#ifdef XP_MACOSX
+pref("accessibility.typeaheadfind.prefillwithselection", false);
+#else
pref("accessibility.typeaheadfind.prefillwithselection", true);
+#endif
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
pref("findbar.highlightAll", false);
pref("findbar.modalHighlight", false);
@@ -1048,7 +1080,7 @@ pref("print.print_edge_right", 0);
pref("print.print_edge_bottom", 0);
// Print via the parent process. This is only used when e10s is enabled.
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
pref("print.print_via_parent", true);
#else
pref("print.print_via_parent", false);
@@ -1306,6 +1338,9 @@ pref("network.protocol-handler.external.mk", false);
pref("network.protocol-handler.external.res", false);
pref("network.protocol-handler.external.shell", false);
pref("network.protocol-handler.external.vnd.ms.radio", false);
+#ifdef XP_MACOSX
+pref("network.protocol-handler.external.help", false);
+#endif
pref("network.protocol-handler.external.disk", false);
pref("network.protocol-handler.external.disks", false);
pref("network.protocol-handler.external.afp", false);
@@ -2476,7 +2511,13 @@ pref("layout.css.text-combine-upright-digits.enabled", false);
// Is support for object-fit and object-position enabled?
pref("layout.css.object-fit-and-position.enabled", true);
+// Is -moz-osx-font-smoothing enabled?
+// Only supported in OSX builds
+#ifdef XP_MACOSX
+pref("layout.css.osx-font-smoothing.enabled", true);
+#else
pref("layout.css.osx-font-smoothing.enabled", false);
+#endif
// Is support for the CSS-wide "unset" value enabled?
pref("layout.css.unset-value.enabled", true);
@@ -3470,6 +3511,328 @@ pref("ui.osk.debug.keyboardDisplayReason", "");
# XP_WIN
#endif
+#ifdef XP_MACOSX
+// Mac specific preference defaults
+pref("browser.drag_out_of_frame_style", 1);
+pref("ui.key.saveLink.shift", false); // true = shift, false = meta
+
+pref("font.name-list.emoji", "Apple Color Emoji");
+
+// default fonts (in UTF8 and using canonical names)
+// to determine canonical font names, use a debug build and
+// enable NSPR logging for module fontInfoLog:5
+// canonical names immediately follow '(fontinit) family:' in the log
+
+pref("font.name.serif.ar", "Al Bayan");
+pref("font.name.sans-serif.ar", "Geeza Pro");
+pref("font.name.monospace.ar", "Geeza Pro");
+pref("font.name.cursive.ar", "DecoType Naskh");
+pref("font.name.fantasy.ar", "KufiStandardGK");
+pref("font.name-list.serif.ar", "Al Bayan");
+pref("font.name-list.sans-serif.ar", "Geeza Pro");
+pref("font.name-list.monospace.ar", "Geeza Pro");
+pref("font.name-list.cursive.ar", "DecoType Naskh");
+pref("font.name-list.fantasy.ar", "KufiStandardGK");
+
+pref("font.name.serif.el", "Times");
+pref("font.name.sans-serif.el", "Helvetica");
+pref("font.name.monospace.el", "Courier New");
+pref("font.name.cursive.el", "Lucida Grande");
+pref("font.name.fantasy.el", "Lucida Grande");
+pref("font.name-list.serif.el", "Times,Times New Roman");
+pref("font.name-list.sans-serif.el", "Helvetica,Lucida Grande");
+pref("font.name-list.monospace.el", "Courier New,Lucida Grande");
+pref("font.name-list.cursive.el", "Times,Lucida Grande");
+pref("font.name-list.fantasy.el", "Times,Lucida Grande");
+
+pref("font.name.serif.he", "Times New Roman");
+pref("font.name.sans-serif.he", "Arial");
+pref("font.name.monospace.he", "Courier New");
+pref("font.name.cursive.he", "Times New Roman");
+pref("font.name.fantasy.he", "Times New Roman");
+pref("font.name-list.serif.he", "Times New Roman");
+pref("font.name-list.sans-serif.he", "Arial");
+pref("font.name-list.monospace.he", "Courier New");
+pref("font.name-list.cursive.he", "Times New Roman");
+pref("font.name-list.fantasy.he", "Times New Roman");
+
+pref("font.name.serif.ja", "Hiragino Mincho ProN");
+pref("font.name.sans-serif.ja", "Hiragino Kaku Gothic ProN");
+pref("font.name.monospace.ja", "Osaka-Mono");
+pref("font.name-list.serif.ja", "Hiragino Mincho ProN,Hiragino Mincho Pro");
+pref("font.name-list.sans-serif.ja", "Hiragino Kaku Gothic ProN,Hiragino Kaku Gothic Pro");
+pref("font.name-list.monospace.ja", "Osaka-Mono");
+
+pref("font.name.serif.ko", "AppleMyungjo");
+pref("font.name.sans-serif.ko", "Apple SD Gothic Neo");
+pref("font.name.monospace.ko", "Apple SD Gothic Neo");
+pref("font.name-list.serif.ko", "AppleMyungjo");
+pref("font.name-list.sans-serif.ko", "Apple SD Gothic Neo,AppleGothic");
+pref("font.name-list.monospace.ko", "Apple SD Gothic Neo,AppleGothic");
+
+pref("font.name.serif.th", "Thonburi");
+pref("font.name.sans-serif.th", "Thonburi");
+pref("font.name.monospace.th", "Ayuthaya");
+pref("font.name-list.serif.th", "Thonburi");
+pref("font.name-list.sans-serif.th", "Thonburi");
+pref("font.name-list.monospace.th", "Ayuthaya");
+
+pref("font.name.serif.x-armn", "Mshtakan");
+pref("font.name.sans-serif.x-armn", "Mshtakan");
+pref("font.name.monospace.x-armn", "Mshtakan");
+pref("font.name-list.serif.x-armn", "Mshtakan");
+pref("font.name-list.sans-serif.x-armn", "Mshtakan");
+pref("font.name-list.monospace.x-armn", "Mshtakan");
+
+// SolaimanLipi, Rupali http://ekushey.org/?page/mac_download
+pref("font.name.serif.x-beng", "Bangla MN");
+pref("font.name.sans-serif.x-beng", "Bangla Sangam MN");
+pref("font.name.monospace.x-beng", "Bangla Sangam MN");
+pref("font.name-list.serif.x-beng", "Bangla MN");
+pref("font.name-list.sans-serif.x-beng", "Bangla Sangam MN");
+pref("font.name-list.monospace.x-beng", "Bangla Sangam MN");
+
+pref("font.name.serif.x-cans", "Euphemia UCAS");
+pref("font.name.sans-serif.x-cans", "Euphemia UCAS");
+pref("font.name.monospace.x-cans", "Euphemia UCAS");
+pref("font.name-list.serif.x-cans", "Euphemia UCAS");
+pref("font.name-list.sans-serif.x-cans", "Euphemia UCAS");
+pref("font.name-list.monospace.x-cans", "Euphemia UCAS");
+
+pref("font.name.serif.x-cyrillic", "Times");
+pref("font.name.sans-serif.x-cyrillic", "Helvetica");
+pref("font.name.monospace.x-cyrillic", "Monaco");
+pref("font.name.cursive.x-cyrillic", "Geneva");
+pref("font.name.fantasy.x-cyrillic", "Charcoal CY");
+pref("font.name-list.serif.x-cyrillic", "Times,Times New Roman");
+pref("font.name-list.sans-serif.x-cyrillic", "Helvetica,Arial");
+pref("font.name-list.monospace.x-cyrillic", "Monaco,Courier New");
+pref("font.name-list.cursive.x-cyrillic", "Geneva");
+pref("font.name-list.fantasy.x-cyrillic", "Charcoal CY");
+
+pref("font.name.serif.x-devanagari", "Devanagari MT");
+pref("font.name.sans-serif.x-devanagari", "Devanagari Sangam MN");
+pref("font.name.monospace.x-devanagari", "Devanagari Sangam MN");
+pref("font.name-list.serif.x-devanagari", "Devanagari MT");
+pref("font.name-list.sans-serif.x-devanagari", "Devanagari Sangam MN,Devanagari MT");
+pref("font.name-list.monospace.x-devanagari", "Devanagari Sangam MN,Devanagari MT");
+
+// Abyssinica SIL http://scripts.sil.org/AbyssinicaSIL_Download
+pref("font.name.serif.x-ethi", "Kefa");
+pref("font.name.sans-serif.x-ethi", "Kefa");
+pref("font.name.monospace.x-ethi", "Kefa");
+pref("font.name-list.serif.x-ethi", "Kefa,Abyssinica SIL");
+pref("font.name-list.sans-serif.x-ethi", "Kefa,Abyssinica SIL");
+pref("font.name-list.monospace.x-ethi", "Kefa,Abyssinica SIL");
+
+// no suitable fonts for georgian ship with mac os x
+// however some can be freely downloaded
+// TITUS Cyberbit Basic http://titus.fkidg1.uni-frankfurt.de/unicode/tituut.asp
+// Zuzumbo http://homepage.mac.com/rsiradze/FileSharing91.html
+pref("font.name.serif.x-geor", "TITUS Cyberbit Basic");
+pref("font.name.sans-serif.x-geor", "Zuzumbo");
+pref("font.name.monospace.x-geor", "Zuzumbo");
+pref("font.name-list.serif.x-geor", "TITUS Cyberbit Basic");
+pref("font.name-list.sans-serif.x-geor", "Zuzumbo");
+pref("font.name-list.monospace.x-geor", "Zuzumbo");
+
+pref("font.name.serif.x-gujr", "Gujarati MT");
+pref("font.name.sans-serif.x-gujr", "Gujarati Sangam MN");
+pref("font.name.monospace.x-gujr", "Gujarati Sangam MN");
+pref("font.name-list.serif.x-gujr", "Gujarati MT");
+pref("font.name-list.sans-serif.x-gujr", "Gujarati Sangam MN,Gujarati MT");
+pref("font.name-list.monospace.x-gujr", "Gujarati Sangam MN,Gujarati MT");
+
+pref("font.name.serif.x-guru", "Gurmukhi MT");
+pref("font.name.sans-serif.x-guru", "Gurmukhi MT");
+pref("font.name.monospace.x-guru", "Gurmukhi MT");
+pref("font.name-list.serif.x-guru", "Gurmukhi MT");
+pref("font.name-list.sans-serif.x-guru", "Gurmukhi MT");
+pref("font.name-list.monospace.x-guru", "Gurmukhi MT");
+
+pref("font.name.serif.x-khmr", "Khmer MN");
+pref("font.name.sans-serif.x-khmr", "Khmer Sangam MN");
+pref("font.name.monospace.x-khmr", "Khmer Sangam MN");
+pref("font.name-list.serif.x-khmr", "Khmer MN");
+pref("font.name-list.sans-serif.x-khmr", "Khmer Sangam MN");
+pref("font.name-list.monospace.x-khmr", "Khmer Sangam MN");
+
+pref("font.name.serif.x-mlym", "Malayalam MN");
+pref("font.name.sans-serif.x-mlym", "Malayalam Sangam MN");
+pref("font.name.monospace.x-mlym", "Malayalam Sangam MN");
+pref("font.name-list.serif.x-mlym", "Malayalam MN");
+pref("font.name-list.sans-serif.x-mlym", "Malayalam Sangam MN");
+pref("font.name-list.monospace.x-mlym", "Malayalam Sangam MN");
+
+pref("font.name.serif.x-orya", "Oriya MN");
+pref("font.name.sans-serif.x-orya", "Oriya Sangam MN");
+pref("font.name.monospace.x-orya", "Oriya Sangam MN");
+pref("font.name-list.serif.x-orya", "Oriya MN");
+pref("font.name-list.sans-serif.x-orya", "Oriya Sangam MN");
+pref("font.name-list.monospace.x-orya", "Oriya Sangam MN");
+
+// Pothana http://web.nickshanks.com/typography/telugu/
+pref("font.name.serif.x-telu", "Telugu MN");
+pref("font.name.sans-serif.x-telu", "Telugu Sangam MN");
+pref("font.name.monospace.x-telu", "Telugu Sangam MN");
+pref("font.name-list.serif.x-telu", "Telugu MN,Pothana");
+pref("font.name-list.sans-serif.x-telu", "Telugu Sangam MN,Pothana");
+pref("font.name-list.monospace.x-telu", "Telugu Sangam MN,Pothana");
+
+// Kedage http://web.nickshanks.com/typography/kannada/
+pref("font.name.serif.x-knda", "Kannada MN");
+pref("font.name.sans-serif.x-knda", "Kannada Sangam MN");
+pref("font.name.monospace.x-knda", "Kannada Sangam MN");
+pref("font.name-list.serif.x-knda", "Kannada MN,Kedage");
+pref("font.name-list.sans-serif.x-knda", "Kannada Sangam MN,Kedage");
+pref("font.name-list.monospace.x-knda", "Kannada Sangam MN,Kedage");
+
+pref("font.name.serif.x-sinh", "Sinhala MN");
+pref("font.name.sans-serif.x-sinh", "Sinhala Sangam MN");
+pref("font.name.monospace.x-sinh", "Sinhala Sangam MN");
+pref("font.name-list.serif.x-sinh", "Sinhala MN");
+pref("font.name-list.sans-serif.x-sinh", "Sinhala Sangam MN");
+pref("font.name-list.monospace.x-sinh", "Sinhala Sangam MN");
+
+pref("font.name.serif.x-tamil", "InaiMathi");
+pref("font.name.sans-serif.x-tamil", "InaiMathi");
+pref("font.name.monospace.x-tamil", "InaiMathi");
+pref("font.name-list.serif.x-tamil", "InaiMathi");
+pref("font.name-list.sans-serif.x-tamil", "InaiMathi");
+pref("font.name-list.monospace.x-tamil", "InaiMathi");
+
+// Kailasa ships with mac os x >= 10.5
+pref("font.name.serif.x-tibt", "Kailasa");
+pref("font.name.sans-serif.x-tibt", "Kailasa");
+pref("font.name.monospace.x-tibt", "Kailasa");
+pref("font.name-list.serif.x-tibt", "Kailasa");
+pref("font.name-list.sans-serif.x-tibt", "Kailasa");
+pref("font.name-list.monospace.x-tibt", "Kailasa");
+
+pref("font.name.serif.x-unicode", "Times");
+pref("font.name.sans-serif.x-unicode", "Helvetica");
+pref("font.name.monospace.x-unicode", "Courier");
+pref("font.name.cursive.x-unicode", "Apple Chancery");
+pref("font.name.fantasy.x-unicode", "Papyrus");
+pref("font.name-list.serif.x-unicode", "Times");
+pref("font.name-list.sans-serif.x-unicode", "Helvetica");
+pref("font.name-list.monospace.x-unicode", "Courier");
+pref("font.name-list.cursive.x-unicode", "Apple Chancery");
+pref("font.name-list.fantasy.x-unicode", "Papyrus");
+
+pref("font.name.serif.x-western", "Times");
+pref("font.name.sans-serif.x-western", "Helvetica");
+pref("font.name.monospace.x-western", "Courier");
+pref("font.name.cursive.x-western", "Apple Chancery");
+pref("font.name.fantasy.x-western", "Papyrus");
+pref("font.name-list.serif.x-western", "Times,Times New Roman");
+pref("font.name-list.sans-serif.x-western", "Helvetica,Arial");
+pref("font.name-list.monospace.x-western", "Courier,Courier New");
+pref("font.name-list.cursive.x-western", "Apple Chancery");
+pref("font.name-list.fantasy.x-western", "Papyrus");
+
+pref("font.name.serif.zh-CN", "Times");
+pref("font.name.sans-serif.zh-CN", "Helvetica");
+pref("font.name.monospace.zh-CN", "Courier");
+pref("font.name.cursive.zh-CN", "Kaiti SC");
+pref("font.name-list.serif.zh-CN", "Times,STSong,Heiti SC");
+pref("font.name-list.sans-serif.zh-CN", "Helvetica,PingFang SC,STHeiti,Heiti SC");
+pref("font.name-list.monospace.zh-CN", "Courier,PingFang SC,STHeiti,Heiti SC");
+
+pref("font.name.serif.zh-TW", "Times");
+pref("font.name.sans-serif.zh-TW", "Helvetica");
+pref("font.name.monospace.zh-TW", "Courier");
+pref("font.name.cursive.zh-TW", "Kaiti TC");
+pref("font.name-list.serif.zh-TW", "Times,LiSong Pro,Heiti TC");
+pref("font.name-list.sans-serif.zh-TW", "Helvetica,PingFang TC,Heiti TC,LiHei Pro");
+pref("font.name-list.monospace.zh-TW", "Courier,PingFang TC,Heiti TC,LiHei Pro");
+
+pref("font.name.serif.zh-HK", "Times");
+pref("font.name.sans-serif.zh-HK", "Helvetica");
+pref("font.name.monospace.zh-HK", "Courier");
+pref("font.name.cursive.zh-HK", "Kaiti TC");
+pref("font.name-list.serif.zh-HK", "Times,LiSong Pro,Heiti TC");
+pref("font.name-list.sans-serif.zh-HK", "Helvetica,PingFang TC,Heiti TC,LiHei Pro");
+pref("font.name-list.monospace.zh-HK", "Courier,PingFang TC,Heiti TC,LiHei Pro");
+
+// XP_MACOSX changes to default font sizes
+pref("font.minimum-size.th", 10);
+pref("font.size.variable.zh-CN", 15);
+pref("font.size.variable.zh-HK", 15);
+pref("font.size.variable.zh-TW", 15);
+
+pref("font.name.serif.x-math", "Latin Modern Math");
+// Apple's Symbol is Unicode so use it
+pref("font.name-list.serif.x-math", "Latin Modern Math, XITS Math, Cambria Math, Libertinus Math, DejaVu Math TeX Gyre, TeX Gyre Bonum Math, TeX Gyre Pagella Math, TeX Gyre Schola, TeX Gyre Termes Math, STIX Math, Asana Math, STIXGeneral, DejaVu Serif, DejaVu Sans, Symbol, Times");
+pref("font.name.sans-serif.x-math", "Helvetica");
+pref("font.name.monospace.x-math", "Courier");
+pref("font.name.cursive.x-math", "Apple Chancery");
+pref("font.name.fantasy.x-math", "Papyrus");
+
+// Individual font faces to be treated as independent families,
+// listed as <Postscript name of face:Owning family name>
+pref("font.single-face-list", "Osaka-Mono:Osaka");
+
+// optimization hint for fonts with localized names to be read in at startup, otherwise read in at lookup miss
+// names are canonical family names (typically English names)
+pref("font.preload-names-list", "Hiragino Kaku Gothic ProN,Hiragino Mincho ProN,STSong");
+
+// Override font-weight values for some problematic families Apple ships
+// (see bug 931426).
+// The name here is the font's PostScript name, which can be checked in
+// the Font Book utility or other tools.
+pref("font.weight-override.AppleSDGothicNeo-Thin", 100); // Ensure Thin < UltraLight < Light
+pref("font.weight-override.AppleSDGothicNeo-UltraLight", 200);
+pref("font.weight-override.AppleSDGothicNeo-Light", 300);
+pref("font.weight-override.AppleSDGothicNeo-Heavy", 900); // Ensure Heavy > ExtraBold (800)
+
+pref("font.weight-override.Avenir-Book", 300); // Ensure Book < Roman (400)
+pref("font.weight-override.Avenir-BookOblique", 300);
+pref("font.weight-override.Avenir-MediumOblique", 500); // Harmonize MediumOblique with Medium
+pref("font.weight-override.Avenir-Black", 900); // Ensure Black > Heavy (800)
+pref("font.weight-override.Avenir-BlackOblique", 900);
+
+pref("font.weight-override.AvenirNext-MediumItalic", 500); // Harmonize MediumItalic with Medium
+pref("font.weight-override.AvenirNextCondensed-MediumItalic", 500);
+
+pref("font.weight-override.HelveticaNeue-Light", 300); // Ensure Light > Thin (200)
+pref("font.weight-override.HelveticaNeue-LightItalic", 300);
+pref("font.weight-override.HelveticaNeue-MediumItalic", 500); // Harmonize MediumItalic with Medium
+
+// Override the Windows settings: no menu key, meta accelerator key. ctrl for general access key in HTML/XUL
+// Use 17 for Ctrl, 18 for Option, 224 for Cmd, 0 for none
+pref("ui.key.menuAccessKey", 0);
+pref("ui.key.accelKey", 224);
+// (pinkerton, joki, saari) IE5 for mac uses Control for access keys. The HTML4 spec
+// suggests to use command on mac, but this really sucks (imagine someone having a "q"
+// as an access key and not letting you quit the app!). As a result, we've made a
+// command decision 1 day before tree lockdown to change it to the control key.
+pref("ui.key.generalAccessKey", -1);
+
+// If generalAccessKey is -1, use the following two prefs instead.
+// Use 0 for disabled, 1 for Shift, 2 for Ctrl, 4 for Alt, 8 for Meta (Cmd)
+// (values can be combined, e.g. 3 for Ctrl+Shift)
+pref("ui.key.chromeAccess", 2);
+pref("ui.key.contentAccess", 6);
+
+// print_extra_margin enables platforms to specify an extra gap or margin
+// around the content of the page for Print Preview only
+pref("print.print_extra_margin", 90); // twips (90 twips is an eigth of an inch)
+
+// See bug 404131, topmost <panel> element wins to Dashboard on MacOSX.
+pref("ui.panel.default_level_parent", false);
+
+pref("ui.plugin.cancel_composition_at_input_source_changed", false);
+
+pref("mousewheel.system_scroll_override_on_root_content.enabled", false);
+
+// Macbook touchpad two finger pixel scrolling
+pref("mousewheel.enable_pixel_scrolling", true);
+
+# XP_MACOSX
+#endif
+
+#ifndef XP_MACOSX
#ifdef XP_UNIX
// Handled differently under Mac/Windows
pref("network.protocol-handler.warn-external.file", false);
@@ -3679,6 +4042,7 @@ pref("gfx.font_rendering.fontconfig.max_generic_substitutions", 3);
# XP_UNIX
#endif
+#endif
#if OS_ARCH==AIX
@@ -3818,6 +4182,9 @@ pref("canvas.poisondata", false);
// WebGL prefs
pref("gl.msaa-level", 2);
pref("gl.require-hardware", false);
+#ifdef XP_MACOSX
+pref("gl.multithreaded", true);
+#endif
pref("gl.ignore-dx-interop2-blacklist", false);
pref("webgl.force-enabled", false);
@@ -3877,17 +4244,17 @@ pref("network.tcp.keepalive.enabled", true);
pref("network.tcp.keepalive.idle_time", 600); // seconds; 10 mins
// Default timeout for retransmission of unack'd keepalive probes.
// Win and Linux only; not configurable on Mac.
-#if defined(XP_UNIX) || defined(XP_WIN)
+#if defined(XP_UNIX) && !defined(XP_MACOSX) || defined(XP_WIN)
pref("network.tcp.keepalive.retry_interval", 1); // seconds
#endif
// Default maximum probe retransmissions.
// Linux only; not configurable on Win and Mac; fixed at 10 and 8 respectively.
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
pref("network.tcp.keepalive.probe_count", 4);
#endif
// Whether to disable acceleration for all widgets.
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
// On Linux this is disabled by default for known issues with "free" drivers
pref("layers.acceleration.enabled", false);
#else
@@ -3940,6 +4307,13 @@ pref("layers.tiles.adjust", true);
// 0 -> full-tilt mode: Recomposite even if not transaction occured.
pref("layers.offmainthreadcomposition.frame-rate", -1);
+#ifdef XP_MACOSX
+pref("layers.enable-tiles", true);
+pref("layers.tile-width", 512);
+pref("layers.tile-height", 512);
+pref("layers.tiles.edge-padding", false);
+#endif
+
// Whether to animate simple opacity and transforms on the compositor
pref("layers.offmainthreadcomposition.async-animations", true);
@@ -3983,6 +4357,11 @@ pref("layers.shared-buffer-provider.enabled", true);
pref("layers.shared-buffer-provider.enabled", false);
#endif
+#ifdef XP_MACOSX
+// cf. Bug 1324908
+pref("layers.shared-buffer-provider.enabled", false);
+#endif
+
// Force all possible layers to be always active layers
pref("layers.force-active", false);
@@ -4135,7 +4514,11 @@ pref("dom.mozPermissionSettings.enabled", false);
// W3C touch events
// 0 - disabled, 1 - enabled, 2 - autodetect
// Autodetection is currently only supported on Windows and GTK3
+#if defined(XP_MACOSX)
+pref("dom.w3c_touch_events.enabled", 0);
+#else
pref("dom.w3c_touch_events.enabled", 2);
+#endif
// W3C draft pointer events
pref("dom.w3c_pointer_events.enabled", true);
@@ -4395,6 +4778,15 @@ pref("dom.udpsocket.enabled", false);
// Disable before keyboard events and after keyboard events by default.
pref("dom.beforeAfterKeyboardEvent.enabled", false);
+#ifdef XP_MACOSX
+#if defined(DEBUG)
+// In debug builds we crash by default on insecure text input (when a
+// password editor has focus but secure event input isn't enabled). The
+// following pref, when turned on, disables this behavior. See bug 1188425.
+pref("intl.allow-insecure-text-input", false);
+#endif
+#endif // XP_MACOSX
+
// Enable meta-viewport support in remote APZ-enabled frames.
pref("dom.meta-viewport.enabled", false);
diff --git a/mozglue/misc/StackWalk.cpp b/mozglue/misc/StackWalk.cpp
index 0fa2250aec..8925ae9488 100644
--- a/mozglue/misc/StackWalk.cpp
+++ b/mozglue/misc/StackWalk.cpp
@@ -29,10 +29,17 @@ static CriticalAddress gCriticalAddress;
#define _GNU_SOURCE
#endif
-#if defined(HAVE_DLOPEN)
+#if defined(HAVE_DLOPEN) || defined(XP_DARWIN)
#include <dlfcn.h>
#endif
+#if (defined(XP_DARWIN) && \
+ (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE)))
+#define MOZ_STACKWALK_SUPPORTS_MACOSX 1
+#else
+#define MOZ_STACKWALK_SUPPORTS_MACOSX 0
+#endif
+
#if (defined(linux) && \
((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \
defined(HAVE__UNWIND_BACKTRACE)))
@@ -51,18 +58,121 @@ static CriticalAddress gCriticalAddress;
extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so
#endif
+#if MOZ_STACKWALK_SUPPORTS_MACOSX
+#include <pthread.h>
+#include <sys/errno.h>
+#ifdef MOZ_WIDGET_COCOA
+#include <CoreServices/CoreServices.h>
+#endif
+
+typedef void
+malloc_logger_t(uint32_t aType,
+ uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3,
+ uintptr_t aResult, uint32_t aNumHotFramesToSkip);
+extern malloc_logger_t* malloc_logger;
+
+static void
+stack_callback(uint32_t aFrameNumber, void* aPc, void* aSp, void* aClosure)
+{
+ const char* name = static_cast<char*>(aClosure);
+ Dl_info info;
+
+ // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The
+ // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The
+ // correct one is the first that we find on our way up, so the
+ // following check for gCriticalAddress.mAddr is critical.
+ if (gCriticalAddress.mAddr || dladdr(aPc, &info) == 0 ||
+ !info.dli_sname || strcmp(info.dli_sname, name) != 0) {
+ return;
+ }
+ gCriticalAddress.mAddr = aPc;
+}
+
+static void
+my_malloc_logger(uint32_t aType,
+ uintptr_t aArg1, uintptr_t aArg2, uintptr_t aArg3,
+ uintptr_t aResult, uint32_t aNumHotFramesToSkip)
+{
+ static bool once = false;
+ if (once) {
+ return;
+ }
+ once = true;
+
+ // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The
+ // stack shows up as having two pthread_cond_wait$UNIX2003 frames.
+ const char* name = "new_sem_from_pool";
+ MozStackWalk(stack_callback, /* skipFrames */ 0, /* maxFrames */ 0,
+ const_cast<char*>(name), 0, nullptr);
+}
+
+// This is called from NS_LogInit() and from the stack walking functions, but
+// only the first call has any effect. We need to call this function from both
+// places because it must run before any mutexes are created, and also before
+// any objects whose refcounts we're logging are created. Running this
+// function during NS_LogInit() ensures that we meet the first criterion, and
+// running this function during the stack walking functions ensures we meet the
+// second criterion.
+MFBT_API void
+StackWalkInitCriticalAddress()
+{
+ if (gCriticalAddress.mInit) {
+ return;
+ }
+ gCriticalAddress.mInit = true;
+ // We must not do work when 'new_sem_from_pool' calls realloc, since
+ // it holds a non-reentrant spin-lock and we will quickly deadlock.
+ // new_sem_from_pool is not directly accessible using dlsym, so
+ // we force a situation where new_sem_from_pool is on the stack and
+ // use dladdr to check the addresses.
+
+ // malloc_logger can be set by external tools like 'Instruments' or 'leaks'
+ malloc_logger_t* old_malloc_logger = malloc_logger;
+ malloc_logger = my_malloc_logger;
+
+ pthread_cond_t cond;
+ int r = pthread_cond_init(&cond, 0);
+ MOZ_ASSERT(r == 0);
+ pthread_mutex_t mutex;
+ r = pthread_mutex_init(&mutex, 0);
+ MOZ_ASSERT(r == 0);
+ r = pthread_mutex_lock(&mutex);
+ MOZ_ASSERT(r == 0);
+ struct timespec abstime = { 0, 1 };
+ r = pthread_cond_timedwait_relative_np(&cond, &mutex, &abstime);
+
+ // restore the previous malloc logger
+ malloc_logger = old_malloc_logger;
+
+ MOZ_ASSERT(r == ETIMEDOUT);
+ r = pthread_mutex_unlock(&mutex);
+ MOZ_ASSERT(r == 0);
+ r = pthread_mutex_destroy(&mutex);
+ MOZ_ASSERT(r == 0);
+ r = pthread_cond_destroy(&cond);
+ MOZ_ASSERT(r == 0);
+}
+
+static bool
+IsCriticalAddress(void* aPC)
+{
+ return gCriticalAddress.mAddr == aPC;
+}
+#else
static bool
IsCriticalAddress(void* aPC)
{
return false;
}
// We still initialize gCriticalAddress.mInit so that this code behaves
-// the same on all platforms.
+// the same on all platforms. Otherwise a failure to init would be visible
+// only on OS X.
MFBT_API void
StackWalkInitCriticalAddress()
{
gCriticalAddress.mInit = true;
}
+#endif
#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code
@@ -799,6 +909,8 @@ MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
void* stackEnd;
#if HAVE___LIBC_STACK_END
stackEnd = __libc_stack_end;
+#elif defined(XP_DARWIN)
+ stackEnd = pthread_get_stackaddr_np(pthread_self());
#else
# error Unsupported configuration
#endif
@@ -939,7 +1051,7 @@ MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails)
#endif
-#if defined(XP_WIN) || defined (XP_LINUX)
+#if defined(XP_WIN) || defined (XP_MACOSX) || defined (XP_LINUX)
namespace mozilla {
bool
FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
@@ -963,8 +1075,8 @@ FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames,
(uintptr_t(next) & 3)) {
break;
}
-#if defined(__powerpc64__)
- // powerpc64 linux
+#if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__)
+ // ppc mac or powerpc64 linux
void* pc = *(bp + 2);
bp += 3;
#else // i386 or powerpc32 linux
diff --git a/mozglue/misc/TimeStamp.h b/mozglue/misc/TimeStamp.h
index 20e70cbdc1..019393b284 100644
--- a/mozglue/misc/TimeStamp.h
+++ b/mozglue/misc/TimeStamp.h
@@ -399,9 +399,18 @@ public:
* retrieved by mozilla::TimeStamp. Since we need this for
* vsync timestamps, we enable the creation of mozilla::TimeStamps
* on platforms that support vsync aligned refresh drivers / compositors
+ * Verified true as of Jan 31, 2015: OS X
* False on Windows 7
* UNTESTED ON OTHER PLATFORMS
*/
+#if defined(XP_DARWIN)
+ static TimeStamp FromSystemTime(int64_t aSystemTime)
+ {
+ static_assert(sizeof(aSystemTime) == sizeof(TimeStampValue),
+ "System timestamp should be same units as TimeStampValue");
+ return TimeStamp(aSystemTime);
+ }
+#endif
/**
* Return true if this is the "null" moment
diff --git a/netwerk/base/NetworkInfoServiceCocoa.cpp b/netwerk/base/NetworkInfoServiceCocoa.cpp
new file mode 100644
index 0000000000..cdfa8e5c97
--- /dev/null
+++ b/netwerk/base/NetworkInfoServiceCocoa.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; 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 <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <net/if.h>
+#include <netdb.h>
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
+
+#include "NetworkInfoServiceImpl.h"
+
+namespace mozilla {
+namespace net {
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aIface, AddrMapType& aAddrMap);
+
+nsresult
+DoListAddresses(AddrMapType& aAddrMap)
+{
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto autoCloseSocket = MakeScopeExit([&] {
+ close(fd);
+ });
+
+ struct ifconf ifconf;
+ /* 16k of space should be enough to list all interfaces. Worst case, if it's
+ * not then we will error out and fail to list addresses. This should only
+ * happen on pathological machines with way too many interfaces.
+ */
+ char buf[16384];
+
+ ifconf.ifc_len = sizeof(buf);
+ ifconf.ifc_buf = buf;
+ if (ioctl(fd, SIOCGIFCONF, &ifconf) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ struct ifreq* ifreq = ifconf.ifc_req;
+ int i = 0;
+ while (i < ifconf.ifc_len) {
+ size_t len = IFNAMSIZ + ifreq->ifr_addr.sa_len;
+
+ DebugOnly<nsresult> rv =
+ ListInterfaceAddresses(fd, ifreq->ifr_name, aAddrMap);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ListInterfaceAddresses failed");
+
+ ifreq = (struct ifreq*) ((char*)ifreq + len);
+ i += len;
+ }
+
+ autoCloseSocket.release();
+ return NS_OK;
+}
+
+static nsresult
+ListInterfaceAddresses(int aFd, const char* aInterface, AddrMapType& aAddrMap)
+{
+ struct ifreq ifreq;
+ memset(&ifreq, 0, sizeof(struct ifreq));
+ strncpy(ifreq.ifr_name, aInterface, IFNAMSIZ - 1);
+ if (ioctl(aFd, SIOCGIFADDR, &ifreq) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char host[128];
+ int family;
+ switch(family=ifreq.ifr_addr.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ getnameinfo(&ifreq.ifr_addr, sizeof(ifreq.ifr_addr), host, sizeof(host), 0, 0, NI_NUMERICHOST);
+ break;
+ case AF_UNSPEC:
+ return NS_OK;
+ default:
+ // Unknown family.
+ return NS_OK;
+ }
+
+ nsCString ifaceStr;
+ ifaceStr.AssignASCII(aInterface);
+
+ nsCString addrStr;
+ addrStr.AssignASCII(host);
+
+ aAddrMap.Put(ifaceStr, addrStr);
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build
index 3ec17b2b2a..1659299f7b 100644
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -257,6 +257,10 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'nsURLHelperWin.cpp',
'ShutdownLayer.cpp',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsURLHelperOSX.cpp',
+ ]
else:
SOURCES += [
'nsURLHelperUnix.cpp',
@@ -268,6 +272,11 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'NetworkInfoServiceWindows.cpp',
'nsNetworkInfoService.cpp',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'NetworkInfoServiceCocoa.cpp',
+ 'nsNetworkInfoService.cpp',
+ ]
elif CONFIG['OS_ARCH'] == 'Linux':
SOURCES += [
'NetworkInfoServiceLinux.cpp',
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
index 3c6804760a..c1011b1053 100644
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -3039,6 +3039,17 @@ nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(bool aEnabled,
}
return NS_OK;
+#elif defined(XP_DARWIN)
+ // Darwin uses sec; only supports idle time being set.
+ int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE,
+ &aIdleTime, sizeof(aIdleTime));
+ if (NS_WARN_IF(err)) {
+ LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
+ mSocketTransport);
+ return NS_ERROR_UNEXPECTED;
+ }
+ return NS_OK;
+
#elif defined(XP_UNIX)
// Not all *nix OSes support the following setsockopt() options
// build errors will tell us if they are not.
diff --git a/netwerk/base/nsURLHelperOSX.cpp b/netwerk/base/nsURLHelperOSX.cpp
new file mode 100644
index 0000000000..bcc0b257fb
--- /dev/null
+++ b/netwerk/base/nsURLHelperOSX.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 et cindent: */
+/* 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/. */
+
+/* Mac OS X-specific local file uri parsing */
+#include "nsURLHelper.h"
+#include "nsEscape.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include <Carbon/Carbon.h>
+
+static nsTArray<nsCString> *gVolumeList = nullptr;
+
+static bool pathBeginsWithVolName(const nsACString& path, nsACString& firstPathComponent)
+{
+ // Return whether the 1st path component in path (escaped) is equal to the name
+ // of a mounted volume. Return the 1st path component (unescaped) in any case.
+ // This needs to be done as quickly as possible, so we cache a list of volume names.
+ // XXX Register an event handler to detect drives being mounted/unmounted?
+
+ if (!gVolumeList) {
+ gVolumeList = new nsTArray<nsCString>;
+ if (!gVolumeList) {
+ return false; // out of memory
+ }
+ }
+
+ // Cache a list of volume names
+ if (!gVolumeList->Length()) {
+ OSErr err;
+ ItemCount volumeIndex = 1;
+
+ do {
+ HFSUniStr255 volName;
+ FSRef rootDirectory;
+ err = ::FSGetVolumeInfo(0, volumeIndex, nullptr, kFSVolInfoNone, nullptr,
+ &volName, &rootDirectory);
+ if (err == noErr) {
+ NS_ConvertUTF16toUTF8 volNameStr(Substring((char16_t *)volName.unicode,
+ (char16_t *)volName.unicode + volName.length));
+ gVolumeList->AppendElement(volNameStr);
+ volumeIndex++;
+ }
+ } while (err == noErr);
+ }
+
+ // Extract the first component of the path
+ nsACString::const_iterator start;
+ path.BeginReading(start);
+ start.advance(1); // path begins with '/'
+ nsACString::const_iterator directory_end;
+ path.EndReading(directory_end);
+ nsACString::const_iterator component_end(start);
+ FindCharInReadable('/', component_end, directory_end);
+
+ nsAutoCString flatComponent((Substring(start, component_end)));
+ NS_UnescapeURL(flatComponent);
+ int32_t foundIndex = gVolumeList->IndexOf(flatComponent);
+ firstPathComponent = flatComponent;
+ return (foundIndex != -1);
+}
+
+void
+net_ShutdownURLHelperOSX()
+{
+ delete gVolumeList;
+ gVolumeList = nullptr;
+}
+
+static nsresult convertHFSPathtoPOSIX(const nsACString& hfsPath, nsACString& posixPath)
+{
+ // Use CFURL to do the conversion. We don't want to do this by simply
+ // using SwapSlashColon - we need the charset mapped from MacRoman
+ // to UTF-8, and we need "/Volumes" (or whatever - Apple says this is subject to change)
+ // prepended if the path is not on the boot drive.
+
+ CFStringRef pathStrRef = CFStringCreateWithCString(nullptr,
+ PromiseFlatCString(hfsPath).get(),
+ kCFStringEncodingMacRoman);
+ if (!pathStrRef)
+ return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr,
+ pathStrRef, kCFURLHFSPathStyle, true);
+ if (urlRef) {
+ UInt8 pathBuf[PATH_MAX];
+ if (CFURLGetFileSystemRepresentation(urlRef, true, pathBuf, sizeof(pathBuf))) {
+ posixPath = (char *)pathBuf;
+ rv = NS_OK;
+ }
+ }
+ CFRelease(pathStrRef);
+ if (urlRef)
+ CFRelease(urlRef);
+ return rv;
+}
+
+static void SwapSlashColon(char *s)
+{
+ while (*s) {
+ if (*s == '/')
+ *s = ':';
+ else if (*s == ':')
+ *s = '/';
+ s++;
+ }
+}
+
+nsresult
+net_GetURLSpecFromActualFile(nsIFile *aFile, nsACString &result)
+{
+ // NOTE: This is identical to the implementation in nsURLHelperUnix.cpp
+
+ nsresult rv;
+ nsAutoCString ePath;
+
+ // construct URL spec from native file path
+ rv = aFile->GetNativePath(ePath);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString escPath;
+ NS_NAMED_LITERAL_CSTRING(prefix, "file://");
+
+ // Escape the path with the directory mask
+ if (NS_EscapeURL(ePath.get(), ePath.Length(), esc_Directory+esc_Forced, escPath))
+ escPath.Insert(prefix, 0);
+ else
+ escPath.Assign(prefix + ePath);
+
+ // esc_Directory does not escape the semicolons, so if a filename
+ // contains semicolons we need to manually escape them.
+ // This replacement should be removed in bug #473280
+ escPath.ReplaceSubstring(";", "%3b");
+
+ result = escPath;
+ return NS_OK;
+}
+
+nsresult
+net_GetFileFromURLSpec(const nsACString &aURL, nsIFile **result)
+{
+ // NOTE: See also the implementation in nsURLHelperUnix.cpp
+ // This matches it except for the HFS path handling.
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localFile));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString directory, fileBaseName, fileExtension, path;
+ bool bHFSPath = false;
+
+ rv = net_ParseFileURL(aURL, directory, fileBaseName, fileExtension);
+ if (NS_FAILED(rv))
+ return rv;
+
+ if (!directory.IsEmpty()) {
+ NS_EscapeURL(directory, esc_Directory|esc_AlwaysCopy, path);
+
+ // The canonical form of file URLs on OSX use POSIX paths:
+ // file:///path-name.
+ // But, we still encounter file URLs that use HFS paths:
+ // file:///volume-name/path-name
+ // Determine that here and normalize HFS paths to POSIX.
+ nsAutoCString possibleVolName;
+ if (pathBeginsWithVolName(directory, possibleVolName)) {
+ // Though we know it begins with a volume name, it could still
+ // be a valid POSIX path if the boot drive is named "Mac HD"
+ // and there is a directory "Mac HD" at its root. If such a
+ // directory doesn't exist, we'll assume this is an HFS path.
+ FSRef testRef;
+ possibleVolName.Insert("/", 0);
+ if (::FSPathMakeRef((UInt8*)possibleVolName.get(), &testRef, nullptr) != noErr)
+ bHFSPath = true;
+ }
+
+ if (bHFSPath) {
+ // "%2F"s need to become slashes, while all other slashes need to
+ // become colons. If we start out by changing "%2F"s to colons, we
+ // can reply on SwapSlashColon() to do what we need
+ path.ReplaceSubstring("%2F", ":");
+ path.Cut(0, 1); // directory begins with '/'
+ SwapSlashColon((char *)path.get());
+ // At this point, path is an HFS path made using the same
+ // algorithm as nsURLHelperMac. We'll convert to POSIX below.
+ }
+ }
+ if (!fileBaseName.IsEmpty())
+ NS_EscapeURL(fileBaseName, esc_FileBaseName|esc_AlwaysCopy, path);
+ if (!fileExtension.IsEmpty()) {
+ path += '.';
+ NS_EscapeURL(fileExtension, esc_FileExtension|esc_AlwaysCopy, path);
+ }
+
+ NS_UnescapeURL(path);
+ if (path.Length() != strlen(path.get()))
+ return NS_ERROR_FILE_INVALID_PATH;
+
+ if (bHFSPath)
+ convertHFSPathtoPOSIX(path, path);
+
+ // assuming path is encoded in the native charset
+ rv = localFile->InitWithNativePath(path);
+ if (NS_FAILED(rv))
+ return rv;
+
+ localFile.forget(result);
+ return NS_OK;
+}
diff --git a/netwerk/build/moz.build b/netwerk/build/moz.build
index 7c8416b9ac..ebafda48bb 100644
--- a/netwerk/build/moz.build
+++ b/netwerk/build/moz.build
@@ -36,7 +36,12 @@ if CONFIG['OS_ARCH'] == 'WINNT':
'/netwerk/system/win32',
]
-if CONFIG['OS_ARCH'] == 'Linux':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/netwerk/system/mac',
+ ]
+
+elif CONFIG['OS_ARCH'] == 'Linux':
LOCAL_INCLUDES += [
'/netwerk/system/linux',
]
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
new file mode 100644
index 0000000000..72b5577749
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
@@ -0,0 +1,779 @@
+/* -*- Mode: C++; tab-width: 2; 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 "MDNSResponderOperator.h"
+#include "MDNSResponderReply.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsDNSServiceInfo.h"
+#include "nsHashPropertyBag.h"
+#include "nsIProperty.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIVariant.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetAddr.h"
+#include "nsNetCID.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCID.h"
+#include "private/pprio.h"
+
+#include "nsASocketHandler.h"
+
+namespace mozilla {
+namespace net {
+
+static LazyLogModule gMDNSLog("MDNSResponderOperator");
+#undef LOG_I
+#define LOG_I(...) MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) MOZ_LOG(mozilla::net::gMDNSLog, mozilla::LogLevel::Error, (__VA_ARGS__))
+
+class MDNSResponderOperator::ServiceWatcher final
+ : public nsASocketHandler
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsASocketHandler methods
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(fd == mFD);
+
+ if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) {
+ LOG_E("error polling on listening socket (%p)", fd);
+ mCondition = NS_ERROR_UNEXPECTED;
+ }
+
+ if (!(outFlags & PR_POLL_READ)) {
+ return;
+ }
+
+ DNSServiceProcessResult(mService);
+ }
+
+ virtual void OnSocketDetached(PRFileDesc *fd) override
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+ MOZ_ASSERT(mThread);
+ MOZ_ASSERT(fd == mFD);
+
+ if (!mFD) {
+ return;
+ }
+
+ // Bug 1175387: do not double close the handle here.
+ PR_ChangeFileDescNativeHandle(mFD, -1);
+ PR_Close(mFD);
+ mFD = nullptr;
+
+ mThread->Dispatch(NewRunnableMethod(this, &ServiceWatcher::Deallocate),
+ NS_DISPATCH_NORMAL);
+ }
+
+ virtual void IsLocal(bool *aIsLocal) override { *aIsLocal = true; }
+
+ virtual void KeepWhenOffline(bool *aKeepWhenOffline) override
+ {
+ *aKeepWhenOffline = true;
+ }
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+
+ explicit ServiceWatcher(DNSServiceRef aService,
+ MDNSResponderOperator* aOperator)
+ : mThread(nullptr)
+ , mSts(nullptr)
+ , mOperatorHolder(aOperator)
+ , mService(aService)
+ , mFD(nullptr)
+ , mAttached(false)
+ {
+ if (!gSocketTransportService)
+ {
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ }
+ }
+
+ nsresult Init()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
+ mThread = NS_GetCurrentThread();
+
+ if (!mService) {
+ return NS_OK;
+ }
+
+ if (!gSocketTransportService) {
+ return NS_ERROR_FAILURE;
+ }
+ mSts = gSocketTransportService;
+
+ int osfd = DNSServiceRefSockFD(mService);
+ if (osfd == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mFD = PR_ImportFile(osfd);
+ return PostEvent(&ServiceWatcher::OnMsgAttach);
+ }
+
+ void Close()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
+
+ if (!gSocketTransportService) {
+ Deallocate();
+ return;
+ }
+
+ PostEvent(&ServiceWatcher::OnMsgClose);
+ }
+
+private:
+ ~ServiceWatcher() = default;
+
+ void Deallocate()
+ {
+ if (mService) {
+ DNSServiceRefDeallocate(mService);
+ mService = nullptr;
+ }
+ mOperatorHolder = nullptr;
+ }
+
+ nsresult PostEvent(void(ServiceWatcher::*func)(void))
+ {
+ return gSocketTransportService->Dispatch(NewRunnableMethod(this, func),
+ NS_DISPATCH_NORMAL);
+ }
+
+ void OnMsgClose()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (NS_FAILED(mCondition)) {
+ return;
+ }
+
+ // tear down socket. this signals the STS to detach our socket handler.
+ mCondition = NS_BINDING_ABORTED;
+
+ // if we are attached, then socket transport service will call our
+ // OnSocketDetached method automatically. Otherwise, we have to call it
+ // (and thus close the socket) manually.
+ if (!mAttached) {
+ OnSocketDetached(mFD);
+ }
+ }
+
+ void OnMsgAttach()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ if (NS_FAILED(mCondition)) {
+ return;
+ }
+
+ mCondition = TryAttach();
+
+ // if we hit an error while trying to attach then bail...
+ if (NS_FAILED(mCondition)) {
+ NS_ASSERTION(!mAttached, "should not be attached already");
+ OnSocketDetached(mFD);
+ }
+
+ }
+
+ nsresult TryAttach()
+ {
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ nsresult rv;
+
+ if (!gSocketTransportService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ //
+ // find out if it is going to be ok to attach another socket to the STS.
+ // if not then we have to wait for the STS to tell us that it is ok.
+ // the notification is asynchronous, which means that when we could be
+ // in a race to call AttachSocket once notified. for this reason, when
+ // we get notified, we just re-enter this function. as a result, we are
+ // sure to ask again before calling AttachSocket. in this way we deal
+ // with the race condition. though it isn't the most elegant solution,
+ // it is far simpler than trying to build a system that would guarantee
+ // FIFO ordering (which wouldn't even be that valuable IMO). see bug
+ // 194402 for more info.
+ //
+ if (!gSocketTransportService->CanAttachSocket()) {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod(this, &ServiceWatcher::OnMsgAttach);
+
+ nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ //
+ // ok, we can now attach our socket to the STS for polling
+ //
+ rv = gSocketTransportService->AttachSocket(mFD, this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mAttached = true;
+
+ //
+ // now, configure our poll flags for listening...
+ //
+ mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIThread> mThread;
+ RefPtr<nsSocketTransportService> mSts;
+ RefPtr<MDNSResponderOperator> mOperatorHolder;
+ DNSServiceRef mService;
+ PRFileDesc* mFD;
+ bool mAttached;
+};
+
+NS_IMPL_ISUPPORTS(MDNSResponderOperator::ServiceWatcher, nsISupports)
+
+MDNSResponderOperator::MDNSResponderOperator()
+ : mService(nullptr)
+ , mWatcher(nullptr)
+ , mThread(NS_GetCurrentThread())
+ , mIsCancelled(false)
+{
+}
+
+MDNSResponderOperator::~MDNSResponderOperator()
+{
+ Stop();
+}
+
+nsresult
+MDNSResponderOperator::Start()
+{
+ if (mIsCancelled) {
+ return NS_OK;
+ }
+
+ if (IsServing()) {
+ Stop();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+MDNSResponderOperator::Stop()
+{
+ return ResetService(nullptr);
+}
+
+nsresult
+MDNSResponderOperator::ResetService(DNSServiceRef aService)
+{
+ nsresult rv;
+
+ if (aService != mService) {
+ if (mWatcher) {
+ mWatcher->Close();
+ mWatcher = nullptr;
+ }
+
+ if (aService) {
+ RefPtr<ServiceWatcher> watcher = new ServiceWatcher(aService, this);
+ if (NS_WARN_IF(NS_FAILED(rv = watcher->Init()))) {
+ return rv;
+ }
+ mWatcher = watcher;
+ }
+
+ mService = aService;
+ }
+ return NS_OK;
+}
+
+BrowseOperator::BrowseOperator(const nsACString& aServiceType,
+ nsIDNSServiceDiscoveryListener* aListener)
+ : MDNSResponderOperator()
+ , mServiceType(aServiceType)
+ , mListener(aListener)
+{
+}
+
+nsresult
+BrowseOperator::Start()
+{
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+ return rv;
+ }
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err = DNSServiceBrowse(&service,
+ 0,
+ kDNSServiceInterfaceIndexAny,
+ mServiceType.get(),
+ nullptr,
+ &BrowseReplyRunnable::Reply,
+ this);
+ NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err, "DNSServiceBrowse fail");
+
+ if (mListener) {
+ if (kDNSServiceErr_NoError == err) {
+ mListener->OnDiscoveryStarted(mServiceType);
+ } else {
+ mListener->OnStartDiscoveryFailed(mServiceType, err);
+ }
+ }
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+nsresult
+BrowseOperator::Stop()
+{
+ bool isServing = IsServing();
+ nsresult rv = MDNSResponderOperator::Stop();
+
+ if (isServing && mListener) {
+ if (NS_SUCCEEDED(rv)) {
+ mListener->OnDiscoveryStopped(mServiceType);
+ } else {
+ mListener->OnStopDiscoveryFailed(mServiceType,
+ static_cast<uint32_t>(rv));
+ }
+ }
+
+ return rv;
+}
+
+void
+BrowseOperator::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aServiceName,
+ const nsACString& aRegType,
+ const nsACString& aReplyDomain)
+{
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+ LOG_E("BrowseOperator::Reply (%d)", aErrorCode);
+ if (mListener) {
+ mListener->OnStartDiscoveryFailed(mServiceType, aErrorCode);
+ }
+ return;
+ }
+
+ if (!mListener) { return; }
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo();
+
+ if (NS_WARN_IF(!info)) { return; }
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aServiceName)))) { return; }
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { return; }
+ if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aReplyDomain)))) { return; }
+
+ if (aFlags & kDNSServiceFlagsAdd) {
+ mListener->OnServiceFound(info);
+ } else {
+ mListener->OnServiceLost(info);
+ }
+}
+
+RegisterOperator::RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSRegistrationListener* aListener)
+ : MDNSResponderOperator()
+ , mServiceInfo(aServiceInfo)
+ , mListener(aListener)
+{
+}
+
+nsresult
+RegisterOperator::Start()
+{
+ nsresult rv;
+
+ rv = MDNSResponderOperator::Start();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ uint16_t port;
+ if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetPort(&port)))) {
+ return rv;
+ }
+ nsAutoCString type;
+ if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetServiceType(type)))) {
+ return rv;
+ }
+
+ TXTRecordRef txtRecord;
+ char buf[TXT_BUFFER_SIZE] = { 0 };
+ TXTRecordCreate(&txtRecord, TXT_BUFFER_SIZE, buf);
+
+ nsCOMPtr<nsIPropertyBag2> attributes;
+ if (NS_FAILED(rv = mServiceInfo->GetAttributes(getter_AddRefs(attributes)))) {
+ LOG_I("register: no attributes");
+ } else {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ if (NS_WARN_IF(NS_FAILED(rv =
+ attributes->GetEnumerator(getter_AddRefs(enumerator))))) {
+ return rv;
+ }
+
+ bool hasMoreElements;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+ hasMoreElements) {
+ nsCOMPtr<nsISupports> element;
+ MOZ_ALWAYS_SUCCEEDS(enumerator->GetNext(getter_AddRefs(element)));
+ nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
+ MOZ_ASSERT(property);
+
+ nsAutoString name;
+ nsCOMPtr<nsIVariant> value;
+ MOZ_ALWAYS_SUCCEEDS(property->GetName(name));
+ MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value)));
+
+ nsAutoCString str;
+ if (NS_WARN_IF(NS_FAILED(value->GetAsACString(str)))) {
+ continue;
+ }
+
+ TXTRecordSetValue(&txtRecord,
+ /* it's safe because key name is ASCII only. */
+ NS_LossyConvertUTF16toASCII(name).get(),
+ str.Length(),
+ str.get());
+ }
+ }
+
+ nsAutoCString host;
+ nsAutoCString name;
+ nsAutoCString domain;
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err =
+ DNSServiceRegister(&service,
+ 0,
+ 0,
+ NS_SUCCEEDED(mServiceInfo->GetServiceName(name)) ?
+ name.get() : nullptr,
+ type.get(),
+ NS_SUCCEEDED(mServiceInfo->GetDomainName(domain)) ?
+ domain.get() : nullptr,
+ NS_SUCCEEDED(mServiceInfo->GetHost(host)) ?
+ host.get() : nullptr,
+ NativeEndian::swapToNetworkOrder(port),
+ TXTRecordGetLength(&txtRecord),
+ TXTRecordGetBytesPtr(&txtRecord),
+ &RegisterReplyRunnable::Reply,
+ this);
+ NS_WARNING_ASSERTION(kDNSServiceErr_NoError == err,
+ "DNSServiceRegister fail");
+
+ TXTRecordDeallocate(&txtRecord);
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ if (mListener) {
+ mListener->OnRegistrationFailed(mServiceInfo, err);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+nsresult
+RegisterOperator::Stop()
+{
+ bool isServing = IsServing();
+ nsresult rv = MDNSResponderOperator::Stop();
+
+ if (isServing && mListener) {
+ if (NS_SUCCEEDED(rv)) {
+ mListener->OnServiceUnregistered(mServiceInfo);
+ } else {
+ mListener->OnUnregistrationFailed(mServiceInfo,
+ static_cast<uint32_t>(rv));
+ }
+ }
+
+ return rv;
+}
+
+void
+RegisterOperator::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aName,
+ const nsACString& aRegType,
+ const nsACString& aDomain)
+{
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ if (kDNSServiceErr_NoError != aErrorCode) {
+ LOG_E("RegisterOperator::Reply (%d)", aErrorCode);
+ }
+
+ if (!mListener) { return; }
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aName)))) { return; }
+ if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { return; }
+ if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aDomain)))) { return; }
+
+ if (kDNSServiceErr_NoError == aErrorCode) {
+ if (aFlags & kDNSServiceFlagsAdd) {
+ mListener->OnServiceRegistered(info);
+ } else {
+ // If a successfully-registered name later suffers a name conflict
+ // or similar problem and has to be deregistered, the callback will
+ // be invoked with the kDNSServiceFlagsAdd flag not set.
+ LOG_E("RegisterOperator::Reply: deregister");
+ if (NS_WARN_IF(NS_FAILED(Stop()))) {
+ return;
+ }
+ }
+ } else {
+ mListener->OnRegistrationFailed(info, aErrorCode);
+ }
+}
+
+ResolveOperator::ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener)
+ : MDNSResponderOperator()
+ , mServiceInfo(aServiceInfo)
+ , mListener(aListener)
+{
+}
+
+nsresult
+ResolveOperator::Start()
+{
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+ return rv;
+ }
+
+ nsAutoCString name;
+ mServiceInfo->GetServiceName(name);
+ nsAutoCString type;
+ mServiceInfo->GetServiceType(type);
+ nsAutoCString domain;
+ mServiceInfo->GetDomainName(domain);
+
+ LOG_I("Resolve: (%s), (%s), (%s)", name.get(), type.get(), domain.get());
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err =
+ DNSServiceResolve(&service,
+ 0,
+ kDNSServiceInterfaceIndexAny,
+ name.get(),
+ type.get(),
+ domain.get(),
+ (DNSServiceResolveReply)&ResolveReplyRunnable::Reply,
+ this);
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ if (mListener) {
+ mListener->OnResolveFailed(mServiceInfo, err);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+void
+ResolveOperator::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aFullName,
+ const nsACString& aHostTarget,
+ uint16_t aPort,
+ uint16_t aTxtLen,
+ const unsigned char* aTxtRecord)
+{
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ auto guard = MakeScopeExit([&] {
+ Unused << NS_WARN_IF(NS_FAILED(Stop()));
+ });
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+ LOG_E("ResolveOperator::Reply (%d)", aErrorCode);
+ return;
+ }
+
+ // Resolve TXT record
+ int count = TXTRecordGetCount(aTxtLen, aTxtRecord);
+ LOG_I("resolve: txt count = %d, len = %d", count, aTxtLen);
+ nsCOMPtr<nsIWritablePropertyBag2> attributes = new nsHashPropertyBag();
+ if (NS_WARN_IF(!attributes)) {
+ return;
+ }
+ if (count) {
+ for (int i = 0; i < count; ++i) {
+ char key[TXT_BUFFER_SIZE] = { '\0' };
+ uint8_t vSize = 0;
+ const void* value = nullptr;
+ if (kDNSServiceErr_NoError !=
+ TXTRecordGetItemAtIndex(aTxtLen,
+ aTxtRecord,
+ i,
+ TXT_BUFFER_SIZE,
+ key,
+ &vSize,
+ &value)) {
+ break;
+ }
+
+ nsAutoCString str(reinterpret_cast<const char*>(value), vSize);
+ LOG_I("resolve TXT: (%d) %s=%s", vSize, key, str.get());
+
+ if (NS_WARN_IF(NS_FAILED(attributes->SetPropertyAsACString(
+ /* it's safe to convert because key name is ASCII only. */
+ NS_ConvertASCIItoUTF16(key),
+ str)))) {
+ break;
+ }
+ }
+ }
+
+ if (!mListener) { return; }
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+ if (NS_WARN_IF(NS_FAILED(info->SetHost(aHostTarget)))) { return; }
+ if (NS_WARN_IF(NS_FAILED(info->SetPort(aPort)))) { return; }
+ if (NS_WARN_IF(NS_FAILED(info->SetAttributes(attributes)))) { return; }
+
+ if (kDNSServiceErr_NoError == aErrorCode) {
+ GetAddrInfor(info);
+ }
+ else {
+ mListener->OnResolveFailed(info, aErrorCode);
+ Unused << NS_WARN_IF(NS_FAILED(Stop()));
+ }
+}
+
+void
+ResolveOperator::GetAddrInfor(nsIDNSServiceInfo* aServiceInfo)
+{
+ RefPtr<GetAddrInfoOperator> getAddreOp = new GetAddrInfoOperator(aServiceInfo,
+ mListener);
+ Unused << NS_WARN_IF(NS_FAILED(getAddreOp->Start()));
+}
+
+GetAddrInfoOperator::GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener)
+ : MDNSResponderOperator()
+ , mServiceInfo(aServiceInfo)
+ , mListener(aListener)
+{
+}
+
+nsresult
+GetAddrInfoOperator::Start()
+{
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+ return rv;
+ }
+
+ nsAutoCString host;
+ mServiceInfo->GetHost(host);
+
+ LOG_I("GetAddrInfo: (%s)", host.get());
+
+ DNSServiceRef service = nullptr;
+ DNSServiceErrorType err =
+ DNSServiceGetAddrInfo(&service,
+ kDNSServiceFlagsForceMulticast,
+ kDNSServiceInterfaceIndexAny,
+ kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6,
+ host.get(),
+ (DNSServiceGetAddrInfoReply)&GetAddrInfoReplyRunnable::Reply,
+ this);
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+ if (mListener) {
+ mListener->OnResolveFailed(mServiceInfo, err);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ return ResetService(service);
+}
+
+void
+GetAddrInfoOperator::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aHostName,
+ const NetAddr& aAddress,
+ uint32_t aTTL)
+{
+ MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+ auto guard = MakeScopeExit([&] {
+ Unused << NS_WARN_IF(NS_FAILED(Stop()));
+ });
+
+ if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+ LOG_E("GetAddrInfoOperator::Reply (%d)", aErrorCode);
+ return;
+ }
+
+ if (!mListener) { return; }
+
+ NetAddr addr = aAddress;
+ nsCOMPtr<nsINetAddr> address = new nsNetAddr(&addr);
+ nsCString addressStr;
+ if (NS_WARN_IF(NS_FAILED(address->GetAddress(addressStr)))) { return; }
+
+ nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+ if (NS_WARN_IF(NS_FAILED(info->SetAddress(addressStr)))) { return; }
+
+ /**
+ * |kDNSServiceFlagsMoreComing| means this callback will be one or more
+ * callback events later, so this instance should be kept alive until all
+ * follow-up events are processed.
+ */
+ if (aFlags & kDNSServiceFlagsMoreComing) {
+ guard.release();
+ }
+
+ if (kDNSServiceErr_NoError == aErrorCode) {
+ mListener->OnServiceResolved(info);
+ } else {
+ mListener->OnResolveFailed(info, aErrorCode);
+ }
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h
new file mode 100644
index 0000000000..a932baa7cc
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
+#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
+
+#include "dns_sd.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIDNSServiceDiscovery.h"
+#include "nsIThread.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class MDNSResponderOperator
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MDNSResponderOperator)
+
+public:
+ MDNSResponderOperator();
+
+ virtual nsresult Start();
+ virtual nsresult Stop();
+ void Cancel() { mIsCancelled = true; }
+ nsIThread* GetThread() const { return mThread; }
+
+protected:
+ virtual ~MDNSResponderOperator();
+
+ bool IsServing() const { return mService; }
+ nsresult ResetService(DNSServiceRef aService);
+
+private:
+ class ServiceWatcher;
+
+ DNSServiceRef mService;
+ RefPtr<ServiceWatcher> mWatcher;
+ nsCOMPtr<nsIThread> mThread; // remember caller thread for callback
+ Atomic<bool> mIsCancelled;
+};
+
+class BrowseOperator final : public MDNSResponderOperator
+{
+public:
+ BrowseOperator(const nsACString& aServiceType,
+ nsIDNSServiceDiscoveryListener* aListener);
+
+ nsresult Start() override;
+ nsresult Stop() override;
+
+ void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aServiceName,
+ const nsACString& aRegType,
+ const nsACString& aReplyDomain);
+
+private:
+ ~BrowseOperator() = default;
+
+ nsCString mServiceType;
+ nsCOMPtr<nsIDNSServiceDiscoveryListener> mListener;
+};
+
+class RegisterOperator final : public MDNSResponderOperator
+{
+ enum { TXT_BUFFER_SIZE = 256 };
+
+public:
+ RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSRegistrationListener* aListener);
+
+ nsresult Start() override;
+ nsresult Stop() override;
+
+ void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aName,
+ const nsACString& aRegType,
+ const nsACString& aDomain);
+
+private:
+ ~RegisterOperator() = default;
+
+ nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+ nsCOMPtr<nsIDNSRegistrationListener> mListener;
+};
+
+class ResolveOperator final : public MDNSResponderOperator
+{
+ enum { TXT_BUFFER_SIZE = 256 };
+
+public:
+ ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener);
+
+ nsresult Start() override;
+
+ void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aFullName,
+ const nsACString& aHostTarget,
+ uint16_t aPort,
+ uint16_t aTxtLen,
+ const unsigned char* aTxtRecord);
+
+private:
+ ~ResolveOperator() = default;
+ void GetAddrInfor(nsIDNSServiceInfo* aServiceInfo);
+
+ nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+ nsCOMPtr<nsIDNSServiceResolveListener> mListener;
+};
+
+union NetAddr;
+
+class GetAddrInfoOperator final : public MDNSResponderOperator
+{
+public:
+ GetAddrInfoOperator(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener);
+
+ nsresult Start() override;
+
+ void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aHostName,
+ const NetAddr& aAddress,
+ uint32_t aTTL);
+
+private:
+ ~GetAddrInfoOperator() = default;
+
+ nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+ nsCOMPtr<nsIDNSServiceResolveListener> mListener;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp
new file mode 100644
index 0000000000..7aa5b3759a
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp
@@ -0,0 +1,302 @@
+/* -*- Mode: C++; tab-width: 2; 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 "MDNSResponderReply.h"
+#include "mozilla/EndianUtils.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+BrowseReplyRunnable::BrowseReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aServiceName,
+ const nsACString& aRegType,
+ const nsACString& aReplyDomain,
+ BrowseOperator* aContext)
+ : mSdRef(aSdRef)
+ , mFlags(aFlags)
+ , mInterfaceIndex(aInterfaceIndex)
+ , mErrorCode(aErrorCode)
+ , mServiceName(aServiceName)
+ , mRegType(aRegType)
+ , mReplyDomain(aReplyDomain)
+ , mContext(aContext)
+{
+}
+
+NS_IMETHODIMP
+BrowseReplyRunnable::Run()
+{
+ MOZ_ASSERT(mContext);
+ mContext->Reply(mSdRef,
+ mFlags,
+ mInterfaceIndex,
+ mErrorCode,
+ mServiceName,
+ mRegType,
+ mReplyDomain);
+ return NS_OK;
+}
+
+void
+BrowseReplyRunnable::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aServiceName,
+ const char* aRegType,
+ const char* aReplyDomain,
+ void* aContext)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ BrowseOperator* obj(reinterpret_cast<BrowseOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ thread->Dispatch(new BrowseReplyRunnable(aSdRef,
+ aFlags,
+ aInterfaceIndex,
+ aErrorCode,
+ nsCString(aServiceName),
+ nsCString(aRegType),
+ nsCString(aReplyDomain),
+ obj),
+ NS_DISPATCH_NORMAL);
+}
+
+RegisterReplyRunnable::RegisterReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aName,
+ const nsACString& aRegType,
+ const nsACString& domain,
+ RegisterOperator* aContext)
+ : mSdRef(aSdRef)
+ , mFlags(aFlags)
+ , mErrorCode(aErrorCode)
+ , mName(aName)
+ , mRegType(aRegType)
+ , mDomain(domain)
+ , mContext(aContext)
+{
+}
+
+NS_IMETHODIMP
+RegisterReplyRunnable::Run()
+{
+ MOZ_ASSERT(mContext);
+
+ mContext->Reply(mSdRef,
+ mFlags,
+ mErrorCode,
+ mName,
+ mRegType,
+ mDomain);
+ return NS_OK;
+}
+
+void
+RegisterReplyRunnable::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const char* aName,
+ const char* aRegType,
+ const char* domain,
+ void* aContext)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ RegisterOperator* obj(reinterpret_cast<RegisterOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ thread->Dispatch(new RegisterReplyRunnable(aSdRef,
+ aFlags,
+ aErrorCode,
+ nsCString(aName),
+ nsCString(aRegType),
+ nsCString(domain),
+ obj),
+ NS_DISPATCH_NORMAL);
+}
+
+ResolveReplyRunnable::ResolveReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aFullName,
+ const nsACString& aHostTarget,
+ uint16_t aPort,
+ uint16_t aTxtLen,
+ const unsigned char* aTxtRecord,
+ ResolveOperator* aContext)
+ : mSdRef(aSdRef)
+ , mFlags(aFlags)
+ , mInterfaceIndex(aInterfaceIndex)
+ , mErrorCode(aErrorCode)
+ , mFullname(aFullName)
+ , mHosttarget(aHostTarget)
+ , mPort(aPort)
+ , mTxtLen(aTxtLen)
+ , mTxtRecord(new unsigned char[aTxtLen])
+ , mContext(aContext)
+{
+ if (mTxtRecord) {
+ memcpy(mTxtRecord.get(), aTxtRecord, aTxtLen);
+ }
+}
+
+ResolveReplyRunnable::~ResolveReplyRunnable()
+{
+}
+
+NS_IMETHODIMP
+ResolveReplyRunnable::Run()
+{
+ MOZ_ASSERT(mContext);
+ mContext->Reply(mSdRef,
+ mFlags,
+ mInterfaceIndex,
+ mErrorCode,
+ mFullname,
+ mHosttarget,
+ mPort,
+ mTxtLen,
+ mTxtRecord.get());
+ return NS_OK;
+}
+
+void
+ResolveReplyRunnable::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aFullName,
+ const char* aHostTarget,
+ uint16_t aPort,
+ uint16_t aTxtLen,
+ const unsigned char* aTxtRecord,
+ void* aContext)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ ResolveOperator* obj(reinterpret_cast<ResolveOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ thread->Dispatch(new ResolveReplyRunnable(aSdRef,
+ aFlags,
+ aInterfaceIndex,
+ aErrorCode,
+ nsCString(aFullName),
+ nsCString(aHostTarget),
+ NativeEndian::swapFromNetworkOrder(aPort),
+ aTxtLen,
+ aTxtRecord,
+ obj),
+ NS_DISPATCH_NORMAL);
+}
+
+GetAddrInfoReplyRunnable::GetAddrInfoReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aHostName,
+ const mozilla::net::NetAddr& aAddress,
+ uint32_t aTTL,
+ GetAddrInfoOperator* aContext)
+ : mSdRef(aSdRef)
+ , mFlags(aFlags)
+ , mInterfaceIndex(aInterfaceIndex)
+ , mErrorCode(aErrorCode)
+ , mHostName(aHostName)
+ , mAddress(aAddress)
+ , mTTL(aTTL)
+ , mContext(aContext)
+{
+}
+
+GetAddrInfoReplyRunnable::~GetAddrInfoReplyRunnable()
+{
+}
+
+NS_IMETHODIMP
+GetAddrInfoReplyRunnable::Run()
+{
+ MOZ_ASSERT(mContext);
+ mContext->Reply(mSdRef,
+ mFlags,
+ mInterfaceIndex,
+ mErrorCode,
+ mHostName,
+ mAddress,
+ mTTL);
+ return NS_OK;
+}
+
+void
+GetAddrInfoReplyRunnable::Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aHostName,
+ const struct sockaddr* aAddress,
+ uint32_t aTTL,
+ void* aContext)
+{
+ MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+ GetAddrInfoOperator* obj(reinterpret_cast<GetAddrInfoOperator*>(aContext));
+ if (!obj) {
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread(obj->GetThread());
+ if (!thread) {
+ return;
+ }
+
+ NetAddr address;
+ address.raw.family = aAddress->sa_family;
+
+ static_assert(sizeof(address.raw.data) >= sizeof(aAddress->sa_data),
+ "size of sockaddr.sa_data is too big");
+ memcpy(&address.raw.data, aAddress->sa_data, sizeof(aAddress->sa_data));
+
+ thread->Dispatch(new GetAddrInfoReplyRunnable(aSdRef,
+ aFlags,
+ aInterfaceIndex,
+ aErrorCode,
+ nsCString(aHostName),
+ address,
+ aTTL,
+ obj),
+ NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/mdns/libmdns/MDNSResponderReply.h b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h
new file mode 100644
index 0000000000..794a585f80
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
+#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
+
+#include "dns_sd.h"
+#include "MDNSResponderOperator.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIThread.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+class BrowseReplyRunnable final : public Runnable
+{
+public:
+ BrowseReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aServiceName,
+ const nsACString& aRegType,
+ const nsACString& aReplyDomain,
+ BrowseOperator* aContext);
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aServiceName,
+ const char* aRegType,
+ const char* aReplyDomain,
+ void* aContext);
+
+private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ uint32_t mInterfaceIndex;
+ DNSServiceErrorType mErrorCode;
+ nsCString mServiceName;
+ nsCString mRegType;
+ nsCString mReplyDomain;
+ RefPtr<BrowseOperator> mContext;
+};
+
+class RegisterReplyRunnable final : public Runnable
+{
+public:
+ RegisterReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aName,
+ const nsACString& aRegType,
+ const nsACString& aDomain,
+ RegisterOperator* aContext);
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ DNSServiceErrorType aErrorCode,
+ const char* aName,
+ const char* aRegType,
+ const char* aDomain,
+ void* aContext);
+
+private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ DNSServiceErrorType mErrorCode;
+ nsCString mName;
+ nsCString mRegType;
+ nsCString mDomain;
+ RefPtr<RegisterOperator> mContext;
+};
+
+class ResolveReplyRunnable final : public Runnable
+{
+public:
+ ResolveReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aFullName,
+ const nsACString& aHostTarget,
+ uint16_t aPort,
+ uint16_t aTxtLen,
+ const unsigned char* aTxtRecord,
+ ResolveOperator* aContext);
+ ~ResolveReplyRunnable();
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aFullName,
+ const char* aHostTarget,
+ uint16_t aPort,
+ uint16_t aTxtLen,
+ const unsigned char* aTxtRecord,
+ void* aContext);
+
+private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ uint32_t mInterfaceIndex;
+ DNSServiceErrorType mErrorCode;
+ nsCString mFullname;
+ nsCString mHosttarget;
+ uint16_t mPort;
+ uint16_t mTxtLen;
+ UniquePtr<unsigned char> mTxtRecord;
+ RefPtr<ResolveOperator> mContext;
+};
+
+class GetAddrInfoReplyRunnable final : public Runnable
+{
+public:
+ GetAddrInfoReplyRunnable(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const nsACString& aHostName,
+ const mozilla::net::NetAddr& aAddress,
+ uint32_t aTTL,
+ GetAddrInfoOperator* aContext);
+ ~GetAddrInfoReplyRunnable();
+
+ NS_IMETHOD Run() override;
+
+ static void Reply(DNSServiceRef aSdRef,
+ DNSServiceFlags aFlags,
+ uint32_t aInterfaceIndex,
+ DNSServiceErrorType aErrorCode,
+ const char* aHostName,
+ const struct sockaddr* aAddress,
+ uint32_t aTTL,
+ void* aContext);
+
+private:
+ DNSServiceRef mSdRef;
+ DNSServiceFlags mFlags;
+ uint32_t mInterfaceIndex;
+ DNSServiceErrorType mErrorCode;
+ nsCString mHostName;
+ mozilla::net::NetAddr mAddress;
+ uint32_t mTTL;
+ RefPtr<GetAddrInfoOperator> mContext;
+};
+
+} // namespace net
+} // namespace mozilla
+
+ #endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
diff --git a/netwerk/dns/mdns/libmdns/moz.build b/netwerk/dns/mdns/libmdns/moz.build
index 5a67c06118..23445756c6 100644
--- a/netwerk/dns/mdns/libmdns/moz.build
+++ b/netwerk/dns/mdns/libmdns/moz.build
@@ -3,20 +3,32 @@
# 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/.
-EXTRA_COMPONENTS += [
- 'nsDNSServiceDiscovery.js',
- 'nsDNSServiceDiscovery.manifest',
-]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'MDNSResponderOperator.cpp',
+ 'MDNSResponderReply.cpp',
+ 'nsDNSServiceDiscovery.cpp',
+ ]
-EXTRA_JS_MODULES += [
- 'fallback/DataReader.jsm',
- 'fallback/DataWriter.jsm',
- 'fallback/DNSPacket.jsm',
- 'fallback/DNSRecord.jsm',
- 'fallback/DNSResourceRecord.jsm',
- 'fallback/DNSTypes.jsm',
- 'fallback/MulticastDNS.jsm',
-]
+ LOCAL_INCLUDES += [
+ '/netwerk/base',
+ ]
+
+else:
+ EXTRA_COMPONENTS += [
+ 'nsDNSServiceDiscovery.js',
+ 'nsDNSServiceDiscovery.manifest',
+ ]
+
+ EXTRA_JS_MODULES += [
+ 'fallback/DataReader.jsm',
+ 'fallback/DataWriter.jsm',
+ 'fallback/DNSPacket.jsm',
+ 'fallback/DNSRecord.jsm',
+ 'fallback/DNSResourceRecord.jsm',
+ 'fallback/DNSTypes.jsm',
+ 'fallback/MulticastDNS.jsm',
+ ]
SOURCES += [
'nsDNSServiceInfo.cpp',
diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp
new file mode 100644
index 0000000000..cec8033d18
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsDNSServiceDiscovery.h"
+#include "MDNSResponderOperator.h"
+#include "nsICancelable.h"
+#include "nsXULAppAPI.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+inline void
+StartService()
+{
+ /*** STUB ***/
+}
+
+inline void
+StopService()
+{
+ /*** STUB ***/
+}
+
+class ServiceCounter
+{
+public:
+ static bool IsServiceRunning()
+ {
+ return !!sUseCount;
+ }
+
+private:
+ static uint32_t sUseCount;
+
+protected:
+ ServiceCounter()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!sUseCount++) {
+ StartService();
+ }
+ }
+
+ virtual ~ServiceCounter()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!--sUseCount) {
+ StopService();
+ }
+ }
+};
+
+uint32_t ServiceCounter::sUseCount = 0;
+
+class DiscoveryRequest final : public nsICancelable
+ , private ServiceCounter
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit DiscoveryRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSServiceDiscoveryListener* aListener);
+
+private:
+ virtual ~DiscoveryRequest() { Cancel(NS_OK); }
+
+ RefPtr<nsDNSServiceDiscovery> mService;
+ nsIDNSServiceDiscoveryListener* mListener;
+};
+
+NS_IMPL_ISUPPORTS(DiscoveryRequest, nsICancelable)
+
+DiscoveryRequest::DiscoveryRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSServiceDiscoveryListener* aListener)
+ : mService(aService)
+ , mListener(aListener)
+{
+}
+
+NS_IMETHODIMP
+DiscoveryRequest::Cancel(nsresult aReason)
+{
+ if (mService) {
+ mService->StopDiscovery(mListener);
+ }
+
+ mService = nullptr;
+ return NS_OK;
+}
+
+class RegisterRequest final : public nsICancelable
+ , private ServiceCounter
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICANCELABLE
+
+ explicit RegisterRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSRegistrationListener* aListener);
+
+private:
+ virtual ~RegisterRequest() { Cancel(NS_OK); }
+
+ RefPtr<nsDNSServiceDiscovery> mService;
+ nsIDNSRegistrationListener* mListener;
+};
+
+NS_IMPL_ISUPPORTS(RegisterRequest, nsICancelable)
+
+RegisterRequest::RegisterRequest(nsDNSServiceDiscovery* aService,
+ nsIDNSRegistrationListener* aListener)
+ : mService(aService)
+ , mListener(aListener)
+{
+}
+
+NS_IMETHODIMP
+RegisterRequest::Cancel(nsresult aReason)
+{
+ if (mService) {
+ mService->UnregisterService(mListener);
+ }
+
+ mService = nullptr;
+ return NS_OK;
+}
+
+} // namespace anonymous
+
+NS_IMPL_ISUPPORTS(nsDNSServiceDiscovery, nsIDNSServiceDiscovery)
+
+nsDNSServiceDiscovery::~nsDNSServiceDiscovery()
+{
+}
+
+nsresult
+nsDNSServiceDiscovery::Init()
+{
+ if (!XRE_IsParentProcess()) {
+ MOZ_ASSERT(false, "nsDNSServiceDiscovery can only be used in parent process");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::StartDiscovery(const nsACString& aServiceType,
+ nsIDNSServiceDiscoveryListener* aListener,
+ nsICancelable** aRetVal)
+{
+ MOZ_ASSERT(aRetVal);
+
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = StopDiscovery(aListener)))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> req = new DiscoveryRequest(this, aListener);
+ RefPtr<BrowseOperator> browserOp = new BrowseOperator(aServiceType,
+ aListener);
+ if (NS_WARN_IF(NS_FAILED(rv = browserOp->Start()))) {
+ return rv;
+ }
+
+ mDiscoveryMap.Put(aListener, browserOp);
+
+ req.forget(aRetVal);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::StopDiscovery(nsIDNSServiceDiscoveryListener* aListener)
+{
+ nsresult rv;
+
+ RefPtr<BrowseOperator> browserOp;
+ if (!mDiscoveryMap.Get(aListener, getter_AddRefs(browserOp))) {
+ return NS_OK;
+ }
+
+ browserOp->Cancel(); // cancel non-started operation
+ if (NS_WARN_IF(NS_FAILED(rv = browserOp->Stop()))) {
+ return rv;
+ }
+
+ mDiscoveryMap.Remove(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::RegisterService(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSRegistrationListener* aListener,
+ nsICancelable** aRetVal)
+{
+ MOZ_ASSERT(aRetVal);
+
+ nsresult rv;
+ if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(aListener)))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICancelable> req = new RegisterRequest(this, aListener);
+ RefPtr<RegisterOperator> registerOp = new RegisterOperator(aServiceInfo,
+ aListener);
+ if (NS_WARN_IF(NS_FAILED(rv = registerOp->Start()))) {
+ return rv;
+ }
+
+ mRegisterMap.Put(aListener, registerOp);
+
+ req.forget(aRetVal);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::UnregisterService(nsIDNSRegistrationListener* aListener)
+{
+ nsresult rv;
+
+ RefPtr<RegisterOperator> registerOp;
+ if (!mRegisterMap.Get(aListener, getter_AddRefs(registerOp))) {
+ return NS_OK;
+ }
+
+ registerOp->Cancel(); // cancel non-started operation
+ if (NS_WARN_IF(NS_FAILED(rv = registerOp->Stop()))) {
+ return rv;
+ }
+
+ mRegisterMap.Remove(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::ResolveService(nsIDNSServiceInfo* aServiceInfo,
+ nsIDNSServiceResolveListener* aListener)
+{
+ if (!ServiceCounter::IsServiceRunning()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ RefPtr<ResolveOperator> resolveOp = new ResolveOperator(aServiceInfo,
+ aListener);
+ if (NS_WARN_IF(NS_FAILED(rv = resolveOp->Start()))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
diff --git a/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h
new file mode 100644
index 0000000000..abe98f3574
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
+#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
+
+#include "MDNSResponderOperator.h"
+#include "nsIDNSServiceDiscovery.h"
+#include "nsCOMPtr.h"
+#include "mozilla/RefPtr.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+class BrowseOperator;
+class RegisterOperator;
+
+class nsDNSServiceDiscovery final : public nsIDNSServiceDiscovery
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDNSSERVICEDISCOVERY
+
+ explicit nsDNSServiceDiscovery() = default;
+
+ /*
+ ** The mDNS service is started on demand. If no one uses, mDNS service will not
+ ** start. Therefore, all operations before service started will fail
+ ** and get error code |kDNSServiceErr_ServiceNotRunning| defined in dns_sd.h.
+ **/
+ nsresult Init();
+
+ nsresult StopDiscovery(nsIDNSServiceDiscoveryListener* aListener);
+ nsresult UnregisterService(nsIDNSRegistrationListener* aListener);
+
+private:
+ virtual ~nsDNSServiceDiscovery();
+
+ nsRefPtrHashtable<nsISupportsHashKey, BrowseOperator> mDiscoveryMap;
+ nsRefPtrHashtable<nsISupportsHashKey, RegisterOperator> mRegisterMap;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
diff --git a/netwerk/streamconv/converters/moz.build b/netwerk/streamconv/converters/moz.build
index 546cfb9989..8630922404 100644
--- a/netwerk/streamconv/converters/moz.build
+++ b/netwerk/streamconv/converters/moz.build
@@ -11,7 +11,6 @@ XPIDL_MODULE = 'necko_http'
SOURCES += [
'mozTXTToHTMLConv.cpp',
- 'nsBinHexDecoder.cpp',
'nsDirIndex.cpp',
'nsDirIndexParser.cpp',
'nsHTTPCompressConv.cpp',
@@ -27,6 +26,11 @@ if 'ftp' in CONFIG['NECKO_PROTOCOLS']:
'ParseFTPList.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'cocoa':
+ SOURCES += [
+ 'nsBinHexDecoder.cpp',
+ ]
+
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
diff --git a/netwerk/system/mac/moz.build b/netwerk/system/mac/moz.build
new file mode 100644
index 0000000000..f884a08b7c
--- /dev/null
+++ b/netwerk/system/mac/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+ 'nsNetworkLinkService.mm',
+]
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/netwerk/system/mac/nsNetworkLinkService.h b/netwerk/system/mac/nsNetworkLinkService.h
new file mode 100644
index 0000000000..ee54622478
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -0,0 +1,54 @@
+/* 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/. */
+
+#ifndef NSNETWORKLINKSERVICEMAC_H_
+#define NSNETWORKLINKSERVICEMAC_H_
+
+#include "nsINetworkLinkService.h"
+#include "nsIObserver.h"
+
+#include <SystemConfiguration/SCNetworkReachability.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+class nsNetworkLinkService : public nsINetworkLinkService,
+ public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSINETWORKLINKSERVICE
+ NS_DECL_NSIOBSERVER
+
+ nsNetworkLinkService();
+
+ nsresult Init();
+ nsresult Shutdown();
+
+protected:
+ virtual ~nsNetworkLinkService();
+
+private:
+ bool mLinkUp;
+ bool mStatusKnown;
+
+ // Toggles allowing the sending of network-changed event.
+ bool mAllowChangedEvent;
+
+ SCNetworkReachabilityRef mReachability;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mRunLoopSource;
+ SCDynamicStoreRef mStoreRef;
+
+ void UpdateReachability();
+ void SendEvent(bool aNetworkChanged);
+ static void ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void *info);
+ static void IPConfigChanged(SCDynamicStoreRef store,
+ CFArrayRef changedKeys,
+ void *info);
+ void calculateNetworkId(void);
+ nsCString mNetworkId;
+};
+
+#endif /* NSNETWORKLINKSERVICEMAC_H_ */
diff --git a/netwerk/system/mac/nsNetworkLinkService.mm b/netwerk/system/mac/nsNetworkLinkService.mm
new file mode 100644
index 0000000000..5b2d7575ac
--- /dev/null
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -0,0 +1,526 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <arpa/inet.h>
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SHA1.h"
+#include "mozilla/Base64.h"
+#include "nsNetworkLinkService.h"
+
+#import <Cocoa/Cocoa.h>
+#import <netinet/in.h>
+
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+
+using namespace mozilla;
+
+static LazyLogModule gNotifyAddrLog("nsNotifyAddr");
+#define LOG(args) MOZ_LOG(gNotifyAddrLog, mozilla::LogLevel::Debug, args)
+
+// If non-successful, extract the error code and return it. This
+// error code dance is inspired by
+// http://developer.apple.com/technotes/tn/tn1145.html
+static OSStatus getErrorCodeBool(Boolean success)
+{
+ OSStatus err = noErr;
+ if (!success) {
+ int scErr = ::SCError();
+ if (scErr == kSCStatusOK) {
+ scErr = kSCStatusFailed;
+ }
+ err = scErr;
+ }
+ return err;
+}
+
+// If given a NULL pointer, return the error code.
+static OSStatus getErrorCodePtr(const void *value)
+{
+ return getErrorCodeBool(value != NULL);
+}
+
+// Convenience function to allow NULL input.
+static void CFReleaseSafe(CFTypeRef cf)
+{
+ if (cf) {
+ // "If cf is NULL, this will cause a runtime error and your
+ // application will crash." / Apple docs
+ ::CFRelease(cf);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsNetworkLinkService,
+ nsINetworkLinkService,
+ nsIObserver)
+
+nsNetworkLinkService::nsNetworkLinkService()
+ : mLinkUp(true)
+ , mStatusKnown(false)
+ , mAllowChangedEvent(true)
+ , mReachability(nullptr)
+ , mCFRunLoop(nullptr)
+ , mRunLoopSource(nullptr)
+ , mStoreRef(nullptr)
+{
+}
+
+nsNetworkLinkService::~nsNetworkLinkService() = default;
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetIsLinkUp(bool *aIsUp)
+{
+ *aIsUp = mLinkUp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkStatusKnown(bool *aIsUp)
+{
+ *aIsUp = mStatusKnown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetworkLinkService::GetLinkType(uint32_t *aLinkType)
+{
+ NS_ENSURE_ARG_POINTER(aLinkType);
+
+ // XXX This function has not yet been implemented for this platform
+ *aLinkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN;
+ return NS_OK;
+}
+
+#ifndef SA_SIZE
+#define SA_SIZE(sa) \
+ ( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \
+ sizeof(uint32_t) : \
+ 1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(uint32_t) - 1) ) )
+#endif
+
+static char *getMac(struct sockaddr_dl *sdl, char *buf, size_t bufsize)
+{
+ char *cp;
+ int n, p = 0;
+
+ buf[0] = 0;
+ cp = (char *)LLADDR(sdl);
+ n = sdl->sdl_alen;
+ if (n > 0) {
+ while (--n >= 0) {
+ p += snprintf(&buf[p], bufsize - p, "%02x%s",
+ *cp++ & 0xff, n > 0 ? ":" : "");
+ }
+ }
+ return buf;
+}
+
+/* If the IP matches, get the MAC and return true */
+static bool matchIp(struct sockaddr_dl *sdl, struct sockaddr_inarp *addr,
+ char *ip, char *buf, size_t bufsize)
+{
+ if (sdl->sdl_alen) {
+ if (!strcmp(inet_ntoa(addr->sin_addr), ip)) {
+ getMac(sdl, buf, bufsize);
+ return true; /* done! */
+ }
+ }
+ return false; /* continue */
+}
+
+/*
+ * Scan for the 'IP' address in the ARP table and store the corresponding MAC
+ * address in 'mac'. The output buffer is 'maclen' bytes big.
+ *
+ * Returns 'true' if it found the IP and returns a MAC.
+ */
+static bool scanArp(char *ip, char *mac, size_t maclen)
+{
+ int mib[6];
+ char *lim, *next;
+ int st;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = AF_INET;
+ mib[4] = NET_RT_FLAGS;
+ mib[5] = RTF_LLINFO;
+
+ size_t needed;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ return false;
+ }
+ if (needed == 0) {
+ // empty table
+ return false;
+ }
+
+ UniquePtr <char[]>buf(new char[needed]);
+
+ for (;;) {
+ st = sysctl(mib, 6, &buf[0], &needed, NULL, 0);
+ if (st == 0 || errno != ENOMEM) {
+ break;
+ }
+ needed += needed / 8;
+
+ auto tmp = MakeUnique<char[]>(needed);
+ memcpy(&tmp[0], &buf[0], needed);
+ buf = Move(tmp);
+ }
+ if (st == -1) {
+ return false;
+ }
+ lim = &buf[needed];
+
+ struct rt_msghdr *rtm;
+ for (next = &buf[0]; next < lim; next += rtm->rtm_msglen) {
+ rtm = reinterpret_cast<struct rt_msghdr *>(next);
+ struct sockaddr_inarp *sin2 =
+ reinterpret_cast<struct sockaddr_inarp *>(rtm + 1);
+ struct sockaddr_dl *sdl =
+ reinterpret_cast<struct sockaddr_dl *>
+ ((char *)sin2 + SA_SIZE(sin2));
+ if (matchIp(sdl, sin2, ip, mac, maclen)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int routingTable(char *gw, size_t aGwLen)
+{
+ size_t needed;
+ int mib[6];
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa;
+ struct sockaddr_in *sockin;
+
+ mib[0] = CTL_NET;
+ mib[1] = PF_ROUTE;
+ mib[2] = 0;
+ mib[3] = 0;
+ mib[4] = NET_RT_DUMP;
+ mib[5] = 0;
+ if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
+ return 1;
+ }
+
+ UniquePtr <char[]>buf(new char[needed]);
+
+ if (sysctl(mib, 6, &buf[0], &needed, NULL, 0) < 0) {
+ return 3;
+ }
+
+ rtm = reinterpret_cast<struct rt_msghdr *>(&buf[0]);
+ sa = reinterpret_cast<struct sockaddr *>(rtm + 1);
+ sa = reinterpret_cast<struct sockaddr *>(SA_SIZE(sa) + (char *)sa);
+ sockin = reinterpret_cast<struct sockaddr_in *>(sa);
+ inet_ntop(AF_INET, &sockin->sin_addr.s_addr, gw, aGwLen-1);
+
+ return 0;
+}
+
+//
+// Figure out the current "network identification" string.
+//
+// It detects the IP of the default gateway in the routing table, then the MAC
+// address of that IP in the ARP table before it hashes that string (to avoid
+// information leakage).
+//
+void nsNetworkLinkService::calculateNetworkId(void)
+{
+ bool found = false;
+ char hw[MAXHOSTNAMELEN];
+ if (!routingTable(hw, sizeof(hw))) {
+ char mac[256]; // big enough for a printable MAC address
+ if (scanArp(hw, mac, sizeof(mac))) {
+ LOG(("networkid: MAC %s\n", hw));
+ nsAutoCString mac(hw);
+ // This 'addition' could potentially be a
+ // fixed number from the profile or something.
+ nsAutoCString addition("local-rubbish");
+ nsAutoCString output;
+ SHA1Sum sha1;
+ nsCString combined(mac + addition);
+ sha1.update(combined.get(), combined.Length());
+ uint8_t digest[SHA1Sum::kHashSize];
+ sha1.finish(digest);
+ nsCString newString(reinterpret_cast<char*>(digest),
+ SHA1Sum::kHashSize);
+ nsresult rv = Base64Encode(newString, output);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
+ LOG(("networkid: id %s\n", output.get()));
+ if (mNetworkId != output) {
+ // new id
+ mNetworkId = output;
+ }
+ else {
+ // same id
+ }
+ found = true;
+ }
+ }
+ if (!found) {
+ // no id
+ }
+}
+
+
+NS_IMETHODIMP
+nsNetworkLinkService::Observe(nsISupports *subject,
+ const char *topic,
+ const char16_t *data)
+{
+ if (!strcmp(topic, "xpcom-shutdown")) {
+ Shutdown();
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void
+nsNetworkLinkService::IPConfigChanged(SCDynamicStoreRef aStoreREf,
+ CFArrayRef aChangedKeys,
+ void *aInfo)
+{
+ nsNetworkLinkService *service =
+ static_cast<nsNetworkLinkService*>(aInfo);
+ service->SendEvent(true);
+}
+
+nsresult
+nsNetworkLinkService::Init(void)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "xpcom-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Preferences::AddBoolVarCache(&mAllowChangedEvent,
+ NETWORK_NOTIFY_CHANGED_PREF, true);
+
+ // If the network reachability API can reach 0.0.0.0 without
+ // requiring a connection, there is a network interface available.
+ struct sockaddr_in addr;
+ bzero(&addr, sizeof(addr));
+ addr.sin_len = sizeof(addr);
+ addr.sin_family = AF_INET;
+ mReachability =
+ ::SCNetworkReachabilityCreateWithAddress(NULL,
+ (struct sockaddr *)&addr);
+ if (!mReachability) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCNetworkReachabilityContext context = {0, this, NULL, NULL, NULL};
+ if (!::SCNetworkReachabilitySetCallback(mReachability,
+ ReachabilityChanged,
+ &context)) {
+ NS_WARNING("SCNetworkReachabilitySetCallback failed.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ SCDynamicStoreContext storeContext = {0, this, NULL, NULL, NULL};
+ mStoreRef =
+ ::SCDynamicStoreCreate(NULL,
+ CFSTR("AddIPAddressListChangeCallbackSCF"),
+ IPConfigChanged, &storeContext);
+
+ CFStringRef patterns[2] = {NULL, NULL};
+ OSStatus err = getErrorCodePtr(mStoreRef);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv4".
+ patterns[0] =
+ ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCCompAnyRegex,
+ kSCEntNetIPv4);
+ err = getErrorCodePtr(patterns[0]);
+ if (err == noErr) {
+ // This pattern is "State:/Network/Service/[^/]+/IPv6".
+ patterns[1] =
+ ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+ kSCDynamicStoreDomainState,
+ kSCCompAnyRegex,
+ kSCEntNetIPv6);
+ err = getErrorCodePtr(patterns[1]);
+ }
+ }
+
+ CFArrayRef patternList = NULL;
+ // Create a pattern list containing just one pattern,
+ // then tell SCF that we want to watch changes in keys
+ // that match that pattern list, then create our run loop
+ // source.
+ if (err == noErr) {
+ patternList = ::CFArrayCreate(NULL, (const void **) patterns,
+ 2, &kCFTypeArrayCallBacks);
+ if (!patternList) {
+ err = -1;
+ }
+ }
+ if (err == noErr) {
+ err =
+ getErrorCodeBool(::SCDynamicStoreSetNotificationKeys(mStoreRef,
+ NULL,
+ patternList));
+ }
+
+ if (err == noErr) {
+ mRunLoopSource =
+ ::SCDynamicStoreCreateRunLoopSource(NULL, mStoreRef, 0);
+ err = getErrorCodePtr(mRunLoopSource);
+ }
+
+ CFReleaseSafe(patterns[0]);
+ CFReleaseSafe(patterns[1]);
+ CFReleaseSafe(patternList);
+
+ if (err != noErr) {
+ CFReleaseSafe(mStoreRef);
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Get the current run loop. This service is initialized at startup,
+ // so we shouldn't run in to any problems with modal dialog run loops.
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ if (!mCFRunLoop) {
+ NS_WARNING("Could not get current run loop.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ ::CFRetain(mCFRunLoop);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
+ ::CFRelease(mReachability);
+ mReachability = NULL;
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = NULL;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ UpdateReachability();
+
+ return NS_OK;
+}
+
+nsresult
+nsNetworkLinkService::Shutdown()
+{
+ if (!::SCNetworkReachabilityUnscheduleFromRunLoop(mReachability,
+ mCFRunLoop,
+ kCFRunLoopDefaultMode)) {
+ NS_WARNING("SCNetworkReachabilityUnscheduleFromRunLoop failed.");
+ }
+
+ CFRunLoopRemoveSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
+ ::CFRelease(mReachability);
+ mReachability = nullptr;
+
+ ::CFRelease(mCFRunLoop);
+ mCFRunLoop = nullptr;
+
+ ::CFRelease(mStoreRef);
+ mStoreRef = nullptr;
+
+ ::CFRelease(mRunLoopSource);
+ mRunLoopSource = nullptr;
+
+ return NS_OK;
+}
+
+void
+nsNetworkLinkService::UpdateReachability()
+{
+ if (!mReachability) {
+ return;
+ }
+
+ SCNetworkConnectionFlags flags;
+ if (!::SCNetworkReachabilityGetFlags(mReachability, &flags)) {
+ mStatusKnown = false;
+ return;
+ }
+
+ bool reachable = (flags & kSCNetworkFlagsReachable) != 0;
+ bool needsConnection = (flags & kSCNetworkFlagsConnectionRequired) != 0;
+
+ mLinkUp = (reachable && !needsConnection);
+ mStatusKnown = true;
+}
+
+void
+nsNetworkLinkService::SendEvent(bool aNetworkChanged)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (!observerService) {
+ return;
+ }
+
+ const char *event;
+ if (aNetworkChanged) {
+ if (!mAllowChangedEvent) {
+ return;
+ }
+ event = NS_NETWORK_LINK_DATA_CHANGED;
+ } else if (!mStatusKnown) {
+ event = NS_NETWORK_LINK_DATA_UNKNOWN;
+ } else {
+ event = mLinkUp ? NS_NETWORK_LINK_DATA_UP
+ : NS_NETWORK_LINK_DATA_DOWN;
+ }
+ LOG(("SendEvent: network is '%s'\n", event));
+
+ observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
+ NS_NETWORK_LINK_TOPIC,
+ NS_ConvertASCIItoUTF16(event).get());
+}
+
+/* static */
+void
+nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
+ SCNetworkConnectionFlags flags,
+ void *info)
+{
+ nsNetworkLinkService *service =
+ static_cast<nsNetworkLinkService*>(info);
+
+ service->UpdateReachability();
+ service->SendEvent(false);
+ service->calculateNetworkId();
+}
diff --git a/netwerk/system/moz.build b/netwerk/system/moz.build
index a8034d7497..dcf7d6c3f1 100644
--- a/netwerk/system/moz.build
+++ b/netwerk/system/moz.build
@@ -5,5 +5,7 @@
if CONFIG['OS_ARCH'] == 'WINNT':
DIRS += ['win32']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += ['mac']
elif CONFIG['OS_ARCH'] == 'Linux':
DIRS += ['linux']
diff --git a/nsprpub/pr/src/linking/prlink.c b/nsprpub/pr/src/linking/prlink.c
index 1f3430714e..012af34c43 100644
--- a/nsprpub/pr/src/linking/prlink.c
+++ b/nsprpub/pr/src/linking/prlink.c
@@ -776,6 +776,9 @@ pr_LoadLibraryByPathname(const char *name, PRIntn flags)
int dl_flags = 0;
#endif
void *h = NULL;
+#if defined(DARWIN)
+ PRBool okToLoad = PR_FALSE;
+#endif
if (flags & PR_LD_LAZY) {
dl_flags |= RTLD_LAZY;
@@ -790,13 +793,37 @@ pr_LoadLibraryByPathname(const char *name, PRIntn flags)
dl_flags |= RTLD_LOCAL;
}
#if defined(DARWIN)
- /* ensure the file exists if it contains a slash character i.e. path */
- /* DARWIN's dlopen ignores the provided path and checks for the */
- /* plain filename in DYLD_LIBRARY_PATH */
- if (strchr(name, PR_DIRECTORY_SEPARATOR) == NULL ||
- PR_Access(name, PR_ACCESS_EXISTS) == PR_SUCCESS) {
- h = dlopen(name, dl_flags);
- }
+ /* If the file contains an absolute or relative path (slash)
+ * and the path doesn't look like a System path, then require
+ * the file exists.
+ * The reason is that DARWIN's dlopen ignores the provided path
+ * and checks for the plain filename in DYLD_LIBRARY_PATH,
+ * which could load an unexpected version of a library. */
+ if (strchr(name, PR_DIRECTORY_SEPARATOR) == NULL) {
+ /* no slash, allow to load from any location */
+ okToLoad = PR_TRUE;
+ } else {
+ const char systemPrefix1[] = "/System/";
+ const size_t systemPrefixLen1 = strlen(systemPrefix1);
+ const char systemPrefix2[] = "/usr/lib/";
+ const size_t systemPrefixLen2 = strlen(systemPrefix2);
+ const name_len = strlen(name);
+ if (((name_len > systemPrefixLen1) &&
+ (strncmp(name, systemPrefix1, systemPrefixLen1) == 0)) ||
+ ((name_len > systemPrefixLen2) &&
+ (strncmp(name, systemPrefix2, systemPrefixLen2) == 0))) {
+ /* found at beginning, it's a system library.
+ * Skip filesystem check (required for macOS 11),
+ * allow loading from any location */
+ okToLoad = PR_TRUE;
+ } else if (PR_Access(name, PR_ACCESS_EXISTS) == PR_SUCCESS) {
+ /* file exists, allow to load */
+ okToLoad = PR_TRUE;
+ }
+ }
+ if (okToLoad) {
+ h = dlopen(name, dl_flags);
+ }
#else
h = dlopen(name, dl_flags);
#endif
diff --git a/old-configure.in b/old-configure.in
index a50baade18..f848e720e4 100644
--- a/old-configure.in
+++ b/old-configure.in
@@ -95,6 +95,12 @@ case "$target" in
;;
esac
+case "$target" in
+*-apple-darwin*)
+ MOZ_IOS_SDK
+ ;;
+esac
+
AC_SUBST(OBJCOPY)
dnl ========================================================
@@ -720,6 +726,11 @@ case "$host" in
esac
;;
+*-darwin*)
+ HOST_CFLAGS="$HOST_CFLAGS -DXP_UNIX -DXP_MACOSX"
+ HOST_OPTIMIZE_FLAGS="${HOST_OPTIMIZE_FLAGS=-O3}"
+ ;;
+
*-linux*|*-kfreebsd*-gnu|*-gnu*)
HOST_CFLAGS="$HOST_CFLAGS -DXP_UNIX"
HOST_OPTIMIZE_FLAGS="${HOST_OPTIMIZE_FLAGS=-O3}"
@@ -756,6 +767,76 @@ dnl System overrides of the defaults for target
dnl ========================================================
case "$target" in
+*-darwin*)
+ MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@'
+ MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@'
+ MOZ_OPTIMIZE_FLAGS="-O3"
+ CXXFLAGS="$CXXFLAGS -stdlib=libc++"
+ DLL_SUFFIX=".dylib"
+ DSO_LDOPTS=''
+ STRIP_FLAGS="$STRIP_FLAGS -x -S"
+ # Ensure that if we're targeting iOS an SDK was provided.
+ AC_CACHE_CHECK(for iOS target,
+ ac_cv_ios_target,
+ [AC_TRY_COMPILE([#include <TargetConditionals.h>
+#if !(TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR)
+#error not iOS
+#endif],
+ [],
+ ac_cv_ios_target="yes",
+ ac_cv_ios_target="no")])
+ if test "$ac_cv_ios_target" = "yes" -a -z $MOZ_IOS; then
+ AC_MSG_ERROR([targeting iOS but not using an iOS SDK?])
+ fi
+ if test -n "$MOZ_IOS"; then
+ direct_nspr_config=1
+ else
+ # The ExceptionHandling framework is needed for Objective-C exception
+ # logging code in nsObjCExceptions.h. Currently we only use that in debug
+ # builds.
+ MOZ_DEBUG_LDFLAGS="$MOZ_DEBUG_LDFLAGS -framework ExceptionHandling";
+ fi
+
+ if test "x$lto_is_enabled" = "xyes"; then
+ echo "Skipping -dead_strip because lto is enabled."
+ dnl DTrace and -dead_strip don't interact well. See bug 403132.
+ dnl ===================================================================
+ elif test "x$enable_dtrace" = "xyes"; then
+ echo "Skipping -dead_strip because DTrace is enabled. See bug 403132."
+ else
+ dnl check for the presence of the -dead_strip linker flag
+ AC_MSG_CHECKING([for -dead_strip option to ld])
+ _SAVE_LDFLAGS=$LDFLAGS
+ LDFLAGS="$LDFLAGS -Wl,-dead_strip"
+ AC_TRY_LINK(,[return 0;],_HAVE_DEAD_STRIP=1,_HAVE_DEAD_STRIP=)
+ if test -n "$_HAVE_DEAD_STRIP" ; then
+ AC_MSG_RESULT([yes])
+ MOZ_OPTIMIZE_LDFLAGS="-Wl,-dead_strip"
+ else
+ AC_MSG_RESULT([no])
+ fi
+
+ LDFLAGS=$_SAVE_LDFLAGS
+ fi
+
+ dnl With newer linkers we need to pass -allow_heap_execute because of
+ dnl Microsoft Silverlight (5.1.10411.0 at least).
+ AC_MSG_CHECKING([for -allow_heap_execute option to ld])
+ _SAVE_LDFLAGS=$LDFLAGS
+ LDFLAGS="$LDFLAGS -Wl,-allow_heap_execute"
+ AC_TRY_LINK(,[return 0;],_HAVE_ALLOW_HEAP_EXECUTE=1,
+ _HAVE_ALLOW_HEAP_EXECUTE=)
+ if test -n "$_HAVE_ALLOW_HEAP_EXECUTE" ; then
+ AC_MSG_RESULT([yes])
+ MOZ_ALLOW_HEAP_EXECUTE_FLAGS="-Wl,-allow_heap_execute"
+ else
+ AC_MSG_RESULT([no])
+ fi
+ LDFLAGS=$_SAVE_LDFLAGS
+
+ MOZ_FIX_LINK_PATHS="-Wl,-executable_path,${DIST}/bin"
+ ;;
+
*-*linux*)
if test "$GNU_CC" -o "$GNU_CXX"; then
MOZ_PGO_OPTIMIZE_FLAGS="-O3"
@@ -1108,6 +1189,9 @@ case "$target" in
*-linux*|*-kfreebsd*-gnu|*-gnu*)
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-Wl,--version-script -Wl,$(BUILD_TOOLS)/gnu-ld-scripts/components-version-script'
;;
+ *-darwin*)
+ MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-Wl,-exported_symbols_list -Wl,$(BUILD_TOOLS)/gnu-ld-scripts/components-export-list'
+ ;;
*-mingw*)
if test -n "$GNU_CC"; then
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS='-Wl,--version-script,$(BUILD_TOOLS)/gnu-ld-scripts/components-version-script'
@@ -1142,6 +1226,8 @@ MOZ_CXX11
AC_LANG_C
case "${OS_TARGET}" in
+Darwin)
+ ;;
*)
STL_FLAGS="-I${DIST}/stl_wrappers"
WRAP_STL_INCLUDES=1
@@ -1223,7 +1309,15 @@ dnl Checks for libraries.
dnl ========================================================
AC_CHECK_LIB(c_r, gethostbyname_r)
+dnl We don't want to link with libdl even if it's present on OS X, since
+dnl it's not used and not part of the default installation. OS/2 has dlfcn
+dnl in libc.
+dnl We don't want to link against libm or libpthread on Darwin since
+dnl they both are just symlinks to libSystem and explicitly linking
+dnl against libSystem causes issues when debugging (see bug 299601).
case $target in
+*-darwin*)
+ ;;
*)
AC_SEARCH_LIBS(dlopen, dl,
MOZ_CHECK_HEADER(dlfcn.h,
@@ -1299,20 +1393,27 @@ AC_SUBST_LIST(XSS_LIBS)
dnl ========================================================
dnl = pthread support
+dnl = Start by checking whether the system support pthreads
dnl ========================================================
-
-AC_CHECK_LIB(pthreads, pthread_create,
- MOZ_USE_PTHREADS=1 _PTHREAD_LDFLAGS="-lpthreads",
- AC_CHECK_LIB(pthread, pthread_create,
- MOZ_USE_PTHREADS=1 _PTHREAD_LDFLAGS="-lpthread",
- AC_CHECK_LIB(c_r, pthread_create,
- MOZ_USE_PTHREADS=1 _PTHREAD_LDFLAGS="-lc_r",
- AC_CHECK_LIB(c, pthread_create,
- MOZ_USE_PTHREADS=1
+case "$target_os" in
+darwin*)
+ MOZ_USE_PTHREADS=1
+ ;;
+*)
+ AC_CHECK_LIB(pthreads, pthread_create,
+ MOZ_USE_PTHREADS=1 _PTHREAD_LDFLAGS="-lpthreads",
+ AC_CHECK_LIB(pthread, pthread_create,
+ MOZ_USE_PTHREADS=1 _PTHREAD_LDFLAGS="-lpthread",
+ AC_CHECK_LIB(c_r, pthread_create,
+ MOZ_USE_PTHREADS=1 _PTHREAD_LDFLAGS="-lc_r",
+ AC_CHECK_LIB(c, pthread_create,
+ MOZ_USE_PTHREADS=1
+ )
)
)
)
-)
+ ;;
+esac
dnl ========================================================
dnl Check the command line for --with-pthreads
@@ -1412,27 +1513,35 @@ AC_FUNC_MEMCMP
AC_CHECK_FUNCS(stat64 lstat64 truncate64 statvfs64 statvfs statfs64 statfs getpagesize gmtime_r localtime_r arc4random arc4random_buf mallinfo gettid lchown setpriority strerror syscall)
dnl check for clock_gettime(), the CLOCK_MONOTONIC clock
-AC_CACHE_CHECK(for clock_gettime(CLOCK_MONOTONIC),
- ac_cv_clock_monotonic,
- [for libs in "" -lrt; do
- _SAVE_LIBS="$LIBS"
- LIBS="$LIBS $libs"
- AC_TRY_LINK([#include <time.h>],
- [ struct timespec ts;
- clock_gettime(CLOCK_MONOTONIC, &ts); ],
- ac_cv_clock_monotonic=$libs
- LIBS="$_SAVE_LIBS"
- break,
- ac_cv_clock_monotonic=no)
- LIBS="$_SAVE_LIBS"
- done])
-if test "$ac_cv_clock_monotonic" != "no"; then
- HAVE_CLOCK_MONOTONIC=1
- REALTIME_LIBS=$ac_cv_clock_monotonic
- AC_DEFINE(HAVE_CLOCK_MONOTONIC)
- AC_SUBST(HAVE_CLOCK_MONOTONIC)
- AC_SUBST_LIST(REALTIME_LIBS)
-fi
+dnl avoid this on Darwin, since depending on your system config, we may think
+dnl it exists but it really doesn't
+case "$OS_TARGET" in
+Darwin)
+ ;;
+*)
+ AC_CACHE_CHECK(for clock_gettime(CLOCK_MONOTONIC),
+ ac_cv_clock_monotonic,
+ [for libs in "" -lrt; do
+ _SAVE_LIBS="$LIBS"
+ LIBS="$LIBS $libs"
+ AC_TRY_LINK([#include <time.h>],
+ [ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts); ],
+ ac_cv_clock_monotonic=$libs
+ LIBS="$_SAVE_LIBS"
+ break,
+ ac_cv_clock_monotonic=no)
+ LIBS="$_SAVE_LIBS"
+ done])
+ if test "$ac_cv_clock_monotonic" != "no"; then
+ HAVE_CLOCK_MONOTONIC=1
+ REALTIME_LIBS=$ac_cv_clock_monotonic
+ AC_DEFINE(HAVE_CLOCK_MONOTONIC)
+ AC_SUBST(HAVE_CLOCK_MONOTONIC)
+ AC_SUBST_LIST(REALTIME_LIBS)
+ fi
+ ;;
+esac
AC_CACHE_CHECK(for pthread_cond_timedwait_monotonic_np,
ac_cv_pthread_cond_timedwait_monotonic_np,
@@ -1504,6 +1613,11 @@ AC_LANG_CPLUSPLUS
ICONV_LIBS=
+case $target_os in
+ darwin*|mingw*)
+ ;;
+ *)
+
AC_CHECK_LIB(c, iconv, [ICONV_LIBS=],
AC_CHECK_LIB(iconv, iconv, [ICONV_LIBS="-liconv"],
AC_CHECK_LIB(iconv, libiconv, [ICONV_LIBS="-liconv"])))
@@ -1551,6 +1665,9 @@ if test "$ac_cv_func_iconv" = "yes"; then
fi
LIBS=$_SAVE_LIBS
+ ;;
+esac
+
AC_SUBST_LIST(ICONV_LIBS)
AM_LANGINFO_CODESET
@@ -1796,7 +1913,7 @@ dnl ========================================================
MOZ_ARG_HEADER(External Packages)
case "$OS_TARGET" in
-WINNT)
+WINNT|Darwin)
MOZ_FOLD_LIBS=1
;;
*)
@@ -2411,7 +2528,7 @@ dnl --enable-webrtc to override. Can disable for everything in
dnl the master list above.
if test -n "$MOZ_WEBRTC"; then
case "$target" in
- *-linux*|*-mingw*|*-dragonfly*|*-freebsd*|*-netbsd*|*-openbsd*)
+ *-linux*|*-mingw*|*-darwin*|*-dragonfly*|*-freebsd*|*-netbsd*|*-openbsd*)
dnl Leave enabled
;;
*)
@@ -2557,11 +2674,11 @@ dnl = Client drawing in titlebar
dnl ========================================================
if test -n "$MOZ_CAN_DRAW_IN_TITLEBAR"; then
case "$OS_TARGET" in
- WINNT)
+ WINNT|Darwin)
AC_DEFINE(MOZ_CAN_DRAW_IN_TITLEBAR)
;;
*)
- AC_MSG_ERROR([Drawing in the titlebar is only supported on Windows targets])
+ AC_MSG_ERROR([Drawing in the titlebar is only supported on Windows and Macintosh targets])
;;
esac
fi
@@ -2719,6 +2836,14 @@ if test -z "$MOZ_SYSTEM_LIBVPX"; then
dnl See if we have assembly on this platform.
case "$OS_ARCH:$CPU_ARCH" in
+ Darwin:x86)
+ VPX_USE_YASM=1
+ VPX_X86_ASM=1
+ ;;
+ Darwin:x86_64)
+ VPX_USE_YASM=1
+ VPX_X86_ASM=1
+ ;;
WINNT:x86_64)
VPX_USE_YASM=1
VPX_X86_ASM=1
@@ -2843,7 +2968,7 @@ dnl ========================================================
dnl If using Desktop Linux, ensure that the PA library is available
case "$OS_TARGET" in
-WINNT)
+WINNT|Darwin)
;;
*)
MOZ_PULSEAUDIO=1
@@ -3045,6 +3170,11 @@ MOZ_ARG_ENABLE_BOOL(gamepad,
if test "$MOZ_GAMEPAD"; then
case "$OS_TARGET" in
+ Darwin)
+ if test -z "$MOZ_IOS"; then
+ MOZ_GAMEPAD_BACKEND=cocoa
+ fi
+ ;;
WINNT)
MOZ_GAMEPAD_BACKEND=windows
;;
@@ -3089,6 +3219,14 @@ if test -n "$MOZ_LIBJPEG_TURBO" -a -n "$COMPILE_ENVIRONMENT"; then
dnl Do we support libjpeg-turbo on this platform?
case "$OS_ARCH:$CPU_ARCH" in
+ Darwin:x86)
+ LIBJPEG_TURBO_ASFLAGS="-DPIC -DMACHO"
+ ;;
+ Darwin:x86_64)
+ LIBJPEG_TURBO_ASFLAGS="-D__x86_64__ -DPIC -DMACHO"
+ ;;
+ Darwin:arm*)
+ ;;
WINNT:x86)
LIBJPEG_TURBO_ASFLAGS="-DPIC -DWIN32"
;;
@@ -3168,6 +3306,9 @@ if test -n "$MOZ_LIBAV_FFT" -a -n "$COMPILE_ENVIRONMENT"; then
AC_DEFINE(MOZ_LIBAV_FFT)
dnl Do we support libav-fft on this platform?
case "$OS_ARCH:$CPU_ARCH" in
+ Darwin:x86_64)
+ LIBAV_FFT_ASFLAGS="-D__x86_64__ -DPIC -DMACHO"
+ ;;
WINNT:x86)
LIBAV_FFT_ASFLAGS="-DPIC -DWIN32"
;;
@@ -3542,11 +3683,11 @@ if test -n "$MOZ_DEBUG"; then
fi
case "${OS_TARGET}" in
-WINNT)
+WINNT|Darwin)
MOZ_GLUE_IN_PROGRAM=
;;
*)
- dnl On !Windows, we only want to link executables against mozglue
+ dnl On !Windows !OSX, we only want to link executables against mozglue
MOZ_GLUE_IN_PROGRAM=1
AC_DEFINE(MOZ_GLUE_IN_PROGRAM)
;;
@@ -4496,6 +4637,11 @@ MOZ_ARG_DISABLE_BOOL(necko-wifi,
if test "$MOZ_NECKO_WIFI"; then
case "$OS_TARGET" in
+ Darwin)
+ if test -z "$MOZ_IOS"; then
+ NECKO_WIFI=1
+ fi
+ ;;
DragonFly|FreeBSD|WINNT)
NECKO_WIFI=1
;;
diff --git a/python/mozbuild/mozbuild/artifacts.py b/python/mozbuild/mozbuild/artifacts.py
index 563fcb9ff0..02538938fb 100644
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -818,6 +818,10 @@ class Artifacts(object):
return ('linux64' if target_64bit else 'linux') + target_suffix
if self._defines.get('XP_WIN', False):
return ('win64' if target_64bit else 'win32') + target_suffix
+ if self._defines.get('XP_MACOSX', False):
+ # We only produce unified builds in automation, so the target_cpu
+ # check is not relevant.
+ return 'macosx64' + target_suffix
raise Exception('Cannot determine default job for |mach artifact|!')
def _pushheads_from_rev(self, rev, count):
diff --git a/python/psutil/psutil/_psutil_posix.c b/python/psutil/psutil/_psutil_posix.c
index 183dab0e12..daf11cd332 100644
--- a/python/psutil/psutil/_psutil_posix.c
+++ b/python/psutil/psutil/_psutil_posix.c
@@ -23,6 +23,7 @@
#include <netdb.h>
#include <netinet/in.h>
#include <net/if_dl.h>
+#include <sys/ioctl.h>
#endif
#if defined(__sun)
diff --git a/testing/crashtest/crashtests.list b/testing/crashtest/crashtests.list
index 3d798220ef..863c10b9f6 100644
--- a/testing/crashtest/crashtests.list
+++ b/testing/crashtest/crashtests.list
@@ -67,6 +67,7 @@ include ../../security/manager/ssl/crashtests/crashtests.list
include ../../view/crashtests/crashtests.list
+include ../../widget/cocoa/crashtests/crashtests.list
include ../../widget/crashtests/crashtests.list
include ../../widget/gtk/crashtests/crashtests.list
diff --git a/toolkit/components/apppicker/content/appPicker.js b/toolkit/components/apppicker/content/appPicker.js
index 6922fce39f..21a007632b 100644
--- a/toolkit/components/apppicker/content/appPicker.js
+++ b/toolkit/components/apppicker/content/appPicker.js
@@ -119,6 +119,12 @@ AppPicker.prototype =
return file.getVersionInfoField("FileDescription");
} catch (e) {}
}
+#elifdef XP_MACOSX
+ if (file instanceof Components.interfaces.nsILocalFileMac) {
+ try {
+ return file.bundleDisplayName;
+ } catch (e) {}
+ }
#endif
return file.leafName;
},
@@ -177,6 +183,8 @@ AppPicker.prototype =
var startLocation;
#ifdef XP_WIN
startLocation = "ProgF"; // Program Files
+#elifdef XP_MACOSX
+ startLocation = "LocApp"; // Local Applications
#else
startLocation = "Home";
#endif
diff --git a/toolkit/components/blocklist/nsBlocklistService.js b/toolkit/components/blocklist/nsBlocklistService.js
index 188fdfb387..f016fe6cdc 100644
--- a/toolkit/components/blocklist/nsBlocklistService.js
+++ b/toolkit/components/blocklist/nsBlocklistService.js
@@ -114,6 +114,15 @@ XPCOMUtils.defineLazyGetter(this, "gABI", function() {
LOG("BlockList Global gABI: XPCOM ABI unknown.");
}
+#ifdef XP_MACOSX
+ // Mac universal build should report a different ABI than either macppc
+ // or mactel.
+ let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
+ getService(Ci.nsIMacUtils);
+
+ if (macutils.isUniversalBinary)
+ abi += "-u-" + macutils.architecturesInBinary;
+#endif
return abi;
});
diff --git a/toolkit/components/downloads/nsDownloadManager.cpp b/toolkit/components/downloads/nsDownloadManager.cpp
index bc01b9ae58..587c1ac8ab 100644
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -57,6 +57,10 @@
#endif
#endif
+#ifdef XP_MACOSX
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
#ifdef MOZ_WIDGET_GTK
#include <gtk/gtk.h>
#endif
@@ -1372,7 +1376,12 @@ nsDownloadManager::GetDefaultDownloadsDirectory(nsIFile **aResult)
mBundle->GetStringFromName(u"downloadsFolder",
getter_Copies(folderName));
-#if defined(XP_WIN)
+#if defined (XP_MACOSX)
+ rv = dirService->Get(NS_OSX_DEFAULT_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(downloadDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+#elif defined(XP_WIN)
rv = dirService->Get(NS_WIN_DEFAULT_DOWNLOAD_DIR,
NS_GET_IID(nsIFile),
getter_AddRefs(downloadDir));
@@ -2436,11 +2445,19 @@ nsDownloadManager::Observe(nsISupports *aSubject,
nsCOMPtr<nsISupportsPRBool> cancelDownloads =
do_QueryInterface(aSubject, &rv);
NS_ENSURE_SUCCESS(rv, rv);
+#ifndef XP_MACOSX
ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
u"quitCancelDownloadsAlertTitle",
u"quitCancelDownloadsAlertMsgMultiple",
u"quitCancelDownloadsAlertMsg",
u"dontQuitButtonWin");
+#else
+ ConfirmCancelDownloads(currDownloadCount, cancelDownloads,
+ u"quitCancelDownloadsAlertTitle",
+ u"quitCancelDownloadsAlertMsgMacMultiple",
+ u"quitCancelDownloadsAlertMsgMac",
+ u"dontQuitButtonMac");
+#endif
} else if (strcmp(aTopic, "offline-requested") == 0 && currDownloadCount) {
nsCOMPtr<nsISupportsPRBool> cancelDownloads =
do_QueryInterface(aSubject, &rv);
@@ -2730,7 +2747,7 @@ nsDownload::SetState(DownloadState aState)
}
}
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mTarget);
nsCOMPtr<nsIFile> file;
nsAutoString path;
@@ -2740,6 +2757,7 @@ nsDownload::SetState(DownloadState aState)
file &&
NS_SUCCEEDED(file->GetPath(path))) {
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
// On Windows and Gtk, add the download to the system's "recent documents"
// list, with a pref to disable.
{
@@ -2777,6 +2795,18 @@ nsDownload::SetState(DownloadState aState)
g_object_unref(gio_file);
#endif
}
+#endif
+
+#ifdef XP_MACOSX
+ // On OS X, make the downloads stack bounce.
+ CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
+ NS_ConvertUTF16toUTF8(path).get(),
+ kCFStringEncodingUTF8);
+ CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
+ ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
+ observedObject, nullptr, TRUE);
+ ::CFRelease(observedObject);
+#endif
}
#ifdef XP_WIN
@@ -3359,10 +3389,14 @@ nsDownload::OpenWithApplication()
if (!prefs || NS_FAILED(prefs->GetBoolPref(PREF_BH_DELETETEMPFILEONEXIT,
&deleteTempFileOnExit))) {
// No prefservice or no pref set; use default value
- // Some users have been very verbal about temp files being deleted on
+#if !defined(XP_MACOSX)
+ // Mac users have been very verbal about temp files being deleted on
// app exit - they don't like it - but we'll continue to do this on
- // all platforms for now.
+ // other platforms for now.
deleteTempFileOnExit = true;
+#else
+ deleteTempFileOnExit = false;
+#endif
}
// Always schedule files to be deleted at the end of the private browsing
diff --git a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
index 8b5c644987..22a6570dac 100644
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -294,7 +294,9 @@ this.DownloadIntegration = {
}
let directoryPath = null;
-#ifdef XP_WIN
+#ifdef XP_MACOSX
+ directoryPath = this._getDirectory("DfltDwnld");
+#elifdef XP_WIN
// For XP/2K, use My Documents/Downloads. Other version uses
// the default Downloads directory.
let version = parseFloat(Services.sysinfo.getProperty("version"));
@@ -363,7 +365,11 @@ this.DownloadIntegration = {
*/
getTemporaryDownloadsDirectory: Task.async(function* () {
let directoryPath = null;
+#ifdef XP_MACOSX
+ directoryPath = yield this.getPreferredDownloadsDirectory();
+#else
directoryPath = this._getDirectory("TmpD");
+#endif
return directoryPath;
}),
diff --git a/toolkit/components/jsdownloads/src/DownloadPlatform.cpp b/toolkit/components/jsdownloads/src/DownloadPlatform.cpp
index 14277e5bd4..66ad2b8fa8 100644
--- a/toolkit/components/jsdownloads/src/DownloadPlatform.cpp
+++ b/toolkit/components/jsdownloads/src/DownloadPlatform.cpp
@@ -25,6 +25,11 @@
#include "nsILocalFileWin.h"
#endif
+#ifdef XP_MACOSX
+#include <CoreFoundation/CoreFoundation.h>
+#include "../../../../xpcom/io/CocoaFileUtils.h"
+#endif
+
#ifdef MOZ_WIDGET_GTK
#include <gtk/gtk.h>
#endif
@@ -64,13 +69,39 @@ static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpoint
}
#endif
+#ifdef XP_MACOSX
+// Caller is responsible for freeing any result (CF Create Rule)
+CFURLRef CreateCFURLFromNSIURI(nsIURI *aURI) {
+ nsAutoCString spec;
+ if (aURI) {
+ aURI->GetSpec(spec);
+ }
+
+ CFStringRef urlStr = ::CFStringCreateWithCString(kCFAllocatorDefault,
+ spec.get(),
+ kCFStringEncodingUTF8);
+ if (!urlStr) {
+ return NULL;
+ }
+
+ CFURLRef url = ::CFURLCreateWithString(kCFAllocatorDefault,
+ urlStr,
+ NULL);
+
+ ::CFRelease(urlStr);
+
+ return url;
+}
+#endif
+
nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIURI* aReferrer, nsIFile* aTarget,
const nsACString& aContentType, bool aIsPrivate)
{
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
nsAutoString path;
if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) {
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
// On Windows and Gtk, add the download to the system's "recent documents"
// list, with a pref to disable.
{
@@ -105,10 +136,53 @@ nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIURI* aReferrer, nsIF
nullptr, gio_set_metadata_done, nullptr);
g_object_unref(file_info);
g_object_unref(gio_file);
-#endif // MOZ_ENABLE_GIO
+#endif
}
+#endif
+
+#ifdef XP_MACOSX
+ // On OS X, make the downloads stack bounce.
+ CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
+ NS_ConvertUTF16toUTF8(path).get(),
+ kCFStringEncodingUTF8);
+ CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
+ ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
+ observedObject, nullptr, TRUE);
+ ::CFRelease(observedObject);
+
+ // Add OS X origin and referrer file metadata
+ CFStringRef pathCFStr = NULL;
+ if (!path.IsEmpty()) {
+ pathCFStr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+ (const UniChar*)path.get(),
+ path.Length());
+ }
+ if (pathCFStr) {
+ bool isFromWeb = IsURLPossiblyFromWeb(aSource);
+
+ CFURLRef sourceCFURL = CreateCFURLFromNSIURI(aSource);
+ CFURLRef referrerCFURL = CreateCFURLFromNSIURI(aReferrer);
+
+ CocoaFileUtils::AddOriginMetadataToFile(pathCFStr,
+ sourceCFURL,
+ referrerCFURL);
+ CocoaFileUtils::AddQuarantineMetadataToFile(pathCFStr,
+ sourceCFURL,
+ referrerCFURL,
+ isFromWeb);
+
+ ::CFRelease(pathCFStr);
+ if (sourceCFURL) {
+ ::CFRelease(sourceCFURL);
+ }
+ if (referrerCFURL) {
+ ::CFRelease(referrerCFURL);
+ }
+ }
+#endif
}
-#endif // defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+
+#endif
return NS_OK;
}
diff --git a/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm b/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
index 860a325d4b..3039525f5f 100644
--- a/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
@@ -184,10 +184,17 @@ this.DownloadPrompter.prototype = {
switch (aPromptType) {
case this.ON_QUIT:
title = s.quitCancelDownloadsAlertTitle;
+#ifdef XP_MACOSX
+ message = aDownloadsCount > 1
+ ? s.quitCancelDownloadsAlertMsgMacMultiple(aDownloadsCount)
+ : s.quitCancelDownloadsAlertMsgMac;
+ cancelButton = s.dontQuitButtonMac;
+#else
message = aDownloadsCount > 1
? s.quitCancelDownloadsAlertMsgMultiple(aDownloadsCount)
: s.quitCancelDownloadsAlertMsg;
cancelButton = s.dontQuitButtonWin;
+#endif
break;
case this.ON_OFFLINE:
title = s.offlineCancelDownloadsAlertTitle;
diff --git a/toolkit/components/jsdownloads/src/moz.build b/toolkit/components/jsdownloads/src/moz.build
index 115435f641..5a08340ae2 100644
--- a/toolkit/components/jsdownloads/src/moz.build
+++ b/toolkit/components/jsdownloads/src/moz.build
@@ -18,11 +18,11 @@ EXTRA_JS_MODULES += [
'DownloadList.jsm',
'Downloads.jsm',
'DownloadStore.jsm',
- 'DownloadUIHelper.jsm',
]
EXTRA_PP_JS_MODULES += [
'DownloadIntegration.jsm',
+ 'DownloadUIHelper.jsm',
]
FINAL_LIBRARY = 'xul'
diff --git a/toolkit/components/parentalcontrols/moz.build b/toolkit/components/parentalcontrols/moz.build
index 6c8bd9a8ce..577162945b 100644
--- a/toolkit/components/parentalcontrols/moz.build
+++ b/toolkit/components/parentalcontrols/moz.build
@@ -10,6 +10,8 @@ XPIDL_MODULE = 'parentalcontrols'
if not CONFIG['MOZ_DISABLE_PARENTAL_CONTROLS']:
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
SOURCES += ['nsParentalControlsServiceWin.cpp']
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += ['nsParentalControlsServiceCocoa.mm']
else:
SOURCES += ['nsParentalControlsServiceDefault.cpp']
diff --git a/toolkit/components/parentalcontrols/nsParentalControlsServiceCocoa.mm b/toolkit/components/parentalcontrols/nsParentalControlsServiceCocoa.mm
new file mode 100644
index 0000000000..0eb0184001
--- /dev/null
+++ b/toolkit/components/parentalcontrols/nsParentalControlsServiceCocoa.mm
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsParentalControlsService.h"
+#include "nsString.h"
+#include "nsIFile.h"
+
+#import <Cocoa/Cocoa.h>
+
+NS_IMPL_ISUPPORTS(nsParentalControlsService, nsIParentalControlsService)
+
+nsParentalControlsService::nsParentalControlsService() :
+ mEnabled(false)
+{
+ mEnabled = CFPreferencesAppValueIsForced(CFSTR("restrictWeb"),
+ CFSTR("com.apple.familycontrols.contentfilter"));
+}
+
+nsParentalControlsService::~nsParentalControlsService()
+{
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetParentalControlsEnabled(bool *aResult)
+{
+ *aResult = mEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetBlockFileDownloadsEnabled(bool *aResult)
+{
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::GetLoggingEnabled(bool *aResult)
+{
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::Log(int16_t aEntryType,
+ bool blocked,
+ nsIURI *aSource,
+ nsIFile *aTarget)
+{
+ // silently drop on the floor
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::RequestURIOverride(nsIURI *aTarget,
+ nsIInterfaceRequestor *aWindowContext,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::RequestURIOverrides(nsIArray *aTargets,
+ nsIInterfaceRequestor *aWindowContext,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsParentalControlsService::IsAllowed(int16_t aAction,
+ nsIURI *aUri,
+ bool *_retval)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
diff --git a/toolkit/components/passwordmgr/content/passwordManager.js b/toolkit/components/passwordmgr/content/passwordManager.js
index 4db3e81b9a..2d71d05f64 100644
--- a/toolkit/components/passwordmgr/content/passwordManager.js
+++ b/toolkit/components/passwordmgr/content/passwordManager.js
@@ -479,7 +479,12 @@ function HandleSignonKeyPress(e) {
return;
}
+#ifdef XP_MACOSX
+ if (e.keyCode == KeyboardEvent.DOM_VK_DELETE ||
+ e.keyCode == KeyboardEvent.DOM_VK_BACK_SPACE) {
+#else
if (e.keyCode == KeyboardEvent.DOM_VK_DELETE) {
+#endif
DeleteSignon();
}
}
diff --git a/toolkit/components/passwordmgr/content/passwordManager.xul b/toolkit/components/passwordmgr/content/passwordManager.xul
index b2713ae6c7..3ff8350eaf 100644
--- a/toolkit/components/passwordmgr/content/passwordManager.xul
+++ b/toolkit/components/passwordmgr/content/passwordManager.xul
@@ -126,8 +126,10 @@
<hbox align="end">
<hbox class="actionButtons" flex="1">
<spacer flex="1"/>
+#ifndef XP_MACOSX
<button oncommand="close();" icon="close"
label="&closebutton.label;" accesskey="&closebutton.accesskey;"/>
+#endif
</hbox>
</hbox>
</window>
diff --git a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
index 15a021bbab..59d84ced15 100644
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -35,7 +35,14 @@
#include <unistd.h>
#endif // defined(XP_WIN)
-#if defined(XP_UNIX)
+#if defined(XP_MACOSX)
+#include <mach/mach_init.h>
+#include <mach/mach_interface.h>
+#include <mach/mach_port.h>
+#include <mach/mach_types.h>
+#include <mach/message.h>
+#include <mach/thread_info.h>
+#elif defined(XP_UNIX)
#include <sys/time.h>
#include <sys/resource.h>
#endif // defined(XP_UNIX)
@@ -1232,7 +1239,30 @@ nsPerformanceStatsService::GetResources(uint64_t* userTime,
MOZ_ASSERT(userTime);
MOZ_ASSERT(systemTime);
-#if defined(XP_UNIX)
+#if defined(XP_MACOSX)
+ // On MacOS X, to get we per-thread data, we need to
+ // reach into the kernel.
+
+ mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
+ thread_basic_info_data_t info;
+ mach_port_t port = mach_thread_self();
+ kern_return_t err =
+ thread_info(/* [in] targeted thread*/ port,
+ /* [in] nature of information*/ THREAD_BASIC_INFO,
+ /* [out] thread information */ (thread_info_t)&info,
+ /* [inout] number of items */ &count);
+
+ // We do not need ability to communicate with the thread, so
+ // let's release the port.
+ mach_port_deallocate(mach_task_self(), port);
+
+ if (err != KERN_SUCCESS)
+ return NS_ERROR_FAILURE;
+
+ *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000;
+ *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000;
+
+#elif defined(XP_UNIX)
struct rusage rusage;
#if defined(RUSAGE_THREAD)
// Under Linux, we can obtain per-thread statistics
@@ -1276,7 +1306,7 @@ nsPerformanceStatsService::GetResources(uint64_t* userTime,
// Convert 100 ns to 1 us.
*userTime = userTimeInt.QuadPart / 10;
-#endif // defined(XP_UNIX) || defined(XP_WIN)
+#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
return NS_OK;
}
diff --git a/toolkit/components/places/PlacesUtils.jsm b/toolkit/components/places/PlacesUtils.jsm
index ab753ba86b..259fb7aa7d 100644
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -60,8 +60,14 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
// refresh instead.
const MIN_TRANSACTIONS_FOR_BATCH = 5;
-// The transferable system converts "\r\n" to "\n" where needed.
+// On Mac OSX, the transferable system converts "\r\n" to "\n\n", where
+// we really just want "\n". On other platforms, the transferable system
+// converts "\r\n" to "\n".
+#ifdef XP_MACOSX
+const NEWLINE = "\n";
+#else
const NEWLINE = "\r\n";
+#endif
function QI_node(aNode, aIID) {
var result = null;
diff --git a/toolkit/components/places/moz.build b/toolkit/components/places/moz.build
index 4c6ce89565..fda73f761f 100644
--- a/toolkit/components/places/moz.build
+++ b/toolkit/components/places/moz.build
@@ -68,9 +68,10 @@ if CONFIG['MOZ_PLACES']:
'PlacesSearchAutocompleteProvider.jsm',
'PlacesSyncUtils.jsm',
'PlacesTransactions.jsm',
- 'PlacesUtils.jsm',
]
+ EXTRA_PP_JS_MODULES += ['PlacesUtils.jsm']
+
EXTRA_COMPONENTS += [
'ColorAnalyzer.js',
'nsLivemarkService.js',
diff --git a/toolkit/components/printing/jar.mn b/toolkit/components/printing/jar.mn
index f77cf1c6aa..a0e9510304 100644
--- a/toolkit/components/printing/jar.mn
+++ b/toolkit/components/printing/jar.mn
@@ -6,9 +6,11 @@ toolkit.jar:
content/global/printdialog.js (content/printdialog.js)
content/global/printdialog.xul (content/printdialog.xul)
#ifdef XP_UNIX
+#ifndef XP_MACOSX
content/global/printjoboptions.js (content/printjoboptions.js)
content/global/printjoboptions.xul (content/printjoboptions.xul)
#endif
+#endif
content/global/printPageSetup.js (content/printPageSetup.js)
content/global/printPageSetup.xul (content/printPageSetup.xul)
* content/global/printPreviewBindings.xml (content/printPreviewBindings.xml)
diff --git a/toolkit/components/prompts/content/commonDialog.xul b/toolkit/components/prompts/content/commonDialog.xul
index a7621ccdfb..990b26586b 100644
--- a/toolkit/components/prompts/content/commonDialog.xul
+++ b/toolkit/components/prompts/content/commonDialog.xul
@@ -3,6 +3,7 @@
- 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/. -->
+
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://global/content/commonDialog.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/commonDialog.css" type="text/css"?>
@@ -62,9 +63,18 @@
<hbox id="iconContainer" align="start">
<image id="info.icon" class="spaced"/>
</hbox>
- <vbox id="infoContainer" pack="center">
+ <vbox id="infoContainer"
+#ifndef XP_MACOSX
+ pack="center"
+#endif
+ >
+ <!-- Only shown on OS X, since it has no dialog title -->
<description id="info.title"
+#ifndef XP_MACOSX
hidden="true"
+#else
+ style="margin-bottom: 1em"
+#endif
/>
<description id="info.body" context="contentAreaContextMenu" noinitialfocus="true"/>
</vbox>
diff --git a/toolkit/components/prompts/content/tabprompts.xml b/toolkit/components/prompts/content/tabprompts.xml
index 5355bb4cff..0ce13203c1 100644
--- a/toolkit/components/prompts/content/tabprompts.xml
+++ b/toolkit/components/prompts/content/tabprompts.xml
@@ -315,16 +315,27 @@
group="system" action="this.onKeyAction('default', event);"/>
<handler event="keypress" keycode="VK_ESCAPE"
group="system" action="this.onKeyAction('cancel', event);"/>
+#ifdef XP_MACOSX
+ <handler event="keypress" key="." modifiers="meta"
+ group="system" action="this.onKeyAction('cancel', event);"/>
+#endif
<handler event="focus" phase="capturing">
let bnum = this.args.defaultButtonNum || 0;
let defaultButton = this.ui["button" + bnum];
- // The default button is only marked as such when no other button has focus.
- // XUL buttons will react to pressing enter as a command, so you can't trigger
- // the default without tabbing to it or something that isn't a button.
+#ifdef XP_MACOSX
+ // On OS X, the default button always stays marked as such (until
+ // the entire prompt blurs).
+ defaultButton.setAttribute("default", true);
+#else
+ // On other platforms, the default button is only marked as such
+ // when no other button has focus. XUL buttons on not-OSX will
+ // react to pressing enter as a command, so you can't trigger the
+ // default without tabbing to it or something that isn't a button.
let focusedDefault = (event.originalTarget == defaultButton);
let someButtonFocused = event.originalTarget instanceof Ci.nsIDOMXULButtonElement;
defaultButton.setAttribute("default", focusedDefault || !someButtonFocused);
+#endif
</handler>
<handler event="blur">
// If focus shifted to somewhere else in the browser, don't make
diff --git a/toolkit/components/prompts/jar.mn b/toolkit/components/prompts/jar.mn
index 1b5da3f801..60ecbdcbc4 100644
--- a/toolkit/components/prompts/jar.mn
+++ b/toolkit/components/prompts/jar.mn
@@ -4,7 +4,7 @@
toolkit.jar:
content/global/commonDialog.js (content/commonDialog.js)
- content/global/commonDialog.xul (content/commonDialog.xul)
+* content/global/commonDialog.xul (content/commonDialog.xul)
content/global/commonDialog.css (content/commonDialog.css)
content/global/selectDialog.js (content/selectDialog.js)
content/global/selectDialog.xul (content/selectDialog.xul)
diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp
index 880ca79b21..bebc60f521 100644
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -1016,11 +1016,27 @@ nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
keyEvent->GetKeyCode(&k);
switch (k) {
case nsIDOMKeyEvent::DOM_VK_DELETE:
+#ifndef XP_MACOSX
mController->HandleDelete(&cancel);
break;
case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
mController->HandleText(&unused);
break;
+#else
+ case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
+ {
+ bool isShift = false;
+ keyEvent->GetShiftKey(&isShift);
+
+ if (isShift) {
+ mController->HandleDelete(&cancel);
+ } else {
+ mController->HandleText(&unused);
+ }
+
+ break;
+ }
+#endif
case nsIDOMKeyEvent::DOM_VK_PAGE_UP:
case nsIDOMKeyEvent::DOM_VK_PAGE_DOWN:
{
diff --git a/toolkit/components/startup/moz.build b/toolkit/components/startup/moz.build
index 7ee23d9ce8..5a922dd857 100644
--- a/toolkit/components/startup/moz.build
+++ b/toolkit/components/startup/moz.build
@@ -16,6 +16,8 @@ if CONFIG['MOZ_USERINFO']:
if CONFIG['OS_ARCH'] == 'WINNT':
# This file cannot be built in unified mode because of name clashes with Windows headers.
SOURCES += ['nsUserInfoWin.cpp']
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += ['nsUserInfoMac.mm']
else:
SOURCES += ['nsUserInfoUnix.cpp']
diff --git a/toolkit/components/startup/nsAppStartup.cpp b/toolkit/components/startup/nsAppStartup.cpp
index 11a000270e..7f67323b7d 100644
--- a/toolkit/components/startup/nsAppStartup.cpp
+++ b/toolkit/components/startup/nsAppStartup.cpp
@@ -273,6 +273,10 @@ nsAppStartup::Run(void)
// with a zombie process.
if (!mShuttingDown && mConsiderQuitStopper != 0) {
+#ifdef XP_MACOSX
+ EnterLastWindowClosingSurvivalArea();
+#endif
+
mRunning = true;
nsresult rv = mAppShell->Run();
@@ -308,10 +312,45 @@ nsAppStartup::Quit(uint32_t aMode)
// If we're considering quitting, we will only do so if:
if (ferocity == eConsiderQuit) {
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIAppShellService> appShell
+ (do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ bool hasHiddenPrivateWindow = false;
+ if (appShell) {
+ appShell->GetHasHiddenPrivateWindow(&hasHiddenPrivateWindow);
+ }
+ int32_t suspiciousCount = hasHiddenPrivateWindow ? 2 : 1;
+#endif
+
if (mConsiderQuitStopper == 0) {
// there are no windows...
ferocity = eAttemptQuit;
}
+#ifdef XP_MACOSX
+ else if (mConsiderQuitStopper == suspiciousCount) {
+ // ... or there is only a hiddenWindow left, and it's useless:
+
+ // Failure shouldn't be fatal, but will abort quit attempt:
+ if (!appShell)
+ return NS_OK;
+
+ bool usefulHiddenWindow;
+ appShell->GetApplicationProvidedHiddenWindow(&usefulHiddenWindow);
+ nsCOMPtr<nsIXULWindow> hiddenWindow;
+ appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ // If the remaining windows are useful, we won't quit:
+ nsCOMPtr<nsIXULWindow> hiddenPrivateWindow;
+ if (hasHiddenPrivateWindow) {
+ appShell->GetHiddenPrivateWindow(getter_AddRefs(hiddenPrivateWindow));
+ if ((!hiddenWindow && !hiddenPrivateWindow) || usefulHiddenWindow)
+ return NS_OK;
+ } else if (!hiddenWindow || usefulHiddenWindow) {
+ return NS_OK;
+ }
+
+ ferocity = eAttemptQuit;
+ }
+#endif
}
nsCOMPtr<nsIObserverService> obsService;
@@ -365,6 +404,10 @@ nsAppStartup::Quit(uint32_t aMode)
if (!mAttemptingQuit) {
mAttemptingQuit = true;
+#ifdef XP_MACOSX
+ // now even the Mac wants to quit when the last window is closed
+ ExitLastWindowClosingSurvivalArea();
+#endif
if (obsService)
obsService->NotifyObservers(nullptr, "quit-application-granted", nullptr);
}
diff --git a/toolkit/components/startup/nsUserInfoMac.h b/toolkit/components/startup/nsUserInfoMac.h
new file mode 100644
index 0000000000..822e0edd5d
--- /dev/null
+++ b/toolkit/components/startup/nsUserInfoMac.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+#ifndef __nsUserInfoMac_h
+#define __nsUserInfoMac_h
+
+#include "nsIUserInfo.h"
+#include "nsReadableUtils.h"
+
+class nsUserInfo: public nsIUserInfo
+{
+public:
+ nsUserInfo();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUSERINFO
+
+ nsresult GetPrimaryEmailAddress(nsCString &aEmailAddress);
+
+protected:
+ virtual ~nsUserInfo() {}
+};
+
+#endif /* __nsUserInfo_h */
diff --git a/toolkit/components/startup/nsUserInfoMac.mm b/toolkit/components/startup/nsUserInfoMac.mm
new file mode 100644
index 0000000000..1895cf1773
--- /dev/null
+++ b/toolkit/components/startup/nsUserInfoMac.mm
@@ -0,0 +1,84 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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 "nsUserInfoMac.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+
+#import <Cocoa/Cocoa.h>
+#import <AddressBook/AddressBook.h>
+
+NS_IMPL_ISUPPORTS(nsUserInfo, nsIUserInfo)
+
+nsUserInfo::nsUserInfo() {}
+
+NS_IMETHODIMP
+nsUserInfo::GetFullname(char16_t **aFullname)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ NS_ConvertUTF8toUTF16 fullName([NSFullUserName() UTF8String]);
+ *aFullname = ToNewUnicode(fullName);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetUsername(char **aUsername)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ nsAutoCString username([NSUserName() UTF8String]);
+ *aUsername = ToNewCString(username);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
+}
+
+nsresult
+nsUserInfo::GetPrimaryEmailAddress(nsCString &aEmailAddress)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ // Try to get this user's primary email from the system addressbook's "me card"
+ // (if they've filled it)
+ ABPerson *me = [[ABAddressBook sharedAddressBook] me];
+ ABMultiValue *emailAddresses = [me valueForProperty:kABEmailProperty];
+ if ([emailAddresses count] > 0) {
+ // get the index of the primary email, in case there are more than one
+ int primaryEmailIndex = [emailAddresses indexForIdentifier:[emailAddresses primaryIdentifier]];
+ aEmailAddress.Assign([[emailAddresses valueAtIndex:primaryEmailIndex] UTF8String]);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetEmailAddress(char **aEmailAddress)
+{
+ nsAutoCString email;
+ if (NS_SUCCEEDED(GetPrimaryEmailAddress(email)))
+ *aEmailAddress = ToNewCString(email);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetDomain(char **aDomain)
+{
+ nsAutoCString email;
+ if (NS_SUCCEEDED(GetPrimaryEmailAddress(email))) {
+ int32_t index = email.FindChar('@');
+ if (index != -1) {
+ // chop off everything before, and including the '@'
+ *aDomain = ToNewCString(Substring(email, index + 1));
+ }
+ }
+ return NS_OK;
+}
diff --git a/toolkit/components/thumbnails/PageThumbUtils.jsm b/toolkit/components/thumbnails/PageThumbUtils.jsm
index f91b364800..fb5d67ddb5 100644
--- a/toolkit/components/thumbnails/PageThumbUtils.jsm
+++ b/toolkit/components/thumbnails/PageThumbUtils.jsm
@@ -71,6 +71,18 @@ this.PageThumbUtils = {
let windowScale = aWindow ? aWindow.devicePixelRatio : systemScale;
let scale = Math.max(systemScale, windowScale);
+#ifdef XP_MACOSX
+ /** *
+ * On retina displays, we can sometimes go down this path
+ * without a window object. In those cases, force 2x scaling
+ * as the system scale doesn't represent the 2x scaling
+ * on OS X.
+ */
+ if (!aWindow) {
+ scale = 2;
+ }
+#endif
+
/** *
* THESE VALUES ARE DEFINED IN newtab.css and hard coded.
* If you change these values from the prefs,
diff --git a/toolkit/components/thumbnails/moz.build b/toolkit/components/thumbnails/moz.build
index 957bc7df0d..92ff2af946 100644
--- a/toolkit/components/thumbnails/moz.build
+++ b/toolkit/components/thumbnails/moz.build
@@ -11,11 +11,11 @@ EXTRA_COMPONENTS += [
EXTRA_JS_MODULES += [
'PageThumbs.jsm',
'PageThumbsWorker.js',
- 'PageThumbUtils.jsm',
]
EXTRA_PP_JS_MODULES += [
'BackgroundPageThumbs.jsm',
+ 'PageThumbUtils.jsm',
]
JAR_MANIFESTS += ['jar.mn']
diff --git a/toolkit/components/viewsource/content/viewPartialSource.xul b/toolkit/components/viewsource/content/viewPartialSource.xul
index c51744fd21..906eb8175a 100644
--- a/toolkit/components/viewsource/content/viewPartialSource.xul
+++ b/toolkit/components/viewsource/content/viewPartialSource.xul
@@ -99,8 +99,10 @@
label="&savePageCmd.label;" accesskey="&savePageCmd.accesskey;"/>
<menuitem command="cmd_pagesetup" id="menu_pageSetup"
label="&pageSetupCmd.label;" accesskey="&pageSetupCmd.accesskey;"/>
+#ifndef XP_MACOSX
<menuitem command="cmd_printpreview" id="menu_printPreview"
label="&printPreviewCmd.label;" accesskey="&printPreviewCmd.accesskey;"/>
+#endif
<menuitem key="key_print" command="cmd_print" id="menu_print"
label="&printCmd.label;" accesskey="&printCmd.accesskey;"/>
<menuseparator/>
diff --git a/toolkit/components/viewsource/content/viewSource.xul b/toolkit/components/viewsource/content/viewSource.xul
index 3ad45a9d9d..a08894f8bd 100644
--- a/toolkit/components/viewsource/content/viewSource.xul
+++ b/toolkit/components/viewsource/content/viewSource.xul
@@ -50,6 +50,10 @@
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(false);"/>
<command id="cmd_findPrevious"
oncommand="document.getElementById('FindToolbar').onFindAgainCommand(true);"/>
+#ifdef XP_MACOSX
+ <command id="cmd_findSelection"
+ oncommand="document.getElementById('FindToolbar').onFindSelectionCommand();"/>
+#endif
<command id="cmd_reload" oncommand="viewSourceChrome.reload();"/>
<command id="cmd_goToLine" oncommand="viewSourceChrome.promptAndGoToLine();" disabled="true"/>
<command id="cmd_highlightSyntax" oncommand="viewSourceChrome.toggleSyntaxHighlighting();"/>
@@ -85,13 +89,21 @@
<key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
<key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
<key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
+#ifdef XP_MACOSX
+ <key id="key_findSelection" key="&findSelectionCmd.commandkey;" command="cmd_findSelection" modifiers="accel"/>
+#endif
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
<key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
<key keycode="VK_BACK" command="Browser:Back"/>
<key keycode="VK_BACK" command="Browser:Forward" modifiers="shift"/>
+#ifndef XP_MACOSX
<key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
<key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>
+#else
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="accel" />
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="accel" />
+#endif
#ifdef XP_UNIX
<key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
<key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
@@ -138,8 +150,10 @@
label="&savePageCmd.label;" accesskey="&savePageCmd.accesskey;"/>
<menuitem command="cmd_pagesetup" id="menu_pageSetup"
label="&pageSetupCmd.label;" accesskey="&pageSetupCmd.accesskey;"/>
+#ifndef XP_MACOSX
<menuitem command="cmd_printpreview" id="menu_printPreview"
label="&printPreviewCmd.label;" accesskey="&printPreviewCmd.accesskey;"/>
+#endif
<menuitem key="key_print" command="cmd_print" id="menu_print"
label="&printCmd.label;" accesskey="&printCmd.accesskey;"/>
<menuseparator/>
diff --git a/toolkit/components/viewsource/jar.mn b/toolkit/components/viewsource/jar.mn
index 33818ae0db..00a1f19a46 100644
--- a/toolkit/components/viewsource/jar.mn
+++ b/toolkit/components/viewsource/jar.mn
@@ -7,6 +7,6 @@ toolkit.jar:
content/global/viewSource.js (content/viewSource.js)
* content/global/viewSource.xul (content/viewSource.xul)
content/global/viewPartialSource.js (content/viewPartialSource.js)
- content/global/viewPartialSource.xul (content/viewPartialSource.xul)
+* content/global/viewPartialSource.xul (content/viewPartialSource.xul)
content/global/viewSourceUtils.js (content/viewSourceUtils.js)
content/global/viewSource-content.js (content/viewSource-content.js)
diff --git a/toolkit/content/aboutProfiles.js b/toolkit/content/aboutProfiles.js
index 0e548fd0ff..29c5f67ad7 100644
--- a/toolkit/content/aboutProfiles.js
+++ b/toolkit/content/aboutProfiles.js
@@ -131,6 +131,8 @@ function display(profileData) {
let button = document.createElement('button');
#ifdef XP_WIN
let string = 'winOpenDir2';
+#elifdef XP_MACOSX
+ let string = 'macOpenDir';
#else
let string = 'openDir';
#endif
diff --git a/toolkit/content/aboutSupport.xhtml b/toolkit/content/aboutSupport.xhtml
index 4ae9927399..8afee18676 100644
--- a/toolkit/content/aboutSupport.xhtml
+++ b/toolkit/content/aboutSupport.xhtml
@@ -156,8 +156,12 @@
#ifdef XP_WIN
&aboutSupport.appBasicsProfileDirWinMac;
#else
+#ifdef XP_MACOSX
+ &aboutSupport.appBasicsProfileDirWinMac;
+#else
&aboutSupport.appBasicsProfileDir;
#endif
+#endif
</th>
<td>
@@ -165,8 +169,12 @@
#ifdef XP_WIN
&aboutSupport.showWin2.label;
#else
+#ifdef XP_MACOSX
+ &aboutSupport.showMac.label;
+#else
&aboutSupport.showDir.label;
#endif
+#endif
</button>
</td>
</tr>
diff --git a/toolkit/content/customizeToolbar.js b/toolkit/content/customizeToolbar.js
index 1775d9f52a..05151b905d 100644
--- a/toolkit/content/customizeToolbar.js
+++ b/toolkit/content/customizeToolbar.js
@@ -212,6 +212,10 @@ function wrapToolbarItems()
{
forEachCustomizableToolbar(function (toolbar) {
Array.forEach(toolbar.childNodes, function (item) {
+#ifdef XP_MACOSX
+ if (item.firstChild && item.firstChild.localName == "menubar")
+ return;
+#endif
if (isToolbarItem(item)) {
let wrapper = wrapToolbarItem(item);
cleanupItemForToolbar(item, wrapper);
diff --git a/toolkit/content/dialogOverlay.xul b/toolkit/content/dialogOverlay.xul
index 9d4d8b6138..3e064be8e0 100644
--- a/toolkit/content/dialogOverlay.xul
+++ b/toolkit/content/dialogOverlay.xul
@@ -3,7 +3,6 @@
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/.
-
WARNING!!! This file is obsoleted by the dialog.xml widget
-->
@@ -14,6 +13,44 @@
<script type="application/javascript" src="chrome://global/content/dialogOverlay.js"/>
+#ifdef XP_MACOSX
+#
+ <hbox id="okCancelButtons">
+ <spacer flex="1"/>
+ <button class="exit-dialog" id="Button3" label="" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="Button2" label="" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ <button class="right exit-dialog" id="ok" default="true" label="&okButton.label;" oncommand="doOKButton();"/>
+ </hbox>
+
+ <hbox id="okCancelHelpButtons">
+ <button class="exit-dialog" id="help" label="&helpButton.label;" oncommand="doHelpButton();"/>
+ <spacer flex="1"/>
+ <button class="exit-dialog" id="Button3" label="" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="Button2" label="" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ <button class="right exit-dialog" id="ok" default="true" label="&okButton.label;" oncommand="doOKButton();"/>
+ </hbox>
+
+ <hbox id="okCancelButtonsRight">
+ <spacer flex="1"/>
+ <button class="exit-dialog" id="Button3" label="" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="Button2" label="" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ <button class="right exit-dialog" id="ok" default="true" label="&okButton.label;" oncommand="doOKButton();"/>
+ </hbox>
+
+ <hbox id="okCancelHelpButtonsRight">
+ <button class="exit-dialog" id="help" label="&helpButton.label;" oncommand="doHelpButton();"/>
+ <spacer flex="1"/>
+ <button class="exit-dialog" id="Button3" label="" collapsed="true" oncommand="doButton3();"/>
+ <button class="exit-dialog" id="Button2" label="" collapsed="true" oncommand="doButton2();"/>
+ <button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
+ <button class="right exit-dialog" id="ok" default="true" label="&okButton.label;" oncommand="doOKButton();"/>
+ </hbox>
+#
+#else
+#
<hbox id="okCancelButtons">
<spacer flex="1"/>
<button class="right exit-dialog" id="ok" label="&okButton.label;" default="true" oncommand="doOKButton();"/>
@@ -49,10 +86,15 @@
<button class="exit-dialog" id="cancel" label="&cancelButton.label;" oncommand="doCancelButton();"/>
<button class="exit-dialog" id="help" label="&helpButton.label;" oncommand="doHelpButton();"/>
</hbox>
+#endif
<keyset id="dialogKeys">
<key keycode="VK_RETURN" oncommand="if (!document.getElementById('ok').disabled) doOKButton();"/>
<key keycode="VK_ESCAPE" oncommand="doCancelButton();"/>
+#ifdef XP_MACOSX
+ <key key="." modifiers="meta" oncommand="doCancelButton();"/>
+#
+#endif
</keyset>
</overlay>
diff --git a/toolkit/content/globalOverlay.js b/toolkit/content/globalOverlay.js
index d5ee131916..d8467f0a12 100644
--- a/toolkit/content/globalOverlay.js
+++ b/toolkit/content/globalOverlay.js
@@ -4,6 +4,12 @@
function closeWindow(aClose, aPromptFunction)
{
+#ifdef XP_MACOSX
+ // Closing the last window doesn't quit the application on OS X.
+ if (typeof(aPromptFunction) == "function" && !aPromptFunction()) {
+ return false;
+ }
+#else
var windowCount = 0;
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
@@ -23,6 +29,7 @@ function closeWindow(aClose, aPromptFunction)
return false;
if (windowCount != 1 && typeof(aPromptFunction) == "function" && !aPromptFunction())
return false;
+#endif
if (aClose) {
window.close();
diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn
index 1f1c880397..6494645086 100644
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -15,7 +15,7 @@ toolkit.jar:
#ifdef MOZ_OFFICIAL_BRANDING
* content/global/aboutRights.xhtml
#else
- content/global/aboutRights.xhtml (aboutRights-unbranded.xhtml)
+* content/global/aboutRights.xhtml (aboutRights-unbranded.xhtml)
#endif
content/global/aboutNetworking.js
content/global/aboutNetworking.xhtml
@@ -38,7 +38,7 @@ toolkit.jar:
* content/global/buildconfig.html
* content/global/contentAreaUtils.js
content/global/customizeToolbar.css
- content/global/customizeToolbar.js
+* content/global/customizeToolbar.js
content/global/customizeToolbar.xul
content/global/datepicker.xhtml
content/global/editMenuOverlay.js
@@ -47,7 +47,7 @@ toolkit.jar:
* content/global/finddialog.xul
content/global/findUtils.js
content/global/filepicker.properties
- content/global/globalOverlay.js
+* content/global/globalOverlay.js
content/global/mozilla.xhtml
#ifdef MOZ_PHOENIX
content/global/logopage.xhtml
@@ -63,7 +63,7 @@ toolkit.jar:
content/global/treeUtils.js
content/global/viewZoomOverlay.js
content/global/globalOverlay.xul
- content/global/dialogOverlay.xul
+* content/global/dialogOverlay.xul
content/global/dialogOverlay.js
content/global/inlineSpellCheckUI.js
content/global/nsClipboard.js
@@ -85,7 +85,7 @@ toolkit.jar:
content/global/bindings/editor.xml (widgets/editor.xml)
content/global/bindings/expander.xml (widgets/expander.xml)
content/global/bindings/filefield.xml (widgets/filefield.xml)
- content/global/bindings/findbar.xml (widgets/findbar.xml)
+* content/global/bindings/findbar.xml (widgets/findbar.xml)
content/global/bindings/general.xml (widgets/general.xml)
content/global/bindings/groupbox.xml (widgets/groupbox.xml)
content/global/bindings/listbox.xml (widgets/listbox.xml)
@@ -114,10 +114,13 @@ toolkit.jar:
content/global/bindings/timepicker.js (widgets/timepicker.js)
content/global/bindings/toolbar.xml (widgets/toolbar.xml)
content/global/bindings/toolbarbutton.xml (widgets/toolbarbutton.xml)
- content/global/bindings/tree.xml (widgets/tree.xml)
+* content/global/bindings/tree.xml (widgets/tree.xml)
content/global/bindings/videocontrols.xml (widgets/videocontrols.xml)
content/global/bindings/videocontrols.css (widgets/videocontrols.css)
* content/global/bindings/wizard.xml (widgets/wizard.xml)
+#ifdef XP_MACOSX
+ content/global/macWindowMenu.js
+#endif
content/global/svg/svgBindings.xml (/layout/svg/resources/content/svgBindings.xml)
content/global/gmp-sources/openh264.json (gmp-sources/openh264.json)
content/global/gmp-sources/widevinecdm.json (gmp-sources/widevinecdm.json)
diff --git a/toolkit/content/license.html b/toolkit/content/license.html
index 94d39959b2..0de306f36e 100644
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -5341,7 +5341,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>
-#if defined(XP_WIN) || defined(XP_LINUX)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
<hr>
diff --git a/toolkit/content/macWindowMenu.inc b/toolkit/content/macWindowMenu.inc
new file mode 100644
index 0000000000..c345ad8b7f
--- /dev/null
+++ b/toolkit/content/macWindowMenu.inc
@@ -0,0 +1,40 @@
+ <script type="application/javascript" src="chrome://global/content/macWindowMenu.js"/>
+ <commandset id="baseMenuCommandSet">
+ <command id="minimizeWindow"
+ label="&minimizeWindow.label;"
+ oncommand="window.minimize();" />
+ <command id="zoomWindow"
+ label="&zoomWindow.label;"
+ oncommand="zoomWindow();" />
+ </commandset>
+ <keyset id="baseMenuKeyset">
+ <key id="key_minimizeWindow"
+ command="minimizeWindow"
+ key="&minimizeWindow.key;"
+ modifiers="accel"/>
+ </keyset>
+ <menu id="windowMenu"
+ label="&windowMenu.label;"
+ datasources="rdf:window-mediator" ref="NC:WindowMediatorRoot"
+ onpopupshowing="macWindowMenuDidShow();"
+ hidden="false">
+ <template>
+ <rule>
+ <menupopup>
+ <menuitem uri="rdf:*"
+ label="rdf:http://home.netscape.com/NC-rdf#Name"
+ type="radio"
+ name="windowList"
+ oncommand="ShowWindowFromResource(event.target)"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup id="windowPopup">
+ <menuitem command="minimizeWindow" key="key_minimizeWindow"/>
+ <menuitem command="zoomWindow"/>
+ <!-- decomment when "BringAllToFront" is implemented
+ <menuseparator/>
+ <menuitem label="&bringAllToFront.label;" disabled="true"/> -->
+ <menuseparator id="sep-window-list"/>
+ </menupopup>
+ </menu>
diff --git a/toolkit/content/macWindowMenu.js b/toolkit/content/macWindowMenu.js
new file mode 100644
index 0000000000..46654c4f8a
--- /dev/null
+++ b/toolkit/content/macWindowMenu.js
@@ -0,0 +1,51 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+function macWindowMenuDidShow()
+{
+ var windowManagerDS =
+ Components.classes['@mozilla.org/rdf/datasource;1?name=window-mediator']
+ .getService(Components.interfaces.nsIWindowDataSource);
+ var sep = document.getElementById("sep-window-list");
+ // Using double parens to avoid warning
+ while ((sep = sep.nextSibling)) {
+ var url = sep.getAttribute('id');
+ var win = windowManagerDS.getWindowForResource(url);
+ if (win.document.documentElement.getAttribute("inwindowmenu") == "false")
+ sep.hidden = true;
+ else if (win == window)
+ sep.setAttribute("checked", "true");
+ }
+}
+
+function toOpenWindow( aWindow )
+{
+ // deminiaturize the window, if it's in the Dock
+ if (aWindow.windowState == STATE_MINIMIZED)
+ aWindow.restore();
+ aWindow.document.commandDispatcher.focusedWindow.focus();
+}
+
+function ShowWindowFromResource( node )
+{
+ var windowManagerDS =
+ Components.classes['@mozilla.org/rdf/datasource;1?name=window-mediator']
+ .getService(Components.interfaces.nsIWindowDataSource);
+
+ var desiredWindow = null;
+ var url = node.getAttribute('id');
+ desiredWindow = windowManagerDS.getWindowForResource( url );
+ if (desiredWindow)
+ toOpenWindow(desiredWindow);
+}
+
+function zoomWindow()
+{
+ if (window.windowState == STATE_NORMAL)
+ window.maximize();
+ else
+ window.restore();
+}
diff --git a/toolkit/content/widgets/dialog.xml b/toolkit/content/widgets/dialog.xml
index aff1621965..d83570ac0f 100644
--- a/toolkit/content/widgets/dialog.xml
+++ b/toolkit/content/widgets/dialog.xml
@@ -422,11 +422,15 @@
if (!event.defaultPrevented)
this.cancelDialog();
</handler>
+#ifdef XP_MACOSX
+ <handler event="keypress" key="." modifiers="meta" phase="capturing" action="this.cancelDialog();"/>
+#else
<handler event="focus" phase="capturing">
var btn = this.getButton(this.defaultButton);
if (btn)
btn.setAttribute("default", event.originalTarget == btn || !(event.originalTarget instanceof Components.interfaces.nsIDOMXULButtonElement));
</handler>
+#endif
</handlers>
</binding>
diff --git a/toolkit/content/widgets/findbar.xml b/toolkit/content/widgets/findbar.xml
index aae799554d..c312a6a25a 100644
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.xml
@@ -1232,6 +1232,39 @@
]]></body>
</method>
+#ifdef XP_MACOSX
+ <!--
+ - Fetches the currently selected text and sets that as the text to search
+ - next. This is a MacOS specific feature.
+ -->
+ <method name="onFindSelectionCommand">
+ <body><![CDATA[
+ let searchString = this.browser.finder.setSearchStringToSelection();
+ if (searchString)
+ this._findField.value = searchString;
+ ]]></body>
+ </method>
+
+ <method name="_onFindFieldFocus">
+ <body><![CDATA[
+ let prefsvc = this._prefsvc;
+ const kPref = "accessibility.typeaheadfind.prefillwithselection";
+ if (this.prefillWithSelection && prefsvc.getBoolPref(kPref))
+ return;
+
+ let clipboardSearchString = this._browser.finder.clipboardSearchString;
+ if (clipboardSearchString && this._findField.value != clipboardSearchString &&
+ !this._findField._willfullyDeleted) {
+ this._findField.value = clipboardSearchString;
+ this._findField._hadValue = true;
+ // Changing the search string makes the previous status invalid, so
+ // we better clear it here.
+ this._updateStatusUI();
+ }
+ ]]></body>
+ </method>
+#endif
+
<!--
- This handles all the result changes for both
- type-ahead-find and highlighting.
diff --git a/toolkit/content/widgets/optionsDialog.xml b/toolkit/content/widgets/optionsDialog.xml
index efd20663e5..f0cdba62f2 100644
--- a/toolkit/content/widgets/optionsDialog.xml
+++ b/toolkit/content/widgets/optionsDialog.xml
@@ -11,6 +11,7 @@
<binding id="optionsDialog"
extends="chrome://global/content/bindings/dialog.xml#dialog">
<content>
+#ifndef XP_MACOSX
<xul:hbox flex="1">
<xul:categoryBox anonid="prefsCategories">
<children/>
@@ -20,6 +21,16 @@
<xul:iframe anonid="panelFrame" name="panelFrame" style="width: 0px;" flex="1"/>
</xul:vbox>
</xul:hbox>
+#else
+ <xul:vbox flex="1">
+ <xul:categoryBox anonid="prefsCategories">
+ <children/>
+ </xul:categoryBox>
+ <xul:vbox flex="1">
+ <xul:iframe anonid="panelFrame" name="panelFrame" style="width: 0px;" flex="1"/>
+ </xul:vbox>
+ </xul:vbox>
+#endif
</content>
<implementation>
diff --git a/toolkit/content/widgets/preferences.xml b/toolkit/content/widgets/preferences.xml
index f7cb109488..11de032462 100644
--- a/toolkit/content/widgets/preferences.xml
+++ b/toolkit/content/widgets/preferences.xml
@@ -1146,7 +1146,11 @@
</handler>
<handler event="keypress"
+#ifdef XP_MACOSX
+ key="&openHelpMac.commandkey;" modifiers="accel"
+#else
keycode="&openHelp.commandkey;"
+#endif
phase="capturing">
<![CDATA[
var helpButton = this.getButton("help");
diff --git a/toolkit/content/widgets/tree.xml b/toolkit/content/widgets/tree.xml
index 19a1fa7727..bacea636db 100644
--- a/toolkit/content/widgets/tree.xml
+++ b/toolkit/content/widgets/tree.xml
@@ -774,6 +774,7 @@
event.preventDefault();
}
</handler>
+#ifndef XP_MACOSX
<!-- Use F2 key to enter text editing. -->
<handler event="keydown" keycode="VK_F2">
<![CDATA[
@@ -785,6 +786,7 @@
event.preventDefault();
]]>
</handler>
+#endif // XP_MACOSX
<handler event="keydown" keycode="VK_ESCAPE">
<![CDATA[
diff --git a/toolkit/content/widgets/wizard.xml b/toolkit/content/widgets/wizard.xml
index eb812f0652..3a8ec2cfef 100644
--- a/toolkit/content/widgets/wizard.xml
+++ b/toolkit/content/widgets/wizard.xml
@@ -465,6 +465,54 @@
</implementation>
</binding>
+#ifdef XP_MACOSX
+ <binding id="wizard-header" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
+ <content>
+ <xul:stack class="wizard-header-stack" flex="1">
+ <xul:vbox class="wizard-header-box-1">
+ <xul:vbox class="wizard-header-box-text">
+ <xul:label class="wizard-header-label" xbl:inherits="xbl:text=label"/>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:hbox class="wizard-header-box-icon">
+ <xul:spacer flex="1"/>
+ <xul:image class="wizard-header-icon" xbl:inherits="src=iconsrc"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+ </binding>
+
+ <binding id="wizard-buttons" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
+ <content>
+ <xul:vbox flex="1">
+ <xul:hbox class="wizard-buttons-btm">
+ <xul:button class="wizard-button" dlgtype="extra1" hidden="true"/>
+ <xul:button class="wizard-button" dlgtype="extra2" hidden="true"/>
+ <xul:button label="&button-cancel-mac.label;" class="wizard-button" dlgtype="cancel"/>
+ <xul:spacer flex="1"/>
+ <xul:button label="&button-back-mac.label;" accesskey="&button-back-mac.accesskey;"
+ class="wizard-button wizard-nav-button" dlgtype="back"/>
+ <xul:button label="&button-next-mac.label;" accesskey="&button-next-mac.accesskey;"
+ class="wizard-button wizard-nav-button" dlgtype="next"
+ default="true" xbl:inherits="hidden=lastpage" />
+ <xul:button label="&button-finish-mac.label;" class="wizard-button"
+ dlgtype="finish" default="true" xbl:inherits="hidden=hidefinishbutton" />
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+
+ <implementation>
+ <method name="onPageChange">
+ <body><![CDATA[
+ this.setAttribute("hidefinishbutton", !(this.getAttribute("lastpage") == "true"));
+ ]]></body>
+ </method>
+ </implementation>
+
+ </binding>
+
+#else
+
<binding id="wizard-header" extends="chrome://global/content/bindings/wizard.xml#wizard-base">
<content>
<xul:hbox class="wizard-header-box-1" flex="1">
@@ -554,5 +602,6 @@
</property>
</implementation>
</binding>
+#endif
</bindings>
diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css
index 7c16cab703..0aa0d3a217 100644
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -243,6 +243,11 @@ caption {
/******** draggable elements *********/
+%ifdef XP_MACOSX
+titlebar,
+toolbar:not([nowindowdrag="true"]):not([customizing="true"]),
+statusbar:not([nowindowdrag="true"]),
+%endif
windowdragbox {
-moz-window-dragging: drag;
}
@@ -283,6 +288,12 @@ toolbar[customizing="true"][hidden="true"] {
display: -moz-box;
}
+%ifdef XP_MACOSX
+toolbar[type="menubar"] {
+ min-height: 0 !important;
+ border: 0 !important;
+}
+%else
toolbar[type="menubar"][autohide="true"] {
-moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-menubar-autohide");
overflow: hidden;
@@ -294,6 +305,7 @@ toolbar[type="menubar"][autohide="true"][inactive="true"]:not([customizing="true
-moz-appearance: none !important;
border-style: none !important;
}
+%endif
%ifdef MOZ_WIDGET_GTK
window[shellshowingmenubar="true"] menubar,
@@ -487,9 +499,15 @@ panel[arrowposition="start_after"]:-moz-locale-dir(rtl) {
%endif
+%ifdef XP_MACOSX
+.statusbar-resizerpanel {
+ display: none;
+}
+%else
window[sizemode="maximized"] statusbarpanel.statusbar-resizerpanel {
visibility: collapse;
}
+%endif
/******** grid **********/
@@ -1000,6 +1018,9 @@ autorepeatbutton {
statusbar {
-moz-binding: url("chrome://global/content/bindings/general.xml#statusbar");
+%ifdef XP_MACOSX
+ padding-right: 14px;
+%endif
}
statusbarpanel {
diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build
index 81f07be661..c2f12b7763 100644
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -12,8 +12,14 @@ def Libxul_defines():
@template
def Libxul(name):
- GeckoSharedLibrary(name, linkage=None)
- SHARED_LIBRARY_NAME = 'xul'
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
+ # This is going to be a framework named "XUL", not an ordinary library named
+ # "libxul.dylib"
+ GeckoFramework(name, linkage=None)
+ SHARED_LIBRARY_NAME = 'XUL'
+ else:
+ GeckoSharedLibrary(name, linkage=None)
+ SHARED_LIBRARY_NAME = 'xul'
DELAYLOAD_DLLS += [
'comdlg32.dll',
@@ -121,6 +127,9 @@ if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT'] or \
'freetype',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ CXXFLAGS += CONFIG['TK_CFLAGS']
+
if CONFIG['MOZ_WEBRTC']:
if CONFIG['OS_TARGET'] == 'WINNT':
OS_LIBS += [
@@ -134,6 +143,19 @@ if CONFIG['MOZ_WEBRTC']:
'wininet',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ OS_LIBS += [
+ '-framework OpenGL',
+ '-framework SystemConfiguration',
+ '-framework AVFoundation',
+ '-framework CoreMedia',
+ '-framework IOKit',
+ '-F%s' % CONFIG['MACOS_PRIVATE_FRAMEWORKS_DIR'],
+ '-framework CoreUI',
+ '-framework CoreSymbolication',
+ 'cups',
+ ]
+
if CONFIG['MOZ_WMF']:
OS_LIBS += [
'mfuuid',
@@ -191,6 +213,9 @@ if 'rtsp' in CONFIG['NECKO_PROTOCOLS']:
OS_LIBS += CONFIG['ICONV_LIBS']
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
+ OS_LIBS += CONFIG['TK_LIBS']
+
if CONFIG['MOZ_SNDIO']:
OS_LIBS += [
'sndio',
@@ -276,11 +301,14 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
]
if CONFIG['COMPILE_ENVIRONMENT']:
- full_libname = '%s%s%s' % (
- CONFIG['DLL_PREFIX'],
- LIBRARY_NAME,
- CONFIG['DLL_SUFFIX']
- )
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):
+ full_libname = SHARED_LIBRARY_NAME
+ else:
+ full_libname = '%s%s%s' % (
+ CONFIG['DLL_PREFIX'],
+ LIBRARY_NAME,
+ CONFIG['DLL_SUFFIX']
+ )
GENERATED_FILES += ['dependentlibs.list']
GENERATED_FILES['dependentlibs.list'].script = 'dependentlibs.py:gen_list'
GENERATED_FILES['dependentlibs.list'].inputs = [
diff --git a/toolkit/modules/AppConstants.jsm b/toolkit/modules/AppConstants.jsm
index 4f018355ab..b5abc3c149 100644
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -93,6 +93,8 @@ this.AppConstants = Object.freeze({
"linux",
#elif XP_WIN
"win",
+#elif XP_MACOSX
+ "macosx",
#elif XP_LINUX
"linux",
#else
diff --git a/toolkit/modules/LightweightThemeConsumer.jsm b/toolkit/modules/LightweightThemeConsumer.jsm
index 9419fdcf23..4010a9ff22 100644
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -147,6 +147,29 @@ LightweightThemeConsumer.prototype = {
footer.removeAttribute("lwthemefooter");
}
+#if defined(XP_MACOSX) && defined(MOZ_CAN_DRAW_IN_TITLEBAR)
+ // On OS X, we extend the lightweight theme into the titlebar, which means setting
+ // the chromemargin attribute. Some XUL applications already draw in the titlebar,
+ // so we need to save the chromemargin value before we overwrite it with the value
+ // that lets us draw in the titlebar. We stash this value on the root attribute so
+ // that XUL applications have the ability to invalidate the saved value.
+ if (stateChanging) {
+ if (!root.hasAttribute("chromemargin-nonlwtheme")) {
+ root.setAttribute("chromemargin-nonlwtheme", root.getAttribute("chromemargin"));
+ }
+
+ if (active) {
+ root.setAttribute("chromemargin", "0,-1,-1,-1");
+ } else {
+ let defaultChromemargin = root.getAttribute("chromemargin-nonlwtheme");
+ if (defaultChromemargin) {
+ root.setAttribute("chromemargin", defaultChromemargin);
+ } else {
+ root.removeAttribute("chromemargin");
+ }
+ }
+ }
+#endif
Services.obs.notifyObservers(this._win, "lightweight-theme-window-updated",
JSON.stringify(aData));
}
diff --git a/toolkit/modules/UpdateUtils.jsm b/toolkit/modules/UpdateUtils.jsm
index 5e3a2e100a..5acf395d3d 100644
--- a/toolkit/modules/UpdateUtils.jsm
+++ b/toolkit/modules/UpdateUtils.jsm
@@ -168,6 +168,17 @@ XPCOMUtils.defineLazyGetter(UpdateUtils, "ABI", function() {
Cu.reportError("XPCOM ABI unknown");
}
+#ifdef XP_MACOSX
+ // Mac universal build should report a different ABI than either macppc
+ // or mactel.
+ let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
+ getService(Ci.nsIMacUtils);
+
+ if (macutils.isUniversalBinary) {
+ abi += "-u-" + macutils.architecturesInBinary;
+ }
+#endif
+
return abi;
});
diff --git a/toolkit/modules/WindowDraggingUtils.jsm b/toolkit/modules/WindowDraggingUtils.jsm
index 5be8814de1..a7986c8b4a 100644
--- a/toolkit/modules/WindowDraggingUtils.jsm
+++ b/toolkit/modules/WindowDraggingUtils.jsm
@@ -2,7 +2,7 @@
* 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/. */
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
const HAVE_CSS_WINDOW_DRAG_SUPPORT = true;
#else
const HAVE_CSS_WINDOW_DRAG_SUPPORT = false;
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
index e38b1b0b75..8509eb7cd3 100644
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -94,7 +94,7 @@ EXTRA_PP_JS_MODULES += [
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
-if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
DEFINES['HAVE_SHELL_SERVICE'] = 1
EXTRA_PP_JS_MODULES += [
@@ -103,7 +103,7 @@ EXTRA_PP_JS_MODULES += [
]
-EXTRA_JS_MODULES += [
+EXTRA_PP_JS_MODULES += [
'LightweightThemeConsumer.jsm',
]
diff --git a/toolkit/moz.build b/toolkit/moz.build
index 9bf579bb26..9e05b2a4fb 100644
--- a/toolkit/moz.build
+++ b/toolkit/moz.build
@@ -39,5 +39,7 @@ DIRS += ['xre']
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
DIRS += ['system/unixproxy']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ DIRS += ['system/osxproxy']
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
DIRS += ['system/windowsproxy']
diff --git a/toolkit/moz.configure b/toolkit/moz.configure
index e3d3eca63d..643b4fbe34 100644
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -48,7 +48,7 @@ set_config('L10NBASEDIR', l10n_base)
# reason.
option('--enable-default-toolkit', nargs=1,
choices=('cairo-windows', 'cairo-gtk2', 'cairo-gtk2-x11', 'cairo-gtk3',
- 'cairo-uikit'),
+ 'cairo-cocoa', 'cairo-uikit'),
help='Select default toolkit')
@depends('--enable-default-toolkit', target)
@@ -58,6 +58,8 @@ def toolkit(value, target):
os = target.os
if target.os == 'WINNT':
platform_choices = ('cairo-windows',)
+ elif target.os == 'OSX':
+ platform_choices = ('cairo-cocoa',)
elif target.os == 'iOS':
platform_choices = ('cairo-uikit',)
else:
@@ -180,7 +182,7 @@ option(env='MOZ_INSTRUMENT_EVENT_LOOP',
@depends('MOZ_INSTRUMENT_EVENT_LOOP', toolkit)
def instrument_event_loop(value, toolkit):
- if value or (toolkit in ('windows', 'gtk2', 'gtk3') and value.origin == 'default'):
+ if value or (toolkit in ('windows', 'gtk2', 'gtk3', 'cocoa') and value.origin == 'default'):
return True
set_config('MOZ_INSTRUMENT_EVENT_LOOP', instrument_event_loop)
@@ -266,7 +268,7 @@ add_old_configure_assignment('FT2_CFLAGS',
# ==============================================================
@depends(toolkit)
def applemedia(toolkit):
- if toolkit in ('uikit'):
+ if toolkit in ('cocoa', 'uikit'):
return True
set_config('MOZ_APPLEMEDIA', applemedia)
diff --git a/toolkit/mozapps/downloads/content/downloads.xul b/toolkit/mozapps/downloads/content/downloads.xul
index b5ca87a0c2..4bca152982 100644
--- a/toolkit/mozapps/downloads/content/downloads.xul
+++ b/toolkit/mozapps/downloads/content/downloads.xul
@@ -6,8 +6,10 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifdef XP_UNIX
+#ifndef XP_MACOSX
#define XP_GNOME 1
#endif
+#endif
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://mozapps/content/downloads/downloads.css"?>
@@ -50,6 +52,9 @@
<key keycode="VK_RETURN" oncommand="doDefaultForSelected();"/>
<key id="key_pauseResume" key=" " oncommand="performCommand('cmd_pauseResume');"/>
<key id="key_removeFromList" keycode="VK_DELETE" oncommand="performCommand('cmd_removeFromList');"/>
+#ifdef XP_MACOSX
+ <key id="key_removeFromList2" keycode="VK_BACK" oncommand="performCommand('cmd_removeFromList');"/>
+#endif
<key id="key_close" key="&cmd.close.commandKey;" oncommand="closeWindow(true);" modifiers="accel"/>
#ifdef XP_GNOME
<key id="key_close2" key="&cmd.close2Unix.commandKey;" oncommand="closeWindow(true);" modifiers="accel,shift"/>
@@ -95,8 +100,13 @@
oncommand="performCommand('cmd_open');"
cmd="cmd_open"/>
<menuitem id="menuitem_show"
+#ifdef XP_MACOSX
+ label="&cmd.showMac.label;"
+ accesskey="&cmd.showMac.accesskey;"
+#else
label="&cmd.show.label;"
accesskey="&cmd.show.accesskey;"
+#endif
oncommand="performCommand('cmd_show');"
cmd="cmd_show"/>
diff --git a/toolkit/mozapps/downloads/content/unknownContentType.xul b/toolkit/mozapps/downloads/content/unknownContentType.xul
index 42d356e9f9..1cea3a8fbf 100644
--- a/toolkit/mozapps/downloads/content/unknownContentType.xul
+++ b/toolkit/mozapps/downloads/content/unknownContentType.xul
@@ -68,7 +68,11 @@
<hbox id="openHandlerBox" flex="1" align="center"/>
<hbox flex="1" align="center">
<button id="chooseButton" oncommand="dialog.chooseApp();"
+#ifdef XP_MACOSX
+ label="&chooseHandlerMac.label;" accesskey="&chooseHandlerMac.accesskey;"/>
+#else
label="&chooseHandler.label;" accesskey="&chooseHandler.accesskey;"/>
+#endif
</hbox>
</deck>
</hbox>
diff --git a/toolkit/mozapps/downloads/nsHelperAppDlg.js b/toolkit/mozapps/downloads/nsHelperAppDlg.js
index 0e5cfdaf0e..90d38c90b0 100644
--- a/toolkit/mozapps/downloads/nsHelperAppDlg.js
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js
@@ -675,7 +675,11 @@ nsUnknownContentTypeDialog.prototype = {
// getPath:
getPath: function (aFile) {
+#ifdef XP_MACOSX
+ return aFile.leafName || aFile.path;
+#else
return aFile.path;
+#endif
},
// initAppAndSaveToDiskValues:
@@ -983,6 +987,12 @@ nsUnknownContentTypeDialog.prototype = {
return file.getVersionInfoField("FileDescription");
} catch (e) {}
}
+#elifdef XP_MACOSX
+ if (file instanceof Components.interfaces.nsILocalFileMac) {
+ try {
+ return file.bundleDisplayName;
+ } catch (e) {}
+ }
#endif
return file.leafName;
diff --git a/toolkit/mozapps/extensions/GMPUtils.jsm b/toolkit/mozapps/extensions/GMPUtils.jsm
index 593fc3c8da..3c691610d5 100644
--- a/toolkit/mozapps/extensions/GMPUtils.jsm
+++ b/toolkit/mozapps/extensions/GMPUtils.jsm
@@ -39,6 +39,11 @@ this.GMPUtils = {
* The plugin to check.
*/
isPluginHidden: function(aPlugin) {
+ if (this._is32bitModeMacOS()) {
+ // GMPs are hidden on MacOS when running in 32 bit mode.
+ // See bug 1291537.
+ return true;
+ }
if (!aPlugin.isEME) {
return false;
}
@@ -66,7 +71,7 @@ this.GMPUtils = {
}
if (aPlugin.id == WIDEVINE_ID) {
-#if defined(XP_WIN) || defined(XP_LINUX)
+#if defined(XP_WIN) || defined(XP_LINUX) || defined(XP_MACOSX)
// The Widevine plugin is available for Windows versions Vista and later,
// Mac OSX, and Linux.
return true;
@@ -78,6 +83,14 @@ this.GMPUtils = {
return true;
},
+ _is32bitModeMacOS: function() {
+#ifdef XP_MACOSX
+ return Services.appinfo.XPCOMABI.split("-")[0] == "x86";
+#else
+ return false;
+#endif
+ },
+
/**
* Checks whether or not a given plugin is visible in the addons manager
* UI and the "enable DRM" notification box. This can be used to test
diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js
index fe84bc4602..2ca3898f17 100644
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1878,7 +1878,11 @@ var gHeader = {
// XXXunf Temporary until bug 371900 is fixed.
let key = document.getElementById("focusSearch").getAttribute("key");
+#ifdef XP_MACOSX
+ let keyModifier = aEvent.metaKey;
+#else
let keyModifier = aEvent.ctrlKey;
+#endif
if (String.fromCharCode(aEvent.charCode) == key && keyModifier) {
this.focusSearchBox();
return;
diff --git a/toolkit/mozapps/extensions/content/update.xul b/toolkit/mozapps/extensions/content/update.xul
index 9f5f35196e..cd74f27143 100644
--- a/toolkit/mozapps/extensions/content/update.xul
+++ b/toolkit/mozapps/extensions/content/update.xul
@@ -100,7 +100,11 @@
oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
</vbox>
<separator flex="1"/>
+#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
<separator class="thin"/>
</wizardpage>
@@ -140,7 +144,11 @@
<description flex="1">&installerrors.intro.label;</description>
</hbox>
<separator flex="1"/>
+#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
<separator class="thin"/>
</wizardpage>
@@ -153,7 +161,11 @@
<description flex="1">&adminDisabled.warning.label;</description>
</hbox>
<separator flex="1"/>
+#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
<separator class="thin"/>
</wizardpage>
@@ -172,7 +184,11 @@
oncommand="gUpdateWizard.shouldAutoCheck = this.checked;"/>
</vbox>
<separator flex="1"/>
+#ifndef XP_MACOSX
<label>&clickFinish.label;</label>
+#else
+ <label>&clickFinish.labelMac;</label>
+#endif
<separator class="thin"/>
</wizardpage>
diff --git a/toolkit/mozapps/extensions/jar.mn b/toolkit/mozapps/extensions/jar.mn
index 878be4df13..e95d93ca02 100644
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -21,7 +21,7 @@ toolkit.jar:
content/mozapps/extensions/selectAddons.xml (content/selectAddons.xml)
content/mozapps/extensions/selectAddons.js (content/selectAddons.js)
content/mozapps/extensions/selectAddons.css (content/selectAddons.css)
- content/mozapps/extensions/update.xul (content/update.xul)
+* content/mozapps/extensions/update.xul (content/update.xul)
content/mozapps/extensions/update.js (content/update.js)
content/mozapps/extensions/eula.xul (content/eula.xul)
content/mozapps/extensions/eula.js (content/eula.js)
diff --git a/toolkit/mozapps/update/common/updatecommon.cpp b/toolkit/mozapps/update/common/updatecommon.cpp
index 17a57eae38..aff7d72602 100644
--- a/toolkit/mozapps/update/common/updatecommon.cpp
+++ b/toolkit/mozapps/update/common/updatecommon.cpp
@@ -41,6 +41,8 @@ void UpdateLog::Init(NS_tchar* sourcePath,
// updater process if the elevated updater process has written the log.
DeleteFileW(mDstFilePath);
}
+#elif XP_MACOSX
+ logFP = NS_tfopen(mDstFilePath, NS_T("w"));
#else
// On platforms that have an updates directory in the installation directory
// (e.g. platforms other than Windows and Mac) the update log is written to
@@ -59,7 +61,7 @@ void UpdateLog::Finish()
return;
}
-#if !defined(XP_WIN)
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
const int blockSize = 1024;
char buffer[blockSize];
fflush(logFP);
diff --git a/toolkit/mozapps/update/common/updatedefines.h b/toolkit/mozapps/update/common/updatedefines.h
index 0db182219a..49fbde6f2f 100644
--- a/toolkit/mozapps/update/common/updatedefines.h
+++ b/toolkit/mozapps/update/common/updatedefines.h
@@ -100,6 +100,10 @@ static inline int mywcsprintf(WCHAR* dest, size_t count, const WCHAR* fmt, ...)
#endif
# include <dirent.h>
+#ifdef XP_MACOSX
+# include <sys/time.h>
+#endif
+
# define LOG_S "%s"
# define NS_T(str) str
# define NS_SLASH NS_T('/')
diff --git a/toolkit/mozapps/update/nsUpdateService.js b/toolkit/mozapps/update/nsUpdateService.js
index 9bea453dbb..ce9be2c512 100644
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -315,6 +315,36 @@ function areDirectoryEntriesWriteable(aDir) {
}
/**
+ * OSX only function to determine if the user requires elevation to be able to
+ * write to the application bundle.
+ *
+ * @return true if elevation is required, false otherwise
+ */
+function getElevationRequired() {
+#ifdef XP_MACOSX
+ try {
+ // Recursively check that the application bundle (and its descendants) can
+ // be written to.
+ LOG("getElevationRequired - recursively testing write access on " +
+ getInstallDirRoot().path);
+ if (!getInstallDirRoot().isWritable() ||
+ !areDirectoryEntriesWriteable(getInstallDirRoot())) {
+ LOG("getElevationRequired - unable to write to application bundle, " +
+ "elevation required");
+ return true;
+ }
+ } catch (ex) {
+ LOG("getElevationRequired - unable to write to application bundle, " +
+ "elevation required. Exception: " + ex);
+ return true;
+ }
+ LOG("getElevationRequired - able to write to application bundle, elevation " +
+ "not required");
+#endif
+ return false;
+}
+
+/**
* Determines whether or not an update can be applied. This is always true on
* Windows when the service is used. Also, this is always true on OSX because we
* offer users the option to perform an elevated update when necessary.
@@ -322,6 +352,7 @@ function areDirectoryEntriesWriteable(aDir) {
* @return true if an update can be applied, false otherwise
*/
function getCanApplyUpdates() {
+#ifndef XP_MACOSX
try {
let updateTestFile = getUpdateFile([FILE_UPDATE_TEST]);
LOG("getCanApplyUpdates - testing write access " + updateTestFile.path);
@@ -400,6 +431,7 @@ function getCanApplyUpdates() {
// No write privileges to install directory
return false;
}
+#endif // !XP_MACOSX
LOG("getCanApplyUpdates - able to apply updates");
return true;
@@ -412,17 +444,28 @@ function getCanApplyUpdates() {
* @return true if updates can be staged for this session.
*/
XPCOMUtils.defineLazyGetter(this, "gCanStageUpdatesSession", function aus_gCSUS() {
+ if (getElevationRequired()) {
+ LOG("gCanStageUpdatesSession - unable to stage updates because elevation " +
+ "is required.");
+ return false;
+ }
+
try {
let updateTestFile;
+#ifdef XP_MACOSX
+ updateTestFile = getUpdateFile([FILE_UPDATE_TEST]);
+#else
updateTestFile = getInstallDirRoot();
updateTestFile.append(FILE_UPDATE_TEST);
+#endif
LOG("gCanStageUpdatesSession - testing write access " +
updateTestFile.path);
testWriteAccess(updateTestFile, true);
- // On all platforms, we need to test the parent directory as well,
- // as we need to be able to move files in that directory during the
+#ifndef XP_MACOSX
+ // On all platforms except Mac, we need to test the parent directory as
+ // well, as we need to be able to move files in that directory during the
// replacing step.
updateTestFile = getInstallDirRoot().parent;
updateTestFile.append(FILE_UPDATE_TEST);
@@ -431,6 +474,7 @@ XPCOMUtils.defineLazyGetter(this, "gCanStageUpdatesSession", function aus_gCSUS(
updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
FileUtils.PERMS_DIRECTORY);
updateTestFile.remove(false);
+#endif // !XP_MACOSX
} catch (e) {
LOG("gCanStageUpdatesSession - unable to stage updates. Exception: " +
e);
@@ -549,6 +593,10 @@ function getAppBaseDir() {
*/
function getInstallDirRoot() {
let dir = getAppBaseDir();
+#ifdef XP_MACOSX
+ // On Mac, we store the Updated.app directory inside the bundle directory.
+ dir = dir.parent.parent;
+#endif
return dir;
}
@@ -832,7 +880,31 @@ function handleUpdateFailure(update, errorCode) {
cancelations++;
Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, cancelations);
+#ifdef XP_MACOSX
+ let osxCancelations = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
+ osxCancelations++;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX,
+ osxCancelations);
+ let maxCancels = Services.prefs.getIntPref(
+ PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
+ DEFAULT_CANCELATIONS_OSX_MAX);
+ // Prevent the preference from setting a value greater than 5.
+ maxCancels = Math.min(maxCancels, 5);
+ if (osxCancelations >= maxCancels) {
+ cleanupActiveUpdate();
+ } else {
+ writeStatusFile(getUpdatesDir(),
+ update.state = STATE_PENDING_ELEVATE);
+ }
+ update.statusText = gUpdateBundle.GetStringFromName("elevationFailure");
+ update.QueryInterface(Ci.nsIWritablePropertyBag);
+ update.setProperty("patchingFailed", "elevationFailure");
+ let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Ci.nsIUpdatePrompt);
+ prompter.showUpdateError(update);
+#else
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
+#endif
return true;
}
@@ -1778,6 +1850,58 @@ UpdateService.prototype = {
});
let update = minorUpdate || majorUpdate;
+#ifdef XP_MACOSX
+ if (update) {
+ if (getElevationRequired()) {
+ let installAttemptVersion = Services.prefs.getCharPref(
+ PREF_APP_UPDATE_ELEVATE_VERSION,
+ "");
+ if (vc.compare(installAttemptVersion, update.appVersion) != 0) {
+ Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_VERSION,
+ update.appVersion);
+ if (Services.prefs.prefHasUserValue(
+ PREF_APP_UPDATE_CANCELATIONS_OSX)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
+ }
+ } else {
+ let numCancels = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
+ let rejectedVersion = Services.prefs.getCharPref(PREF_APP_UPDATE_ELEVATE_NEVER, "");
+ let maxCancels = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
+ DEFAULT_CANCELATIONS_OSX_MAX);
+ if (numCancels >= maxCancels) {
+ LOG("UpdateService:selectUpdate - the user requires elevation to " +
+ "install this update, but the user has exceeded the max " +
+ "number of elevation attempts.");
+ update.elevationFailure = true;
+ } else if (vc.compare(rejectedVersion, update.appVersion) == 0) {
+ LOG("UpdateService:selectUpdate - the user requires elevation to " +
+ "install this update, but elevation is disabled for this " +
+ "version.");
+ update.elevationFailure = true;
+ } else {
+ LOG("UpdateService:selectUpdate - the user requires elevation to " +
+ "install the update.");
+ }
+ }
+ } else {
+ // Clear elevation-related prefs since they no longer apply (the user
+ // may have gained write access to the Firefox directory or an update
+ // was executed with a different profile).
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_VERSION)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_VERSION);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
+ }
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
+ }
+ }
+ }
+#endif
return update;
},
@@ -1895,8 +2019,7 @@ UpdateService.prototype = {
* See nsIUpdateService.idl
*/
get elevationRequired() {
- /** Mac Stub, but keeping this for now to not break the API **/
- return false;
+ return getElevationRequired();
},
/**
@@ -3147,7 +3270,11 @@ Downloader.prototype = {
"max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
if (Components.isSuccessCode(status)) {
if (this._verifyDownload()) {
- state = STATE_PENDING;
+ if (getElevationRequired()) {
+ state = STATE_PENDING_ELEVATE;
+ } else {
+ state = STATE_PENDING;
+ }
if (this.background) {
shouldShowPrompt = !getCanStageUpdates();
}
diff --git a/toolkit/mozapps/update/updater/Makefile.in b/toolkit/mozapps/update/updater/Makefile.in
index c1cfcead7e..84a843d185 100644
--- a/toolkit/mozapps/update/updater/Makefile.in
+++ b/toolkit/mozapps/update/updater/Makefile.in
@@ -14,3 +14,16 @@ endif
endif
include $(topsrcdir)/config/rules.mk
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+export::
+ sed -e 's/%MOZ_MACBUNDLE_ID%/$(MOZ_MACBUNDLE_ID)/' $(srcdir)/macbuild/Contents/Info.plist.in > $(DIST)/bin/Info.plist
+libs::
+ $(NSINSTALL) -D $(DIST)/bin/updater.app
+ rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/updater.app
+ rsync -a -C $(DIST)/bin/Info.plist $(DIST)/bin/updater.app/Contents
+ sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
+ iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/updater.app/Contents/Resources/English.lproj/InfoPlist.strings
+ $(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS
+ $(NSINSTALL) $(DIST)/bin/org.mozilla.updater $(DIST)/bin/updater.app/Contents/MacOS
+endif
diff --git a/toolkit/mozapps/update/updater/launchchild_osx.mm b/toolkit/mozapps/update/updater/launchchild_osx.mm
new file mode 100644
index 0000000000..5a36ae6237
--- /dev/null
+++ b/toolkit/mozapps/update/updater/launchchild_osx.mm
@@ -0,0 +1,384 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 <Cocoa/Cocoa.h>
+#include <CoreServices/CoreServices.h>
+#include <crt_externs.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <spawn.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include "readstrings.h"
+
+class MacAutoreleasePool {
+public:
+ MacAutoreleasePool()
+ {
+ mPool = [[NSAutoreleasePool alloc] init];
+ }
+ ~MacAutoreleasePool()
+ {
+ [mPool release];
+ }
+
+private:
+ NSAutoreleasePool* mPool;
+};
+
+void LaunchChild(int argc, const char** argv)
+{
+ MacAutoreleasePool pool;
+
+ @try {
+ NSString* launchPath = [NSString stringWithUTF8String:argv[0]];
+ NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:argc - 1];
+ for (int i = 1; i < argc; i++) {
+ [arguments addObject:[NSString stringWithUTF8String:argv[i]]];
+ }
+ [NSTask launchedTaskWithLaunchPath:launchPath
+ arguments:arguments];
+ } @catch (NSException* e) {
+ NSLog(@"%@: %@", e.name, e.reason);
+ }
+}
+
+void
+LaunchMacPostProcess(const char* aAppBundle)
+{
+ MacAutoreleasePool pool;
+
+ // Launch helper to perform post processing for the update; this is the Mac
+ // analogue of LaunchWinPostProcess (PostUpdateWin).
+ NSString* iniPath = [NSString stringWithUTF8String:aAppBundle];
+ iniPath =
+ [iniPath stringByAppendingPathComponent:@"Contents/Resources/updater.ini"];
+
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ if (![fileManager fileExistsAtPath:iniPath]) {
+ // the file does not exist; there is nothing to run
+ return;
+ }
+
+ int readResult;
+ char values[2][MAX_TEXT_LEN];
+ readResult = ReadStrings([iniPath UTF8String],
+ "ExeRelPath\0ExeArg\0",
+ 2,
+ values,
+ "PostUpdateMac");
+ if (readResult) {
+ return;
+ }
+
+ NSString *exeRelPath = [NSString stringWithUTF8String:values[0]];
+ NSString *exeArg = [NSString stringWithUTF8String:values[1]];
+ if (!exeArg || !exeRelPath) {
+ return;
+ }
+
+ // The path must not traverse directories and it must be a relative path.
+ if ([exeRelPath rangeOfString:@".."].location != NSNotFound ||
+ [exeRelPath rangeOfString:@"./"].location != NSNotFound ||
+ [exeRelPath rangeOfString:@"/"].location == 0) {
+ return;
+ }
+
+ NSString* exeFullPath = [NSString stringWithUTF8String:aAppBundle];
+ exeFullPath = [exeFullPath stringByAppendingPathComponent:exeRelPath];
+
+ char optVals[1][MAX_TEXT_LEN];
+ readResult = ReadStrings([iniPath UTF8String],
+ "ExeAsync\0",
+ 1,
+ optVals,
+ "PostUpdateMac");
+
+ NSTask *task = [[NSTask alloc] init];
+ [task setLaunchPath:exeFullPath];
+ [task setArguments:[NSArray arrayWithObject:exeArg]];
+ [task launch];
+ if (!readResult) {
+ NSString *exeAsync = [NSString stringWithUTF8String:optVals[0]];
+ if ([exeAsync isEqualToString:@"false"]) {
+ [task waitUntilExit];
+ }
+ }
+ // ignore the return value of the task, there's nothing we can do with it
+ [task release];
+}
+
+id ConnectToUpdateServer()
+{
+ MacAutoreleasePool pool;
+
+ id updateServer = nil;
+ BOOL isConnected = NO;
+ int currTry = 0;
+ const int numRetries = 10; // Number of IPC connection retries before
+ // giving up.
+ while (!isConnected && currTry < numRetries) {
+ @try {
+ updateServer = (id)[NSConnection
+ rootProxyForConnectionWithRegisteredName:
+ @"org.mozilla.updater.server"
+ host:nil
+ usingNameServer:[NSSocketPortNameServer sharedInstance]];
+ if (!updateServer ||
+ ![updateServer respondsToSelector:@selector(abort)] ||
+ ![updateServer respondsToSelector:@selector(getArguments)] ||
+ ![updateServer respondsToSelector:@selector(shutdown)]) {
+ NSLog(@"Server doesn't exist or doesn't provide correct selectors.");
+ sleep(1); // Wait 1 second.
+ currTry++;
+ } else {
+ isConnected = YES;
+ }
+ } @catch (NSException* e) {
+ NSLog(@"Encountered exception, retrying: %@: %@", e.name, e.reason);
+ sleep(1); // Wait 1 second.
+ currTry++;
+ }
+ }
+ if (!isConnected) {
+ NSLog(@"Failed to connect to update server after several retries.");
+ return nil;
+ }
+ return updateServer;
+}
+
+void CleanupElevatedMacUpdate(bool aFailureOccurred)
+{
+ MacAutoreleasePool pool;
+
+ id updateServer = ConnectToUpdateServer();
+ if (updateServer) {
+ @try {
+ if (aFailureOccurred) {
+ [updateServer performSelector:@selector(abort)];
+ } else {
+ [updateServer performSelector:@selector(shutdown)];
+ }
+ } @catch (NSException* e) { }
+ }
+
+ NSFileManager* manager = [NSFileManager defaultManager];
+ [manager removeItemAtPath:@"/Library/PrivilegedHelperTools/org.mozilla.updater"
+ error:nil];
+ [manager removeItemAtPath:@"/Library/LaunchDaemons/org.mozilla.updater.plist"
+ error:nil];
+ const char* launchctlArgs[] = {"/bin/launchctl",
+ "remove",
+ "org.mozilla.updater"};
+ // The following call will terminate the current process due to the "remove"
+ // argument in launchctlArgs.
+ LaunchChild(3, launchctlArgs);
+}
+
+// Note: Caller is responsible for freeing argv.
+bool ObtainUpdaterArguments(int* argc, char*** argv)
+{
+ MacAutoreleasePool pool;
+
+ id updateServer = ConnectToUpdateServer();
+ if (!updateServer) {
+ // Let's try our best and clean up.
+ CleanupElevatedMacUpdate(true);
+ return false; // Won't actually get here due to CleanupElevatedMacUpdate.
+ }
+
+ @try {
+ NSArray* updaterArguments =
+ [updateServer performSelector:@selector(getArguments)];
+ *argc = [updaterArguments count];
+ char** tempArgv = (char**)malloc(sizeof(char*) * (*argc));
+ for (int i = 0; i < *argc; i++) {
+ int argLen = [[updaterArguments objectAtIndex:i] length] + 1;
+ tempArgv[i] = (char*)malloc(argLen);
+ strncpy(tempArgv[i], [[updaterArguments objectAtIndex:i] UTF8String],
+ argLen);
+ }
+ *argv = tempArgv;
+ } @catch (NSException* e) {
+ // Let's try our best and clean up.
+ CleanupElevatedMacUpdate(true);
+ return false; // Won't actually get here due to CleanupElevatedMacUpdate.
+ }
+ return true;
+}
+
+/**
+ * The ElevatedUpdateServer is launched from a non-elevated updater process.
+ * It allows an elevated updater process (usually a privileged helper tool) to
+ * connect to it and receive all the necessary arguments to complete a
+ * successful update.
+ */
+@interface ElevatedUpdateServer : NSObject
+{
+ NSArray* mUpdaterArguments;
+ BOOL mShouldKeepRunning;
+ BOOL mAborted;
+}
+- (id)initWithArgs:(NSArray*)args;
+- (BOOL)runServer;
+- (NSArray*)getArguments;
+- (void)abort;
+- (BOOL)wasAborted;
+- (void)shutdown;
+- (BOOL)shouldKeepRunning;
+@end
+
+@implementation ElevatedUpdateServer
+
+- (id)initWithArgs:(NSArray*)args
+{
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+ mUpdaterArguments = args;
+ mShouldKeepRunning = YES;
+ mAborted = NO;
+ return self;
+}
+
+- (BOOL)runServer
+{
+ NSPort* serverPort = [NSSocketPort port];
+ NSConnection* server = [NSConnection connectionWithReceivePort:serverPort
+ sendPort:serverPort];
+ [server setRootObject:self];
+ if ([server registerName:@"org.mozilla.updater.server"
+ withNameServer:[NSSocketPortNameServer sharedInstance]] == NO) {
+ NSLog(@"Unable to register as DirectoryServer.");
+ NSLog(@"Is another copy running?");
+ return NO;
+ }
+
+ while ([self shouldKeepRunning] &&
+ [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate distantFuture]]);
+ return ![self wasAborted];
+}
+
+- (NSArray*)getArguments
+{
+ return mUpdaterArguments;
+}
+
+- (void)abort
+{
+ mAborted = YES;
+ [self shutdown];
+}
+
+- (BOOL)wasAborted
+{
+ return mAborted;
+}
+
+- (void)shutdown
+{
+ mShouldKeepRunning = NO;
+}
+
+- (BOOL)shouldKeepRunning
+{
+ return mShouldKeepRunning;
+}
+
+@end
+
+bool ServeElevatedUpdate(int argc, const char** argv)
+{
+ MacAutoreleasePool pool;
+
+ NSMutableArray* updaterArguments = [NSMutableArray arrayWithCapacity:argc];
+ for (int i = 0; i < argc; i++) {
+ [updaterArguments addObject:[NSString stringWithUTF8String:argv[i]]];
+ }
+
+ ElevatedUpdateServer* updater =
+ [[ElevatedUpdateServer alloc] initWithArgs:[updaterArguments copy]];
+ bool didSucceed = [updater runServer];
+
+ [updater release];
+ return didSucceed;
+}
+
+bool IsOwnedByGroupAdmin(const char* aAppBundle)
+{
+ MacAutoreleasePool pool;
+
+ NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+
+ NSDictionary* attributes = [fileManager attributesOfItemAtPath:appDir
+ error:nil];
+ bool isOwnedByAdmin = false;
+ if (attributes &&
+ [[attributes valueForKey:NSFileGroupOwnerAccountID] intValue] == 80) {
+ isOwnedByAdmin = true;
+ }
+ return isOwnedByAdmin;
+}
+
+void SetGroupOwnershipAndPermissions(const char* aAppBundle)
+{
+ MacAutoreleasePool pool;
+
+ NSString* appDir = [NSString stringWithUTF8String:aAppBundle];
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ NSError* error = nil;
+ NSArray* paths =
+ [fileManager subpathsOfDirectoryAtPath:appDir
+ error:&error];
+ if (error) {
+ return;
+ }
+
+ // Set group ownership of Firefox.app to 80 ("admin") and permissions to
+ // 0775.
+ if (![fileManager setAttributes:@{ NSFileGroupOwnerAccountID: @(80),
+ NSFilePosixPermissions: @(0775) }
+ ofItemAtPath:appDir
+ error:&error] || error) {
+ return;
+ }
+
+ NSArray* permKeys = [NSArray arrayWithObjects:NSFileGroupOwnerAccountID,
+ NSFilePosixPermissions,
+ nil];
+ // For all descendants of Firefox.app, set group ownership to 80 ("admin") and
+ // ensure write permission for the group.
+ for (NSString* currPath in paths) {
+ NSString* child = [appDir stringByAppendingPathComponent:currPath];
+ NSDictionary* oldAttributes =
+ [fileManager attributesOfItemAtPath:child
+ error:&error];
+ if (error) {
+ return;
+ }
+ // Skip symlinks, since they could be pointing to files outside of the .app
+ // bundle.
+ if ([oldAttributes fileType] == NSFileTypeSymbolicLink) {
+ continue;
+ }
+ NSNumber* oldPerms =
+ (NSNumber*)[oldAttributes valueForKey:NSFilePosixPermissions];
+ NSArray* permObjects =
+ [NSArray arrayWithObjects:
+ [NSNumber numberWithUnsignedLong:80],
+ [NSNumber numberWithUnsignedLong:[oldPerms shortValue] | 020],
+ nil];
+ NSDictionary* attributes = [NSDictionary dictionaryWithObjects:permObjects
+ forKeys:permKeys];
+ if (![fileManager setAttributes:attributes
+ ofItemAtPath:child
+ error:&error] || error) {
+ return;
+ }
+ }
+}
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in
new file mode 100644
index 0000000000..a9b9fcba9d
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>org.mozilla.updater</string>
+ <key>CFBundleIconFile</key>
+ <string>updater.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.mozilla.updater</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.5</string>
+ <key>LSMinimumSystemVersionByArchitecture</key>
+ <dict>
+ <key>i386</key>
+ <string>10.5.0</string>
+ <key>x86_64</key>
+ <string>10.6.0</string>
+ </dict>
+ <key>SMAuthorizedClients</key>
+ <array>
+ <string>identifier "%MOZ_MACBUNDLE_ID%" and ((anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]) or (anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = "43AQ936H96"))</string>
+ </array>
+</dict>
+</plist>
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo b/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo
new file mode 100644
index 0000000000..bd04210fb4
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo
@@ -0,0 +1 @@
+APPL???? \ No newline at end of file
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 0000000000..bca4022e75
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,7 @@
+/* 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/. */
+
+/* Localized versions of Info.plist keys */
+
+CFBundleName = "%APP_NAME% Software Update";
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..6cfb50406b
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,19 @@
+{
+ IBClasses = (
+ {
+ CLASS = FirstResponder;
+ LANGUAGE = ObjC;
+ SUPERCLASS = NSObject;
+},
+ {
+ CLASS = UpdaterUI;
+ LANGUAGE = ObjC;
+ OUTLETS = {
+ progressBar = NSProgressIndicator;
+ progressTextField = NSTextField;
+ };
+ SUPERCLASS = NSObject;
+}
+ );
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..1509178370
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>111 162 356 240 0 0 1440 878 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>106 299 84 44 0 0 1440 878 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>489.0</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>21</integer>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>10J567</string>
+</dict>
+</plist>
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000000..61ff026009
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns
new file mode 100644
index 0000000000..d7499c6692
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns
Binary files differ
diff --git a/toolkit/mozapps/update/updater/moz.build b/toolkit/mozapps/update/updater/moz.build
index 6cf377afef..4dc557ea34 100644
--- a/toolkit/mozapps/update/updater/moz.build
+++ b/toolkit/mozapps/update/updater/moz.build
@@ -3,13 +3,26 @@
# 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/.
-Program('updater')
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ Program('org.mozilla.updater')
+else:
+ Program('updater')
updater_rel_path = ''
include('updater-common.build')
CXXFLAGS += CONFIG['MOZ_BZ2_CFLAGS']
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LDFLAGS += ['-sectcreate',
+ '__TEXT',
+ '__info_plist',
+ TOPOBJDIR + '/dist/bin/Info.plist',
+ '-sectcreate',
+ '__TEXT',
+ '__launchd_plist',
+ SRCDIR + '/Launchd.plist']
+
GENERATED_FILES = [
'primaryCert.h',
'secondaryCert.h',
diff --git a/toolkit/mozapps/update/updater/progressui.h b/toolkit/mozapps/update/updater/progressui.h
index 6dc20e06bc..5462815dee 100644
--- a/toolkit/mozapps/update/updater/progressui.h
+++ b/toolkit/mozapps/update/updater/progressui.h
@@ -24,6 +24,9 @@ int InitProgressUI(int *argc, NS_tchar ***argv);
// Called on the main thread at startup
int ShowProgressUI(bool indeterminate = false, bool initUIStrings = true);
int InitProgressUIStrings();
+#elif defined(XP_MACOSX)
+ // Called on the main thread at startup
+ int ShowProgressUI(bool indeterminate = false);
#else
// Called on the main thread at startup
int ShowProgressUI();
diff --git a/toolkit/mozapps/update/updater/progressui_osx.mm b/toolkit/mozapps/update/updater/progressui_osx.mm
new file mode 100644
index 0000000000..54c9c41b72
--- /dev/null
+++ b/toolkit/mozapps/update/updater/progressui_osx.mm
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "mozilla/Sprintf.h"
+#include "progressui.h"
+#include "readstrings.h"
+#include "errors.h"
+
+#define TIMER_INTERVAL 0.2
+
+static float sProgressVal; // between 0 and 100
+static BOOL sQuit = NO;
+static BOOL sIndeterminate = NO;
+static StringTable sLabels;
+static const char *sUpdatePath;
+
+@interface UpdaterUI : NSObject
+{
+ IBOutlet NSProgressIndicator *progressBar;
+ IBOutlet NSTextField *progressTextField;
+}
+@end
+
+@implementation UpdaterUI
+
+-(void)awakeFromNib
+{
+ NSWindow *w = [progressBar window];
+
+ [w setTitle:[NSString stringWithUTF8String:sLabels.title]];
+ [progressTextField setStringValue:[NSString stringWithUTF8String:sLabels.info]];
+
+ NSRect origTextFrame = [progressTextField frame];
+ [progressTextField sizeToFit];
+
+ int widthAdjust = progressTextField.frame.size.width - origTextFrame.size.width;
+
+ if (widthAdjust > 0) {
+ NSRect f;
+ f.size.width = w.frame.size.width + widthAdjust;
+ f.size.height = w.frame.size.height;
+ [w setFrame:f display:YES];
+ }
+
+ [w center];
+
+ [progressBar setIndeterminate:sIndeterminate];
+ [progressBar setDoubleValue:0.0];
+
+ [[NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self
+ selector:@selector(updateProgressUI:)
+ userInfo:nil repeats:YES] retain];
+
+ // Make sure we are on top initially
+ [NSApp activateIgnoringOtherApps:YES];
+}
+
+// called when the timer goes off
+-(void)updateProgressUI:(NSTimer *)aTimer
+{
+ if (sQuit) {
+ [aTimer invalidate];
+ [aTimer release];
+
+ // It seems to be necessary to activate and hide ourselves before we stop,
+ // otherwise the "run" method will not return until the user focuses some
+ // other app. The activate step is necessary if we are not the active app.
+ // This is a big hack, but it seems to do the trick.
+ [NSApp activateIgnoringOtherApps:YES];
+ [NSApp hide:self];
+ [NSApp stop:self];
+ }
+
+ float progress = sProgressVal;
+
+ [progressBar setDoubleValue:(double)progress];
+}
+
+// leave this as returning a BOOL instead of NSApplicationTerminateReply
+// for backward compatibility
+- (BOOL)applicationShouldTerminate:(NSApplication *)sender
+{
+ return sQuit;
+}
+
+@end
+
+int
+InitProgressUI(int *pargc, char ***pargv)
+{
+ sUpdatePath = (*pargv)[1];
+
+ return 0;
+}
+
+int
+ShowProgressUI(bool indeterminate)
+{
+ // Only show the Progress UI if the process is taking a significant amount of
+ // time where a significant amount of time is defined as .5 seconds after
+ // ShowProgressUI is called sProgress is less than 70.
+ usleep(500000);
+
+ if (sQuit || sProgressVal > 70.0f)
+ return 0;
+
+ char path[PATH_MAX];
+ SprintfLiteral(path, "%s/updater.ini", sUpdatePath);
+ if (ReadStrings(path, &sLabels) != OK)
+ return -1;
+
+ // Continue the update without showing the Progress UI if any of the supplied
+ // strings are larger than MAX_TEXT_LEN (Bug 628829).
+ if (!(strlen(sLabels.title) < MAX_TEXT_LEN - 1 &&
+ strlen(sLabels.info) < MAX_TEXT_LEN - 1))
+ return -1;
+
+ sIndeterminate = indeterminate;
+ [NSApplication sharedApplication];
+ [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
+ [NSApp run];
+
+ return 0;
+}
+
+// Called on a background thread
+void
+QuitProgressUI()
+{
+ sQuit = YES;
+}
+
+// Called on a background thread
+void
+UpdateProgressUI(float progress)
+{
+ sProgressVal = progress; // 32-bit writes are atomic
+}
diff --git a/toolkit/mozapps/update/updater/updater-common.build b/toolkit/mozapps/update/updater/updater-common.build
index 1ace8fcc71..ce4a219001 100644
--- a/toolkit/mozapps/update/updater/updater-common.build
+++ b/toolkit/mozapps/update/updater/updater-common.build
@@ -73,6 +73,24 @@ if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
'progressui_gtk.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ have_progressui = 1
+ srcs += [
+ 'launchchild_osx.mm',
+ 'progressui_osx.mm',
+ ]
+ OS_LIBS += [
+ '-framework Cocoa',
+ '-framework Security',
+ '-framework SystemConfiguration',
+ ]
+ UNIFIED_SOURCES += [
+ '/toolkit/xre/updaterfileutils_osx.mm',
+ ]
+ LOCAL_INCLUDES += [
+ '/toolkit/xre',
+ ]
+
if have_progressui == 0:
srcs += [
'progressui_null.cpp',
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
index f31c16d45c..f5f71935dc 100644
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -53,6 +53,9 @@
#include <algorithm>
#include "updatecommon.h"
+#ifdef XP_MACOSX
+#include "updaterfileutils_osx.h"
+#endif // XP_MACOSX
#include "mozilla/Compiler.h"
#include "mozilla/Types.h"
@@ -72,6 +75,23 @@
#define PARENT_WAIT 10000
#endif
+#if defined(XP_MACOSX)
+// These functions are defined in launchchild_osx.mm
+void CleanupElevatedMacUpdate(bool aFailureOccurred);
+bool IsOwnedByGroupAdmin(const char* aAppBundle);
+bool IsRecursivelyWritable(const char* aPath);
+void LaunchChild(int argc, const char** argv);
+void LaunchMacPostProcess(const char* aAppBundle);
+bool ObtainUpdaterArguments(int* argc, char*** argv);
+bool ServeElevatedUpdate(int argc, const char** argv);
+void SetGroupOwnershipAndPermissions(const char* aAppBundle);
+struct UpdateServerThreadArgs
+{
+ int argc;
+ const NS_tchar** argv;
+};
+#endif
+
#ifndef _O_BINARY
# define _O_BINARY 0
#endif
@@ -86,13 +106,14 @@
// We want to use execv to invoke the callback executable on platforms where
// we were launched using execv. See nsUpdateDriver.cpp.
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
#define USE_EXECV
#endif
# define MAYBE_USE_HARD_LINKS 0
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN)
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \
+ !defined(XP_MACOSX)
#include "nss.h"
#include "prerror.h"
#endif
@@ -1675,6 +1696,21 @@ PatchFile::Execute()
AutoFile ofile(ensure_open(mFile.get(), shouldTruncate ? NS_T("wb+") : NS_T("rb+"),
ss.st_mode));
+#elif defined(XP_MACOSX)
+ AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
+ // Modified code from FileUtils.cpp
+ fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, header.dlen};
+ // Try to get a continous chunk of disk space
+ rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
+ if (rv == -1) {
+ // OK, perhaps we are too fragmented, allocate non-continuous
+ store.fst_flags = F_ALLOCATEALL;
+ rv = fcntl(fileno((FILE *)ofile), F_PREALLOCATE, &store);
+ }
+
+ if (rv != -1) {
+ ftruncate(fileno((FILE *)ofile), header.dlen);
+ }
#else
AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
#endif
@@ -2055,6 +2091,8 @@ LaunchCallbackApp(const NS_tchar *workingDir,
#if defined(USE_EXECV)
execv(argv[0], argv);
+#elif defined(XP_MACOSX)
+ LaunchChild(argc, (const char**)argv);
#elif defined(XP_WIN)
WinLaunchChild(argv[0], argc, argv, nullptr);
#else
@@ -2170,15 +2208,19 @@ CopyInstallDirToDestDir()
// These files should not be copied over to the updated app
#ifdef XP_WIN
#define SKIPLIST_COUNT 3
+#elif XP_MACOSX
+#define SKIPLIST_COUNT 0
#else
#define SKIPLIST_COUNT 2
#endif
copy_recursive_skiplist<SKIPLIST_COUNT> skiplist;
+#ifndef XP_MACOSX
skiplist.append(0, gInstallDirPath, NS_T("updated"));
skiplist.append(1, gInstallDirPath, NS_T("updates/0"));
#ifdef XP_WIN
skiplist.append(2, gInstallDirPath, NS_T("updated.update_in_progress.lock"));
#endif
+#endif
return ensure_copy_recursive(gInstallDirPath, gWorkingDirPath, skiplist);
}
@@ -2197,7 +2239,11 @@ ProcessReplaceRequest()
// 2. Move newDir to destDir. In case of failure, revert step 1 and abort.
// 3. Delete tmpDir (or defer it to the next reboot).
-#ifdef XP_WIN
+#ifdef XP_MACOSX
+ NS_tchar destDir[MAXPATHLEN];
+ NS_tsnprintf(destDir, sizeof(destDir)/sizeof(destDir[0]),
+ NS_T("%s/Contents"), gInstallDirPath);
+#elif XP_WIN
// Windows preserves the case of the file/directory names. We use the
// GetLongPathName API in order to get the correct case for the directory
// name, so that if the user has used a different case when launching the
@@ -2217,8 +2263,13 @@ ProcessReplaceRequest()
NS_tchar newDir[MAXPATHLEN];
NS_tsnprintf(newDir, sizeof(newDir)/sizeof(newDir[0]),
+#ifdef XP_MACOSX
+ NS_T("%s/Contents"),
+ gWorkingDirPath);
+#else
NS_T("%s.bak/updated"),
gInstallDirPath);
+#endif
// First try to remove the possibly existing temp directory, because if this
// directory exists, we will fail to rename destDir.
@@ -2256,6 +2307,14 @@ ProcessReplaceRequest()
LOG(("Begin moving newDir (" LOG_S ") to destDir (" LOG_S ")",
newDir, destDir));
rv = rename_file(newDir, destDir, true);
+#ifdef XP_MACOSX
+ if (rv) {
+ LOG(("Moving failed. Begin copying newDir (" LOG_S ") to destDir (" LOG_S ")",
+ newDir, destDir));
+ copy_recursive_skiplist<0> skiplist;
+ rv = ensure_copy_recursive(newDir, destDir, skiplist);
+ }
+#endif
if (rv) {
LOG(("Moving newDir to destDir failed, err: %d", rv));
LOG(("Now, try to move tmpDir back to destDir"));
@@ -2269,7 +2328,7 @@ ProcessReplaceRequest()
return rv;
}
-#if !defined(XP_WIN)
+#if !defined(XP_WIN) && !defined(XP_MACOSX)
// Platforms that have their updates directory in the installation directory
// need to have the last-update.log and backup-update.log files moved from the
// old installation directory to the new installation directory.
@@ -2303,6 +2362,15 @@ ProcessReplaceRequest()
#endif
}
+#ifdef XP_MACOSX
+ // On OS X, we we need to remove the staging directory after its Contents
+ // directory has been moved.
+ NS_tchar updatedAppDir[MAXPATHLEN];
+ NS_tsnprintf(updatedAppDir, sizeof(updatedAppDir)/sizeof(updatedAppDir[0]),
+ NS_T("%s/Updated.app"), gPatchDirPath);
+ ensure_remove_recursive(updatedAppDir);
+#endif
+
gSucceeded = true;
return 0;
@@ -2412,7 +2480,11 @@ UpdateThreadFunc(void *param)
NS_tchar updateSettingsPath[MAX_TEXT_LEN];
NS_tsnprintf(updateSettingsPath,
sizeof(updateSettingsPath) / sizeof(updateSettingsPath[0]),
+#ifdef XP_MACOSX
+ NS_T("%s/Contents/Resources/update-settings.ini"),
+#else
NS_T("%s/update-settings.ini"),
+#endif
gWorkingDirPath);
MARChannelStringTable MARStrings;
if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) {
@@ -2484,6 +2556,15 @@ UpdateThreadFunc(void *param)
if (rv) {
LOG(("failed: %d", rv));
} else {
+#ifdef XP_MACOSX
+ // If the update was successful we need to update the timestamp on the
+ // top-level Mac OS X bundle directory so that Mac OS X's Launch Services
+ // picks up any major changes when the bundle is updated.
+ if (!sStagedUpdate && utimes(gInstallDirPath, nullptr) != 0) {
+ LOG(("Couldn't set access/modification time on application bundle."));
+ }
+#endif
+
LOG(("succeeded"));
}
WriteStatusFile(rv);
@@ -2493,11 +2574,34 @@ UpdateThreadFunc(void *param)
QuitProgressUI();
}
+#ifdef XP_MACOSX
+static void
+ServeElevatedUpdateThreadFunc(void* param)
+{
+ UpdateServerThreadArgs* threadArgs = (UpdateServerThreadArgs*)param;
+ gSucceeded = ServeElevatedUpdate(threadArgs->argc, threadArgs->argv);
+ if (!gSucceeded) {
+ WriteStatusFile(ELEVATION_CANCELED);
+ }
+ QuitProgressUI();
+}
+
+void freeArguments(int argc, char** argv)
+{
+ for (int i = 0; i < argc; i++) {
+ free(argv[i]);
+ }
+ free(argv);
+}
+#endif
+
int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
int callbackIndex
#ifdef XP_WIN
, const WCHAR* elevatedLockFilePath
, HANDLE updateLockFileHandle
+#elif XP_MACOSX
+ , bool isElevated
#endif
)
{
@@ -2509,11 +2613,19 @@ int LaunchCallbackAndPostProcessApps(int argc, NS_tchar** argv,
}
}
EXIT_WHEN_ELEVATED(elevatedLockFilePath, updateLockFileHandle, 0);
+#elif XP_MACOSX
+ if (!isElevated) {
+ if (gSucceeded) {
+ LaunchMacPostProcess(gInstallDirPath);
+ }
#endif
LaunchCallbackApp(argv[5],
argc - callbackIndex,
argv + callbackIndex);
+#ifdef XP_MACOSX
+ } // if (!isElevated)
+#endif /* XP_MACOSX */
}
return 0;
}
@@ -2525,7 +2637,20 @@ int NS_main(int argc, NS_tchar **argv)
// argument prior to callbackIndex is the working directory.
const int callbackIndex = 6;
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN)
+#ifdef XP_MACOSX
+ bool isElevated =
+ strstr(argv[0], "/Library/PrivilegedHelperTools/org.mozilla.updater") != 0;
+ if (isElevated) {
+ if (!ObtainUpdaterArguments(&argc, &argv)) {
+ // Won't actually get here because ObtainUpdaterArguments will terminate
+ // the current process on failure.
+ return 1;
+ }
+ }
+#endif
+
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \
+ !defined(XP_MACOSX)
// On Windows and Mac we rely on native APIs to do verifications so we don't
// need to initialize NSS at all there.
// Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
@@ -2538,7 +2663,13 @@ int NS_main(int argc, NS_tchar **argv)
}
#endif
- InitProgressUI(&argc, &argv);
+#ifdef XP_MACOSX
+ if (!isElevated) {
+#endif
+ InitProgressUI(&argc, &argv);
+#ifdef XP_MACOSX
+ }
+#endif
// To process an update the updater command line must at a minimum have the
// directory path containing the updater.mar file to process as the first
@@ -2556,6 +2687,12 @@ int NS_main(int argc, NS_tchar **argv)
// launched.
if (argc < 4) {
fprintf(stderr, "Usage: updater patch-dir install-dir apply-to-dir [wait-pid [callback-working-dir callback-path args...]]\n");
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
@@ -2564,6 +2701,12 @@ int NS_main(int argc, NS_tchar **argv)
// directory is invalid don't write the status file.
fprintf(stderr, "The patch directory path is not valid for this " \
"application (" LOG_S ")\n", argv[1]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
// The directory containing the update information.
@@ -2573,6 +2716,12 @@ int NS_main(int argc, NS_tchar **argv)
WriteStatusFile(INVALID_INSTALL_DIR_PATH_ERROR);
fprintf(stderr, "The install directory path is not valid for this " \
"application (" LOG_S ")\n", argv[2]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
// The directory we're going to update to.
@@ -2635,6 +2784,12 @@ int NS_main(int argc, NS_tchar **argv)
WriteStatusFile(INVALID_WORKING_DIR_PATH_ERROR);
fprintf(stderr, "The working directory path is not valid for this " \
"application (" LOG_S ")\n", argv[3]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
// The directory we're going to update to.
@@ -2653,6 +2808,12 @@ int NS_main(int argc, NS_tchar **argv)
WriteStatusFile(INVALID_CALLBACK_PATH_ERROR);
fprintf(stderr, "The callback file path is not valid for this " \
"application (" LOG_S ")\n", argv[callbackIndex]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
@@ -2663,10 +2824,37 @@ int NS_main(int argc, NS_tchar **argv)
WriteStatusFile(INVALID_CALLBACK_DIR_ERROR);
fprintf(stderr, "The callback file must be located in the " \
"installation directory (" LOG_S ")\n", argv[callbackIndex]);
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
}
+#ifdef XP_MACOSX
+ if (!isElevated && !IsRecursivelyWritable(argv[2])) {
+ // If the app directory isn't recursively writeable, an elevated update is
+ // required.
+ UpdateServerThreadArgs threadArgs;
+ threadArgs.argc = argc;
+ threadArgs.argv = const_cast<const NS_tchar**>(argv);
+
+ Thread t1;
+ if (t1.Run(ServeElevatedUpdateThreadFunc, &threadArgs) == 0) {
+ // Show an indeterminate progress bar while an elevated update is in
+ // progress.
+ ShowProgressUI(true);
+ }
+ t1.Join();
+
+ LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex, false);
+ return gSucceeded ? 0 : 1;
+ }
+#endif
+
if (EnvHasValue("MOZ_OS_UPDATE")) {
sIsOSUpdate = true;
putenv(const_cast<char*>("MOZ_OS_UPDATE="));
@@ -2676,6 +2864,12 @@ int NS_main(int argc, NS_tchar **argv)
if (!WriteStatusFile("applying")) {
LOG(("failed setting status to 'applying'"));
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
@@ -2912,6 +3106,12 @@ int NS_main(int argc, NS_tchar **argv)
// Try to create the destination directory if it doesn't exist
int rv = NS_tmkdir(gWorkingDirPath, 0755);
if (rv != OK && errno != EEXIST) {
+#ifdef XP_MACOSX
+ if (isElevated) {
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(true);
+ }
+#endif
return 1;
}
}
@@ -3152,7 +3352,11 @@ int NS_main(int argc, NS_tchar **argv)
// is an elevated process on OSX.
Thread t;
if (t.Run(UpdateThreadFunc, nullptr) == 0) {
- if (!sStagedUpdate && !sReplaceRequest) {
+ if (!sStagedUpdate && !sReplaceRequest
+#ifdef XP_MACOSX
+ && !isElevated
+#endif
+ ) {
ShowProgressUI();
}
}
@@ -3189,12 +3393,66 @@ int NS_main(int argc, NS_tchar **argv)
}
#endif /* XP_WIN */
+#ifdef XP_MACOSX
+ // When the update is successful remove the precomplete file in the root of
+ // the application bundle and move the distribution directory from
+ // Contents/MacOS to Contents/Resources and if both exist delete the
+ // directory under Contents/MacOS (see Bug 1068439).
+ if (gSucceeded && !sStagedUpdate) {
+ NS_tchar oldPrecomplete[MAXPATHLEN];
+ NS_tsnprintf(oldPrecomplete, sizeof(oldPrecomplete)/sizeof(oldPrecomplete[0]),
+ NS_T("%s/precomplete"), gInstallDirPath);
+ NS_tremove(oldPrecomplete);
+
+ NS_tchar oldDistDir[MAXPATHLEN];
+ NS_tsnprintf(oldDistDir, sizeof(oldDistDir)/sizeof(oldDistDir[0]),
+ NS_T("%s/Contents/MacOS/distribution"), gInstallDirPath);
+ int rv = NS_taccess(oldDistDir, F_OK);
+ if (!rv) {
+ NS_tchar newDistDir[MAXPATHLEN];
+ NS_tsnprintf(newDistDir, sizeof(newDistDir)/sizeof(newDistDir[0]),
+ NS_T("%s/Contents/Resources/distribution"), gInstallDirPath);
+ rv = NS_taccess(newDistDir, F_OK);
+ if (!rv) {
+ LOG(("New distribution directory already exists... removing old " \
+ "distribution directory: " LOG_S, oldDistDir));
+ rv = ensure_remove_recursive(oldDistDir);
+ if (rv) {
+ LOG(("Removing old distribution directory failed - err: %d", rv));
+ }
+ } else {
+ LOG(("Moving old distribution directory to new location. src: " LOG_S \
+ ", dst:" LOG_S, oldDistDir, newDistDir));
+ rv = rename_file(oldDistDir, newDistDir, true);
+ if (rv) {
+ LOG(("Moving old distribution directory to new location failed - " \
+ "err: %d", rv));
+ }
+ }
+ }
+ }
+
+ if (isElevated) {
+ SetGroupOwnershipAndPermissions(gInstallDirPath);
+ freeArguments(argc, argv);
+ CleanupElevatedMacUpdate(false);
+ } else if (IsOwnedByGroupAdmin(gInstallDirPath)) {
+ // If the group ownership of the Firefox .app bundle was set to the "admin"
+ // group during a previous elevated update, we need to ensure that all files
+ // in the bundle have group ownership of "admin" as well as write permission
+ // for the group to not break updates in the future.
+ SetGroupOwnershipAndPermissions(gInstallDirPath);
+ }
+#endif /* XP_MACOSX */
+
LogFinish();
int retVal = LaunchCallbackAndPostProcessApps(argc, argv, callbackIndex
#ifdef XP_WIN
, elevatedLockFilePath
, updateLockFileHandle
+#elif XP_MACOSX
+ , isElevated
#endif
);
@@ -3650,8 +3908,13 @@ int AddPreCompleteActions(ActionList *list)
return OK;
}
+#ifdef XP_MACOSX
+ mozilla::UniquePtr<NS_tchar[]> manifestPath(get_full_path(
+ NS_T("Contents/Resources/precomplete")));
+#else
mozilla::UniquePtr<NS_tchar[]> manifestPath(get_full_path(
NS_T("precomplete")));
+#endif
NS_tchar *rb = GetManifestContents(manifestPath.get());
if (rb == nullptr) {
diff --git a/toolkit/profile/content/createProfileWizard.js b/toolkit/profile/content/createProfileWizard.js
index f378f36769..aa87eacd71 100644
--- a/toolkit/profile/content/createProfileWizard.js
+++ b/toolkit/profile/content/createProfileWizard.js
@@ -116,7 +116,11 @@ function checkCurrentInput(currentInput)
if (!errorMessage) {
finishText.className = "";
+#ifdef XP_MACOSX
+ finishText.firstChild.data = gProfileManagerBundle.getString("profileFinishTextMac");
+#else
finishText.firstChild.data = gProfileManagerBundle.getString("profileFinishText");
+#endif
canAdvance = true;
}
else {
diff --git a/toolkit/profile/content/createProfileWizard.xul b/toolkit/profile/content/createProfileWizard.xul
index c7e0702a62..eab1a93414 100644
--- a/toolkit/profile/content/createProfileWizard.xul
+++ b/toolkit/profile/content/createProfileWizard.xul
@@ -31,11 +31,15 @@
<description>&profileCreationExplanation_2.text;</description>
<description>&profileCreationExplanation_3.text;</description>
<spacer flex="1"/>
+#ifdef XP_MACOSX
+ <description>&profileCreationExplanation_4Mac.text;</description>
+#else
#ifdef XP_UNIX
<description>&profileCreationExplanation_4Gnome.text;</description>
#else
<description>&profileCreationExplanation_4.text;</description>
#endif
+#endif
</wizardpage>
<wizardpage id="createProfile" onpageshow="initSecondWizardPage();">
diff --git a/toolkit/profile/content/profileSelection.js b/toolkit/profile/content/profileSelection.js
index 9fb77dfcd0..05ef6f5edb 100644
--- a/toolkit/profile/content/profileSelection.js
+++ b/toolkit/profile/content/profileSelection.js
@@ -134,7 +134,9 @@ function onProfilesKey(aEvent)
switch ( aEvent.keyCode )
{
case KeyEvent.DOM_VK_BACK_SPACE:
+#ifndef XP_MACOSX
break;
+#endif
case KeyEvent.DOM_VK_DELETE:
ConfirmDelete();
break;
diff --git a/toolkit/profile/jar.mn b/toolkit/profile/jar.mn
index 9b7c22266e..1c4afac4ca 100644
--- a/toolkit/profile/jar.mn
+++ b/toolkit/profile/jar.mn
@@ -3,7 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar:
- content/mozapps/profile/createProfileWizard.js (content/createProfileWizard.js)
+* content/mozapps/profile/createProfileWizard.js (content/createProfileWizard.js)
* content/mozapps/profile/createProfileWizard.xul (content/createProfileWizard.xul)
- content/mozapps/profile/profileSelection.js (content/profileSelection.js)
+* content/mozapps/profile/profileSelection.js (content/profileSelection.js)
content/mozapps/profile/profileSelection.xul (content/profileSelection.xul)
diff --git a/toolkit/profile/nsProfileLock.cpp b/toolkit/profile/nsProfileLock.cpp
index 654fbcd465..d75b6082d3 100644
--- a/toolkit/profile/nsProfileLock.cpp
+++ b/toolkit/profile/nsProfileLock.cpp
@@ -13,6 +13,11 @@
#include "nsAutoPtr.h"
#endif
+#if defined(XP_MACOSX)
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CoreFoundation.h>
+#endif
+
#ifdef XP_UNIX
#include <unistd.h>
#include <fcntl.h>
@@ -421,7 +426,10 @@ nsresult nsProfileLock::GetReplacedLockTime(PRTime *aResult) {
nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
nsIProfileUnlocker* *aUnlocker)
{
-#if defined (XP_UNIX)
+#if defined (XP_MACOSX)
+ NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
+ NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "parent.lock");
+#elif defined (XP_UNIX)
NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "lock");
NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock");
#else
@@ -450,7 +458,67 @@ nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
if (NS_FAILED(rv))
return rv;
-#if defined(XP_UNIX)
+#if defined(XP_MACOSX)
+ // First, try locking using fcntl. It is more reliable on
+ // a local machine, but may not be supported by an NFS server.
+
+ rv = LockWithFcntl(lockFile);
+ if (NS_FAILED(rv) && (rv != NS_ERROR_FILE_ACCESS_DENIED))
+ {
+ // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
+ // assume we tried an NFS that does not support it. Now, try with symlink.
+ rv = LockWithSymlink(lockFile, false);
+ }
+
+ if (NS_SUCCEEDED(rv))
+ {
+ // Check for the old-style lock used by pre-mozilla 1.3 builds.
+ // Those builds used an earlier check to prevent the application
+ // from launching if another instance was already running. Because
+ // of that, we don't need to create an old-style lock as well.
+ struct LockProcessInfo
+ {
+ ProcessSerialNumber psn;
+ unsigned long launchDate;
+ };
+
+ PRFileDesc *fd = nullptr;
+ int32_t ioBytes;
+ ProcessInfoRec processInfo;
+ LockProcessInfo lockProcessInfo;
+
+ rv = lockFile->SetLeafName(OLD_LOCKFILE_NAME);
+ if (NS_FAILED(rv))
+ return rv;
+ rv = lockFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
+ if (NS_SUCCEEDED(rv))
+ {
+ ioBytes = PR_Read(fd, &lockProcessInfo, sizeof(LockProcessInfo));
+ PR_Close(fd);
+
+ if (ioBytes == sizeof(LockProcessInfo))
+ {
+#ifdef __LP64__
+ processInfo.processAppRef = nullptr;
+#else
+ processInfo.processAppSpec = nullptr;
+#endif
+ processInfo.processName = nullptr;
+ processInfo.processInfoLength = sizeof(ProcessInfoRec);
+ if (::GetProcessInformation(&lockProcessInfo.psn, &processInfo) == noErr &&
+ processInfo.processLaunchDate == lockProcessInfo.launchDate)
+ {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ NS_WARNING("Could not read lock file - ignoring lock");
+ }
+ }
+ rv = NS_OK; // Don't propagate error from OpenNSPRFileDesc.
+ }
+#elif defined(XP_UNIX)
// Get the old lockfile name
nsCOMPtr<nsIFile> oldLockFile;
rv = aProfileDir->Clone(getter_AddRefs(oldLockFile));
diff --git a/toolkit/profile/nsToolkitProfileService.cpp b/toolkit/profile/nsToolkitProfileService.cpp
index 3380246da1..2fe51b2853 100644
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -26,6 +26,11 @@
#include "nsIFile.h"
#include "nsISimpleEnumerator.h"
+#ifdef XP_MACOSX
+#include <CoreFoundation/CoreFoundation.h>
+#include "nsILocalFileMac.h"
+#endif
+
#include "nsAppDirectoryServiceDefs.h"
#include "nsXULAppAPI.h"
@@ -1020,7 +1025,33 @@ NS_NewToolkitProfileService(nsIToolkitProfileService* *aResult)
nsresult
XRE_GetFileFromPath(const char *aPath, nsIFile* *aResult)
{
-#if defined(XP_UNIX)
+#if defined(XP_MACOSX)
+ int32_t pathLen = strlen(aPath);
+ if (pathLen > MAXPATHLEN)
+ return NS_ERROR_INVALID_ARG;
+
+ CFURLRef fullPath =
+ CFURLCreateFromFileSystemRepresentation(nullptr, (const UInt8 *) aPath,
+ pathLen, true);
+ if (!fullPath)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> lf;
+ nsresult rv = NS_NewNativeLocalFile(EmptyCString(), true,
+ getter_AddRefs(lf));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = lfMac->InitWithCFURL(fullPath);
+ if (NS_SUCCEEDED(rv)) {
+ lf.forget(aResult);
+ }
+ }
+ }
+ CFRelease(fullPath);
+ return rv;
+
+#elif defined(XP_UNIX)
char fullPath[MAXPATHLEN];
if (!realpath(aPath, fullPath))
diff --git a/toolkit/system/osxproxy/ProxyUtils.h b/toolkit/system/osxproxy/ProxyUtils.h
new file mode 100644
index 0000000000..d6e5ddbd45
--- /dev/null
+++ b/toolkit/system/osxproxy/ProxyUtils.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_toolkit_system_osxproxy_ProxyUtils_h
+#define mozilla_toolkit_system_osxproxy_ProxyUtils_h
+
+#include "nsStringGlue.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+bool IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride);
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
+
+#endif // mozilla_toolkit_system_osxproxy_ProxyUtils_h
diff --git a/toolkit/system/osxproxy/ProxyUtils.mm b/toolkit/system/osxproxy/ProxyUtils.mm
new file mode 100644
index 0000000000..4e59f226a0
--- /dev/null
+++ b/toolkit/system/osxproxy/ProxyUtils.mm
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 2; 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 "ProxyUtils.h"
+#include "nsTArray.h"
+#include "prnetdb.h"
+#include "prtypes.h"
+
+namespace mozilla {
+namespace toolkit {
+namespace system {
+
+/**
+ * Normalize the short IP form into the complete form.
+ * For example, it converts "192.168" into "192.168.0.0"
+ */
+static bool
+NormalizeAddr(const nsACString& aAddr, nsCString& aNormalized)
+{
+ nsTArray<nsCString> addr;
+ if (!ParseString(aAddr, '.', addr)) {
+ return false;
+ }
+ aNormalized = "";
+ for (uint32_t i = 0; i < 4; ++i) {
+ if (i != 0) {
+ aNormalized.Append(".");
+ }
+ if (i < addr.Length()) {
+ aNormalized.Append(addr[i]);
+ } else {
+ aNormalized.Append("0");
+ }
+ }
+ return true;
+}
+
+static PRUint32
+MaskIPv4Addr(PRUint32 aAddr, uint16_t aMaskLen)
+{
+ if (aMaskLen == 32) {
+ return aAddr;
+ }
+ return PR_htonl(PR_ntohl(aAddr) & (~0L << (32 - aMaskLen)));
+}
+
+static void
+MaskIPv6Addr(PRIPv6Addr& aAddr, uint16_t aMaskLen)
+{
+ if (aMaskLen == 128) {
+ return;
+ }
+
+ if (aMaskLen > 96) {
+ aAddr.pr_s6_addr32[3] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[3]) & (~0L << (128 - aMaskLen)));
+ } else if (aMaskLen > 64) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[2]) & (~0L << (96 - aMaskLen)));
+ } else if (aMaskLen > 32) {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[1]) & (~0L << (64 - aMaskLen)));
+ } else {
+ aAddr.pr_s6_addr32[3] = 0;
+ aAddr.pr_s6_addr32[2] = 0;
+ aAddr.pr_s6_addr32[1] = 0;
+ aAddr.pr_s6_addr32[0] = PR_htonl(
+ PR_ntohl(aAddr.pr_s6_addr32[0]) & (~0L << (32 - aMaskLen)));
+ }
+
+ return;
+}
+
+static bool
+IsMatchMask(const nsACString& aHost, const nsACString& aOverride)
+{
+ nsresult rv;
+
+ auto tokenEnd = aOverride.FindChar('/');
+ if (tokenEnd == -1) {
+ return false;
+ }
+
+ nsAutoCString prefixStr(Substring(aOverride,
+ tokenEnd + 1,
+ aOverride.Length() - tokenEnd - 1));
+ auto maskLen = prefixStr.ToInteger(&rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsAutoCString override(aOverride);
+ if (!NormalizeAddr(Substring(aOverride, 0, tokenEnd), override)) {
+ return false;
+ }
+
+ PRNetAddr prAddrHost;
+ PRNetAddr prAddrOverride;
+ if (PR_SUCCESS != PR_StringToNetAddr(PromiseFlatCString(aHost).get(),
+ &prAddrHost) ||
+ PR_SUCCESS != PR_StringToNetAddr(override.get(),
+ &prAddrOverride)) {
+ return false;
+ }
+
+ if (prAddrHost.raw.family == PR_AF_INET &&
+ prAddrOverride.raw.family == PR_AF_INET) {
+ return MaskIPv4Addr(prAddrHost.inet.ip, maskLen) ==
+ MaskIPv4Addr(prAddrOverride.inet.ip, maskLen);
+ }
+ else if (prAddrHost.raw.family == PR_AF_INET6 &&
+ prAddrOverride.raw.family == PR_AF_INET6) {
+ MaskIPv6Addr(prAddrHost.ipv6.ip, maskLen);
+ MaskIPv6Addr(prAddrOverride.ipv6.ip, maskLen);
+
+ return memcmp(&prAddrHost.ipv6.ip,
+ &prAddrOverride.ipv6.ip,
+ sizeof(PRIPv6Addr)) == 0;
+ }
+
+ return false;
+}
+
+static bool
+IsMatchWildcard(const nsACString& aHost, const nsACString& aOverride)
+{
+ nsAutoCString host(aHost);
+ nsAutoCString override(aOverride);
+
+ int32_t overrideLength = override.Length();
+ int32_t tokenStart = 0;
+ int32_t offset = 0;
+ bool star = false;
+
+ while (tokenStart < overrideLength) {
+ int32_t tokenEnd = override.FindChar('*', tokenStart);
+ if (tokenEnd == tokenStart) {
+ // Star is the first character in the token.
+ star = true;
+ tokenStart++;
+ // If the character following the '*' is a '.' character then skip
+ // it so that "*.foo.com" allows "foo.com".
+ if (override.FindChar('.', tokenStart) == tokenStart) {
+ nsAutoCString token(Substring(override,
+ tokenStart + 1,
+ overrideLength - tokenStart - 1));
+ if (host.Equals(token)) {
+ return true;
+ }
+ }
+ } else {
+ if (tokenEnd == -1) {
+ tokenEnd = overrideLength; // no '*' char, match rest of string
+ }
+ nsAutoCString token(Substring(override, tokenStart, tokenEnd - tokenStart));
+ offset = host.Find(token, offset);
+ if (offset == -1 || (!star && offset)) {
+ return false;
+ }
+ star = false;
+ tokenStart = tokenEnd;
+ offset += token.Length();
+ }
+ }
+
+ return (star || (offset == static_cast<int32_t>(host.Length())));
+}
+
+bool
+IsHostProxyEntry(const nsACString& aHost, const nsACString& aOverride)
+{
+ return IsMatchMask(aHost, aOverride) || IsMatchWildcard(aHost, aOverride);
+}
+
+} // namespace system
+} // namespace toolkit
+} // namespace mozilla
diff --git a/toolkit/system/osxproxy/moz.build b/toolkit/system/osxproxy/moz.build
new file mode 100644
index 0000000000..64a01ce6b8
--- /dev/null
+++ b/toolkit/system/osxproxy/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+TEST_DIRS += ['tests/gtest']
+
+SOURCES += [
+ 'nsOSXSystemProxySettings.mm',
+ 'ProxyUtils.mm',
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm b/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm
new file mode 100644
index 0000000000..77fd2e482c
--- /dev/null
+++ b/toolkit/system/osxproxy/nsOSXSystemProxySettings.mm
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import <SystemConfiguration/SystemConfiguration.h>
+
+#include "nsISystemProxySettings.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsIServiceManager.h"
+#include "nsPrintfCString.h"
+#include "nsNetCID.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURI.h"
+#include "nsObjCExceptions.h"
+#include "mozilla/Attributes.h"
+#include "ProxyUtils.h"
+
+class nsOSXSystemProxySettings final : public nsISystemProxySettings {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISYSTEMPROXYSETTINGS
+
+ nsOSXSystemProxySettings();
+ nsresult Init();
+
+ // called by OSX when the proxy settings have changed
+ void ProxyHasChanged();
+
+ // is there a PAC url specified in the system configuration
+ bool IsAutoconfigEnabled() const;
+ // retrieve the pac url
+ nsresult GetAutoconfigURL(nsAutoCString& aResult) const;
+
+ // Find the SystemConfiguration proxy & port for a given URI
+ nsresult FindSCProxyPort(const nsACString &aScheme, nsACString& aResultHost, int32_t& aResultPort, bool& aResultSocksProxy);
+
+ // is host:port on the proxy exception list?
+ bool IsInExceptionList(const nsACString& aHost) const;
+
+private:
+ ~nsOSXSystemProxySettings();
+
+ SCDynamicStoreContext mContext;
+ SCDynamicStoreRef mSystemDynamicStore;
+ NSDictionary* mProxyDict;
+
+
+ // Mapping of URI schemes to SystemConfiguration keys
+ struct SchemeMapping {
+ const char* mScheme;
+ CFStringRef mEnabled;
+ CFStringRef mHost;
+ CFStringRef mPort;
+ bool mIsSocksProxy;
+ };
+ static const SchemeMapping gSchemeMappingList[];
+};
+
+NS_IMPL_ISUPPORTS(nsOSXSystemProxySettings, nsISystemProxySettings)
+
+NS_IMETHODIMP
+nsOSXSystemProxySettings::GetMainThreadOnly(bool *aMainThreadOnly)
+{
+ *aMainThreadOnly = true;
+ return NS_OK;
+}
+
+// Mapping of URI schemes to SystemConfiguration keys
+const nsOSXSystemProxySettings::SchemeMapping nsOSXSystemProxySettings::gSchemeMappingList[] = {
+ {"http", kSCPropNetProxiesHTTPEnable, kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort, false},
+ {"https", kSCPropNetProxiesHTTPSEnable, kSCPropNetProxiesHTTPSProxy, kSCPropNetProxiesHTTPSPort, false},
+ {"ftp", kSCPropNetProxiesFTPEnable, kSCPropNetProxiesFTPProxy, kSCPropNetProxiesFTPPort, false},
+ {"socks", kSCPropNetProxiesSOCKSEnable, kSCPropNetProxiesSOCKSProxy, kSCPropNetProxiesSOCKSPort, true},
+ {NULL, NULL, NULL, NULL, false},
+};
+
+static void
+ProxyHasChangedWrapper(SCDynamicStoreRef aStore, CFArrayRef aChangedKeys, void* aInfo)
+{
+ static_cast<nsOSXSystemProxySettings*>(aInfo)->ProxyHasChanged();
+}
+
+
+nsOSXSystemProxySettings::nsOSXSystemProxySettings()
+ : mSystemDynamicStore(NULL), mProxyDict(NULL)
+{
+ mContext = (SCDynamicStoreContext){0, this, NULL, NULL, NULL};
+}
+
+nsresult
+nsOSXSystemProxySettings::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Register for notification of proxy setting changes
+ // See: http://developer.apple.com/documentation/Networking/Conceptual/CFNetwork/CFStreamTasks/chapter_4_section_5.html
+ mSystemDynamicStore = SCDynamicStoreCreate(NULL, CFSTR("Mozilla"), ProxyHasChangedWrapper, &mContext);
+ if (!mSystemDynamicStore)
+ return NS_ERROR_FAILURE;
+
+ // Set up the store to monitor any changes to the proxies
+ CFStringRef proxiesKey = SCDynamicStoreKeyCreateProxies(NULL);
+ if (!proxiesKey)
+ return NS_ERROR_FAILURE;
+
+ CFArrayRef keyArray = CFArrayCreate(NULL, (const void**)(&proxiesKey), 1, &kCFTypeArrayCallBacks);
+ CFRelease(proxiesKey);
+ if (!keyArray)
+ return NS_ERROR_FAILURE;
+
+ SCDynamicStoreSetNotificationKeys(mSystemDynamicStore, keyArray, NULL);
+ CFRelease(keyArray);
+
+ // Add the dynamic store to the run loop
+ CFRunLoopSourceRef storeRLSource = SCDynamicStoreCreateRunLoopSource(NULL, mSystemDynamicStore, 0);
+ if (!storeRLSource)
+ return NS_ERROR_FAILURE;
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLSource, kCFRunLoopCommonModes);
+ CFRelease(storeRLSource);
+
+ // Load the initial copy of proxy info
+ mProxyDict = (NSDictionary*)SCDynamicStoreCopyProxies(mSystemDynamicStore);
+ if (!mProxyDict)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsOSXSystemProxySettings::~nsOSXSystemProxySettings()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mProxyDict release];
+
+ if (mSystemDynamicStore) {
+ // Invalidate the dynamic store's run loop source
+ // to get the store out of the run loop
+ CFRunLoopSourceRef rls = SCDynamicStoreCreateRunLoopSource(NULL, mSystemDynamicStore, 0);
+ if (rls) {
+ CFRunLoopSourceInvalidate(rls);
+ CFRelease(rls);
+ }
+ CFRelease(mSystemDynamicStore);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+
+void
+nsOSXSystemProxySettings::ProxyHasChanged()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mProxyDict release];
+ mProxyDict = (NSDictionary*)SCDynamicStoreCopyProxies(mSystemDynamicStore);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult
+nsOSXSystemProxySettings::FindSCProxyPort(const nsACString &aScheme, nsACString& aResultHost, int32_t& aResultPort, bool& aResultSocksProxy)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_TRUE(mProxyDict != NULL, NS_ERROR_FAILURE);
+
+ for (const SchemeMapping* keys = gSchemeMappingList; keys->mScheme != NULL; ++keys) {
+ // Check for matching scheme (when appropriate)
+ if (strcasecmp(keys->mScheme, PromiseFlatCString(aScheme).get()) &&
+ !keys->mIsSocksProxy)
+ continue;
+
+ // Check the proxy is enabled
+ NSNumber* enabled = [mProxyDict objectForKey:(NSString*)keys->mEnabled];
+ NS_ENSURE_TRUE(enabled == NULL || [enabled isKindOfClass:[NSNumber class]], NS_ERROR_FAILURE);
+ if ([enabled intValue] == 0)
+ continue;
+
+ // Get the proxy host
+ NSString* host = [mProxyDict objectForKey:(NSString*)keys->mHost];
+ if (host == NULL)
+ break;
+ NS_ENSURE_TRUE([host isKindOfClass:[NSString class]], NS_ERROR_FAILURE);
+ aResultHost.Assign([host UTF8String]);
+
+ // Get the proxy port
+ NSNumber* port = [mProxyDict objectForKey:(NSString*)keys->mPort];
+ NS_ENSURE_TRUE([port isKindOfClass:[NSNumber class]], NS_ERROR_FAILURE);
+ aResultPort = [port intValue];
+
+ aResultSocksProxy = keys->mIsSocksProxy;
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+nsOSXSystemProxySettings::IsAutoconfigEnabled() const
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSNumber* value = [mProxyDict objectForKey:(NSString*)kSCPropNetProxiesProxyAutoConfigEnable];
+ NS_ENSURE_TRUE(value == NULL || [value isKindOfClass:[NSNumber class]], false);
+ return ([value intValue] != 0);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+nsresult
+nsOSXSystemProxySettings::GetAutoconfigURL(nsAutoCString& aResult) const
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString* value = [mProxyDict objectForKey:(NSString*)kSCPropNetProxiesProxyAutoConfigURLString];
+ if (value != NULL) {
+ NS_ENSURE_TRUE([value isKindOfClass:[NSString class]], NS_ERROR_FAILURE);
+ aResult.Assign([value UTF8String]);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+nsOSXSystemProxySettings::IsInExceptionList(const nsACString& aHost) const
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mProxyDict != NULL, false);
+
+ NSArray* exceptionList = [mProxyDict objectForKey:(NSString*)kSCPropNetProxiesExceptionsList];
+ NS_ENSURE_TRUE(exceptionList == NULL || [exceptionList isKindOfClass:[NSArray class]], false);
+
+ NSEnumerator* exceptionEnumerator = [exceptionList objectEnumerator];
+ NSString* currentValue = NULL;
+ while ((currentValue = [exceptionEnumerator nextObject])) {
+ NS_ENSURE_TRUE([currentValue isKindOfClass:[NSString class]], false);
+ nsAutoCString overrideStr([currentValue UTF8String]);
+ if (mozilla::toolkit::system::IsHostProxyEntry(aHost, overrideStr))
+ return true;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+nsresult
+nsOSXSystemProxySettings::GetPACURI(nsACString& aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_TRUE(mProxyDict != NULL, NS_ERROR_FAILURE);
+
+ nsAutoCString pacUrl;
+ if (IsAutoconfigEnabled() && NS_SUCCEEDED(GetAutoconfigURL(pacUrl))) {
+ aResult.Assign(pacUrl);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsOSXSystemProxySettings::GetProxyForURI(const nsACString & aSpec,
+ const nsACString & aScheme,
+ const nsACString & aHost,
+ const int32_t aPort,
+ nsACString & aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t proxyPort;
+ nsAutoCString proxyHost;
+ bool proxySocks;
+ nsresult rv = FindSCProxyPort(aScheme, proxyHost, proxyPort, proxySocks);
+
+ if (NS_FAILED(rv) || IsInExceptionList(aHost)) {
+ aResult.AssignLiteral("DIRECT");
+ } else if (proxySocks) {
+ aResult.Assign(NS_LITERAL_CSTRING("SOCKS ") + proxyHost + nsPrintfCString(":%d", proxyPort));
+ } else {
+ aResult.Assign(NS_LITERAL_CSTRING("PROXY ") + proxyHost + nsPrintfCString(":%d", proxyPort));
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#define NS_OSXSYSTEMPROXYSERVICE_CID /* 9afcd4b8-2e0f-41f4-8f1f-3bf0d3cf67de */\
+ { 0x9afcd4b8, 0x2e0f, 0x41f4, \
+ { 0x8f, 0x1f, 0x3b, 0xf0, 0xd3, 0xcf, 0x67, 0xde } }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsOSXSystemProxySettings, Init);
+NS_DEFINE_NAMED_CID(NS_OSXSYSTEMPROXYSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kOSXSysProxyCIDs[] = {
+ { &kNS_OSXSYSTEMPROXYSERVICE_CID, false, NULL, nsOSXSystemProxySettingsConstructor },
+ { NULL }
+};
+
+static const mozilla::Module::ContractIDEntry kOSXSysProxyContracts[] = {
+ { NS_SYSTEMPROXYSETTINGS_CONTRACTID, &kNS_OSXSYSTEMPROXYSERVICE_CID },
+ { NULL }
+};
+
+static const mozilla::Module kOSXSysProxyModule = {
+ mozilla::Module::kVersion,
+ kOSXSysProxyCIDs,
+ kOSXSysProxyContracts
+};
+
+NSMODULE_DEFN(nsOSXProxyModule) = &kOSXSysProxyModule;
diff --git a/toolkit/system/osxproxy/tests/gtest/TestProxyBypassRules.cpp b/toolkit/system/osxproxy/tests/gtest/TestProxyBypassRules.cpp
new file mode 100644
index 0000000000..7903491090
--- /dev/null
+++ b/toolkit/system/osxproxy/tests/gtest/TestProxyBypassRules.cpp
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; 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 "gtest/gtest.h"
+#include "ProxyUtils.h"
+
+using namespace mozilla::toolkit::system;
+
+TEST(OSXProxy, TestProxyBypassRules)
+{
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("mozilla.org"), NS_LITERAL_CSTRING("mozilla.org")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("mozilla.org"),NS_LITERAL_CSTRING("*mozilla.org")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("mozilla.org"), NS_LITERAL_CSTRING("*.mozilla.org")));
+ EXPECT_FALSE(IsHostProxyEntry(NS_LITERAL_CSTRING("notmozilla.org"), NS_LITERAL_CSTRING("*.mozilla.org")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("www.mozilla.org"), NS_LITERAL_CSTRING("*mozilla.org")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("www.mozilla.org"), NS_LITERAL_CSTRING("*.mozilla.org")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("www.mozilla.com"), NS_LITERAL_CSTRING("*.mozilla.*")));
+}
+
+TEST(OSXProxy, TestProxyBypassRulesIPv4)
+{
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("192.168.1.1"), NS_LITERAL_CSTRING("192.168.1.*")));
+ EXPECT_FALSE(IsHostProxyEntry(NS_LITERAL_CSTRING("192.168.1.1"), NS_LITERAL_CSTRING("192.168.2.*")));
+
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("10.1.2.3"), NS_LITERAL_CSTRING("10.0.0.0/8")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("192.168.192.1"), NS_LITERAL_CSTRING("192.168/16")));
+ EXPECT_FALSE(IsHostProxyEntry(NS_LITERAL_CSTRING("192.168.192.1"), NS_LITERAL_CSTRING("192.168/17")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("192.168.192.1"), NS_LITERAL_CSTRING("192.168.128/17")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("192.168.1.1"), NS_LITERAL_CSTRING("192.168.1.1/32")));
+}
+
+TEST(OSXProxy, TestProxyBypassRulesIPv6)
+{
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"), NS_LITERAL_CSTRING("2001:db8:abcd:0012::0/64")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("2001:0DB8:ABCD:0012:0000:4567:89AB:CDEF"), NS_LITERAL_CSTRING("2001:db8:abcd:0012::0/80")));
+ EXPECT_FALSE(IsHostProxyEntry(NS_LITERAL_CSTRING("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"), NS_LITERAL_CSTRING("2001:db8:abcd:0012::0/80")));
+ EXPECT_TRUE(IsHostProxyEntry(NS_LITERAL_CSTRING("2001:0DB8:ABCD:0012:0000:0000:89AB:CDEF"), NS_LITERAL_CSTRING("2001:db8:abcd:0012::0/96")));
+ EXPECT_FALSE(IsHostProxyEntry(NS_LITERAL_CSTRING("2001:0DB8:ABCD:0012:0123:4567:89AB:CDEF"), NS_LITERAL_CSTRING("2001:db8:abcd:0012::0/96")));
+}
diff --git a/toolkit/system/osxproxy/tests/gtest/moz.build b/toolkit/system/osxproxy/tests/gtest/moz.build
new file mode 100644
index 0000000000..94768a204e
--- /dev/null
+++ b/toolkit/system/osxproxy/tests/gtest/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+UNIFIED_SOURCES += [
+ 'TestProxyBypassRules.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '/toolkit/system/osxproxy',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wshadow']
diff --git a/toolkit/themes/moz.build b/toolkit/themes/moz.build
index b24036f1a0..74f53391e2 100644
--- a/toolkit/themes/moz.build
+++ b/toolkit/themes/moz.build
@@ -21,7 +21,9 @@ if CONFIG['MOZ_PHOENIX']:
else:
app = CONFIG['MOZ_BUILD_APP']
-if toolkit in ('gtk2', 'gtk3'):
+if toolkit == 'cocoa':
+ DIRS += ['osx']
+elif toolkit in ('gtk2', 'gtk3'):
DIRS += ['linux']
else:
DIRS += ['windows']
diff --git a/toolkit/themes/osx/global/10pct_transparent_grey.png b/toolkit/themes/osx/global/10pct_transparent_grey.png
new file mode 100644
index 0000000000..01f2edd9f4
--- /dev/null
+++ b/toolkit/themes/osx/global/10pct_transparent_grey.png
Binary files differ
diff --git a/toolkit/themes/osx/global/50pct_transparent_grey.png b/toolkit/themes/osx/global/50pct_transparent_grey.png
new file mode 100644
index 0000000000..0e462a46fa
--- /dev/null
+++ b/toolkit/themes/osx/global/50pct_transparent_grey.png
Binary files differ
diff --git a/toolkit/themes/osx/global/alerts/alert.css b/toolkit/themes/osx/global/alerts/alert.css
new file mode 100644
index 0000000000..3ca1a6e066
--- /dev/null
+++ b/toolkit/themes/osx/global/alerts/alert.css
@@ -0,0 +1,30 @@
+/* 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/. */
+
+/* ===== alert.css =====================================================
+ == Styles specific to the alerts dialog.
+ ======================================================================= */
+
+@import url("chrome://global/skin/alerts/alert-common.css");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+#alertNotification {
+ -moz-appearance: none;
+ background: transparent;
+}
+
+#alertBox {
+ border: 1px solid ThreeDShadow;
+ border-radius: 1px;
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+.alertCloseButton {
+ -moz-appearance: none;
+ padding: 0;
+ margin: 2px;
+ border: none;
+}
diff --git a/toolkit/themes/osx/global/arrow.css b/toolkit/themes/osx/global/arrow.css
new file mode 100644
index 0000000000..f8d14becab
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow.css
@@ -0,0 +1,38 @@
+/* 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/. */
+
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.up {
+ min-width: 0px;
+ list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+}
+.up[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
+}
+
+.down {
+ min-width: 0px;
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+.down[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
+}
+
+.left {
+ min-width: 0px;
+ list-style-image: url("chrome://global/skin/arrow/arrow-lft.gif");
+}
+.left[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-lft-dis.gif");
+}
+
+.right {
+ min-width: 0px;
+ list-style-image: url("chrome://global/skin/arrow/arrow-rit.gif");
+}
+.right[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-rit-dis.gif");
+}
diff --git a/toolkit/themes/osx/global/arrow/arrow-dn-dis.gif b/toolkit/themes/osx/global/arrow/arrow-dn-dis.gif
new file mode 100644
index 0000000000..3d62e40063
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-dn-dis.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-dn-dis.png b/toolkit/themes/osx/global/arrow/arrow-dn-dis.png
new file mode 100644
index 0000000000..a202fd85c9
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-dn-dis.png
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-dn-sharp.gif b/toolkit/themes/osx/global/arrow/arrow-dn-sharp.gif
new file mode 100644
index 0000000000..206d7c19dd
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-dn-sharp.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-dn.gif b/toolkit/themes/osx/global/arrow/arrow-dn.gif
new file mode 100644
index 0000000000..33849a6391
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-dn.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-dn.png b/toolkit/themes/osx/global/arrow/arrow-dn.png
new file mode 100644
index 0000000000..91486a3e9a
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-dn.png
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-lft-dis.gif b/toolkit/themes/osx/global/arrow/arrow-lft-dis.gif
new file mode 100644
index 0000000000..33243517b1
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-lft-dis.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-lft-hov.gif b/toolkit/themes/osx/global/arrow/arrow-lft-hov.gif
new file mode 100644
index 0000000000..3367bde312
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-lft-hov.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-lft-sharp-end.gif b/toolkit/themes/osx/global/arrow/arrow-lft-sharp-end.gif
new file mode 100644
index 0000000000..c22294ba21
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-lft-sharp-end.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-lft-sharp.gif b/toolkit/themes/osx/global/arrow/arrow-lft-sharp.gif
new file mode 100644
index 0000000000..ae9b1dd0fb
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-lft-sharp.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-lft.gif b/toolkit/themes/osx/global/arrow/arrow-lft.gif
new file mode 100644
index 0000000000..c5c362d89b
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-lft.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-rit-dis.gif b/toolkit/themes/osx/global/arrow/arrow-rit-dis.gif
new file mode 100644
index 0000000000..cda95fe215
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-rit-dis.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-rit-hov.gif b/toolkit/themes/osx/global/arrow/arrow-rit-hov.gif
new file mode 100644
index 0000000000..5010921adc
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-rit-hov.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-rit-sharp-end.gif b/toolkit/themes/osx/global/arrow/arrow-rit-sharp-end.gif
new file mode 100644
index 0000000000..c1b3750d4c
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-rit-sharp-end.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-rit-sharp.gif b/toolkit/themes/osx/global/arrow/arrow-rit-sharp.gif
new file mode 100644
index 0000000000..ca628ba69b
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-rit-sharp.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-rit.gif b/toolkit/themes/osx/global/arrow/arrow-rit.gif
new file mode 100644
index 0000000000..dce39aecc1
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-rit.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-up-dis.gif b/toolkit/themes/osx/global/arrow/arrow-up-dis.gif
new file mode 100644
index 0000000000..381dee3e5d
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-up-dis.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-up-sharp.gif b/toolkit/themes/osx/global/arrow/arrow-up-sharp.gif
new file mode 100644
index 0000000000..883a4f95ca
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-up-sharp.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/arrow-up.gif b/toolkit/themes/osx/global/arrow/arrow-up.gif
new file mode 100644
index 0000000000..b8e09b21b8
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/arrow-up.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/panelarrow-horizontal.png b/toolkit/themes/osx/global/arrow/panelarrow-horizontal.png
new file mode 100644
index 0000000000..c110f8592d
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/panelarrow-horizontal.png
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/panelarrow-horizontal@2x.png b/toolkit/themes/osx/global/arrow/panelarrow-horizontal@2x.png
new file mode 100644
index 0000000000..4cb7353e70
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/panelarrow-horizontal@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/panelarrow-vertical.png b/toolkit/themes/osx/global/arrow/panelarrow-vertical.png
new file mode 100644
index 0000000000..3986f9cbf5
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/panelarrow-vertical.png
Binary files differ
diff --git a/toolkit/themes/osx/global/arrow/panelarrow-vertical@2x.png b/toolkit/themes/osx/global/arrow/panelarrow-vertical@2x.png
new file mode 100644
index 0000000000..a741dd0e16
--- /dev/null
+++ b/toolkit/themes/osx/global/arrow/panelarrow-vertical@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/autocomplete.css b/toolkit/themes/osx/global/autocomplete.css
new file mode 100644
index 0000000000..7e05d2f29c
--- /dev/null
+++ b/toolkit/themes/osx/global/autocomplete.css
@@ -0,0 +1,174 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/* .padded is used by autocomplete widgets that don't have an icon. Gross. -dwh */
+textbox:not(.padded) {
+ cursor: default;
+ padding: 0;
+}
+
+textbox[nomatch="true"][highlightnonmatches="true"] {
+ color: red;
+}
+
+textbox:not(.padded) .textbox-input-box {
+ margin: 0 3px;
+}
+
+.textbox-input-box {
+ -moz-box-align: center;
+}
+
+/* ::::: history button ::::: */
+
+.autocomplete-history-dropmarker {
+ -moz-appearance: none !important;
+ border: none !important;
+ background-color: transparent !important;
+ padding: 0px;
+ list-style-image: url("chrome://global/skin/icons/autocomplete-dropmarker.png");
+ margin: 0px;
+}
+
+/* ::::: autocomplete popups ::::: */
+
+panel[type="autocomplete"],
+panel[type="autocomplete-richlistbox"],
+.autocomplete-history-popup {
+ padding: 0px !important;
+ color: -moz-FieldText;
+ background-color: -moz-Field;
+ font: icon;
+ -moz-appearance: none;
+}
+
+.autocomplete-history-popup {
+ max-height: 180px;
+}
+
+/* ::::: tree ::::: */
+
+.autocomplete-tree {
+ -moz-appearance: none !important;
+ border: none !important;
+ background-color: transparent !important;
+}
+
+.autocomplete-treecol {
+ -moz-appearance: none !important;
+ margin: 0 !important;
+ border: none !important;
+ padding: 0 !important;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text {
+ padding-left: 2px;
+}
+
+.autocomplete-treebody::-moz-tree-row {
+ border-top: none;
+}
+
+treechildren.autocomplete-treebody::-moz-tree-row(selected) {
+ background-color: Highlight;
+}
+
+treechildren.autocomplete-treebody::-moz-tree-cell-text(selected) {
+ color: HighlightText !important;
+}
+
+.autocomplete-treebody::-moz-tree-image(treecolAutoCompleteValue) {
+ max-width: 16px;
+ height: 16px;
+}
+
+/* ::::: richlistbox autocomplete ::::: */
+
+.autocomplete-richlistbox {
+ -moz-appearance: none;
+ margin: 0;
+}
+
+.ac-type-icon {
+ width: 16px;
+ height: 16px;
+ max-width: 16px;
+ max-height: 16px;
+ margin-inline-start: 16px;
+ margin-inline-end: 6px;
+}
+
+.ac-site-icon {
+ width: 16px;
+ height: 16px;
+ max-width: 16px;
+ max-height: 16px;
+ margin-inline-start: 0;
+ margin-inline-end: 11px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.ac-site-icon[selected] {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon-inverted.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .ac-site-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon@2x.png");
+ }
+ .ac-site-icon[selected] {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon-inverted@2x.png");
+ }
+}
+
+.ac-title {
+ margin-inline-start: 0;
+ margin-inline-end: 6px;
+}
+
+html|span.ac-tag {
+ margin-inline-start: 0;
+ margin-inline-end: 2px;
+}
+
+.ac-tags {
+ margin-inline-start: 0;
+ margin-inline-end: 4px;
+}
+
+.ac-separator {
+ margin-inline-start: 0;
+ margin-inline-end: 6px;
+}
+
+/* Better align the URL/action with the title. */
+.ac-tags,
+.ac-separator,
+.ac-url,
+.ac-action {
+ margin-bottom: -2px;
+}
+
+.ac-title-text,
+.ac-tags-text,
+.ac-separator-text,
+.ac-url-text,
+.ac-action-text,
+.ac-text-overflow-container {
+ padding: 0 !important;
+ margin: 0 !important;
+}
+
+/* ::::: textboxes inside toolbarpaletteitems ::::: */
+
+toolbarpaletteitem > toolbaritem > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
+
+toolbarpaletteitem > toolbaritem > * > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
diff --git a/toolkit/themes/osx/global/button.css b/toolkit/themes/osx/global/button.css
new file mode 100644
index 0000000000..45f292e1f1
--- /dev/null
+++ b/toolkit/themes/osx/global/button.css
@@ -0,0 +1,85 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+button {
+ -moz-appearance: button;
+ /* The horizontal margin used here come from the Aqua Human Interface
+ Guidelines, there should be 12 pixels between two buttons. */
+ margin: 5px 6px 3px;
+ min-width: 79px;
+ color: ButtonText;
+ text-shadow: none;
+}
+
+button:not([disabled="true"]):hover:active {
+ color: -moz-mac-buttonactivetext;
+}
+
+/* When the window isn't focused, the default button background isn't drawn,
+ * so don't change the text color then: */
+button[default="true"]:not([disabled="true"]):not(:-moz-window-inactive) {
+ color: -moz-mac-defaultbuttontext;
+}
+
+/* Likewise, when active (mousedown) but not hovering, the default button
+ * background isn't drawn, override the previous selector for that case: */
+button[default="true"]:not(:hover):active {
+ color: ButtonText;
+}
+
+.button-text {
+ margin: 1px 0 !important;
+ margin-inline-start: 3px !important;
+ margin-inline-end: 2px !important;
+ text-align: center;
+}
+
+.button-icon {
+ margin-inline-start: 1px;
+}
+
+button[type="default"] {
+ font: menu;
+}
+
+/* .......... disabled state .......... */
+
+button[disabled="true"] {
+ color: GrayText;
+}
+
+/* ::::: menu/menu-button buttons ::::: */
+
+button[type="menu-button"] {
+ margin: 0;
+ border: none;
+}
+
+.button-menu-dropmarker,
+.button-menubutton-dropmarker {
+ -moz-appearance: none !important;
+ border: none;
+ background-color: transparent !important;
+ margin: 1px;
+}
+
+.button-menu-dropmarker {
+ display: none;
+}
+
+/* ::::: plain buttons ::::: */
+
+button.plain {
+ margin: 0 !important;
+ padding: 0 !important;
+}
+
+/* ::::: help button ::::: */
+
+button[dlgtype="help"] {
+ -moz-appearance: -moz-mac-help-button;
+ width: 20px;
+}
diff --git a/toolkit/themes/osx/global/checkbox.css b/toolkit/themes/osx/global/checkbox.css
new file mode 100644
index 0000000000..b49af98d04
--- /dev/null
+++ b/toolkit/themes/osx/global/checkbox.css
@@ -0,0 +1,39 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+checkbox {
+ -moz-appearance: checkbox-container;
+ -moz-box-align: center;
+ margin: 4px 2px;
+}
+
+.checkbox-icon {
+ margin-right: 2px;
+}
+
+.checkbox-label {
+ margin: 1px 0 !important;
+}
+
+/* ..... disabled state ..... */
+
+checkbox[disabled="true"] {
+ color: GrayText !important;
+}
+
+/* ::::: checkmark image ::::: */
+
+.checkbox-check {
+ -moz-appearance: checkbox;
+ margin: 1px 1px 0;
+ /* vertical-align tells native theming where to snap to. However, this doesn't
+ * always work reliably because of bug 503833. */
+ vertical-align: top;
+ width: 1.3em;
+ height: 1.3em;
+}
+
+
diff --git a/toolkit/themes/osx/global/checkbox/cbox-check-dis.gif b/toolkit/themes/osx/global/checkbox/cbox-check-dis.gif
new file mode 100644
index 0000000000..bd43dd17c3
--- /dev/null
+++ b/toolkit/themes/osx/global/checkbox/cbox-check-dis.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/checkbox/cbox-check.gif b/toolkit/themes/osx/global/checkbox/cbox-check.gif
new file mode 100644
index 0000000000..f6919f8fad
--- /dev/null
+++ b/toolkit/themes/osx/global/checkbox/cbox-check.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/colorpicker.css b/toolkit/themes/osx/global/colorpicker.css
new file mode 100644
index 0000000000..075437db89
--- /dev/null
+++ b/toolkit/themes/osx/global/colorpicker.css
@@ -0,0 +1,41 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* ::::: colorpicker button ::::: */
+
+colorpicker[type="button"] {
+ width: 38px;
+ height: 24px;
+ border: 1px solid #a7a7a7;
+ background-color: ThreeDFace;
+ padding: 3px;
+ -moz-appearance: button-bevel;
+}
+
+.colorpicker-button-colorbox {
+ border: 1px solid #000000;
+}
+
+/* ::::: colorpicker tiles ::::: */
+
+.colorpickertile {
+ width : 20px;
+ height : 20px;
+ margin : 1px;
+}
+
+.colorpickertile[selected="true"] {
+ border : 1px outset #C0C0C0;
+
+}
+
+.colorpickertile[hover="true"] {
+ border : 1px dotted #A7A7A7;
+}
+
+.cp-light[hover="true"] {
+ border : 1px dotted #000000;
+}
diff --git a/toolkit/themes/osx/global/commonDialog.css b/toolkit/themes/osx/global/commonDialog.css
new file mode 100644
index 0000000000..53b02796d2
--- /dev/null
+++ b/toolkit/themes/osx/global/commonDialog.css
@@ -0,0 +1,35 @@
+/* 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/. */
+
+#commonDialog {
+ line-height: 13px;
+}
+
+#filler {
+ margin: 0px -14px;
+}
+
+#infoContainer {
+ max-width: 33em;
+}
+
+#loginContainer {
+ padding-top: 10px;
+}
+
+#info\.icon {
+ margin-inline-end: 14px;
+}
+
+#info\.title,
+#info\.header,
+#info\.body {
+ font: menu;
+ line-height: 16px;
+ margin-bottom: 6px;
+}
+
+#info\.title {
+ font-weight: bold;
+}
diff --git a/toolkit/themes/osx/global/console/console-error-caret.gif b/toolkit/themes/osx/global/console/console-error-caret.gif
new file mode 100644
index 0000000000..a8f30f9263
--- /dev/null
+++ b/toolkit/themes/osx/global/console/console-error-caret.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/console/console-error-dash.gif b/toolkit/themes/osx/global/console/console-error-dash.gif
new file mode 100644
index 0000000000..74679a25e2
--- /dev/null
+++ b/toolkit/themes/osx/global/console/console-error-dash.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/console/console.css b/toolkit/themes/osx/global/console/console.css
new file mode 100644
index 0000000000..f18f6f680c
--- /dev/null
+++ b/toolkit/themes/osx/global/console/console.css
@@ -0,0 +1,165 @@
+/* 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/. */
+
+/* ===== console.css ====================================================
+ == Styles used by the Error Console window.
+ ======================================================================= */
+
+/* View buttons */
+@import "chrome://global/skin/viewbuttons.css";
+
+%include ../shared.inc
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.console-box {
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+ overflow: auto;
+}
+
+/* ::::: console rows ::::: */
+
+.console-row {
+ border-bottom: 1px solid #A3A3A3;
+ padding: 4px;
+}
+
+.console-row-file {
+ color: #505050;
+}
+
+.console-row-msg > label:first-child {
+ font-weight: bold;
+}
+
+.console-row-msg > label, .comsole-row-msg > description, .console-error-msg, .console-row-file, .console-row-code {
+ margin: 2px;
+}
+
+.console-row-file > label {
+ margin: 0;
+}
+
+.console-msg-text {
+ white-space: pre-wrap !important;
+}
+.console-icon {
+ list-style-image: inherit;
+ padding-right: 6px;
+ padding-left: 6px;
+}
+
+/* ..... error rows ..... */
+
+.console-row-code {
+ color: #0000BB;
+ font-size: larger;
+}
+
+.console-dots,
+.console-caret {
+ height: 9px;
+}
+
+.console-dots {
+ background: url("chrome://global/skin/console/console-error-dash.gif") repeat-x top;
+}
+
+.console-caret {
+ width: 7px;
+ background: url("chrome://global/skin/console/console-error-caret.gif") no-repeat top;
+}
+
+/* ..... message rows ..... */
+
+.console-row[type="message"] {
+ font-family: monospace;
+}
+
+/* ..... selected state ..... */
+
+.console-row[selected="true"] {
+ background-color: #3D80DF !important;
+ color: #FFF;
+}
+
+.console-row-code[selected="true"],
+.console-row-content[selected="true"] > .console-row-file,
+.console-row-content[selected="true"] > .console-row-file > .console-error-source > .text-link {
+ color: #FFF !important;
+}
+
+/* ::::: row colors ::::: */
+
+.console-row[type="error"],
+.console-row[type="exception"] {
+ background-color: #FFD0DC;
+}
+
+.console-row[type="warning"] {
+ background-color: #F8F3CC;
+}
+
+.console-row[type="message"] {
+ background-color: #D3EDFF;
+}
+
+/* ::::: toolbars ::::: */
+
+#ToolbarEval {
+ -moz-appearance: none;
+ background: @scopeBarBackground@;
+ border-bottom: @scopeBarSeparatorBorder@;
+ padding: 2px;
+}
+
+#ToolbarEval > label {
+ font-weight: bold;
+ color: @scopeBarTitleColor@;
+}
+
+#TextfieldEval {
+ margin: 2px !important;
+}
+
+#ButtonEval {
+ margin: 0 4px;
+ padding: 1px 10px;
+ -moz-appearance: none;
+ border-radius: 10000px;
+ border: @roundButtonBorder@;
+ background: @roundButtonBackground@;
+ box-shadow: @roundButtonShadow@;
+}
+
+#ButtonEval:hover:active {
+ text-shadow: @loweredShadow@;
+ background: @roundButtonPressedBackground@;
+ box-shadow: @roundButtonPressedShadow@;
+}
+
+toolbarseparator {
+ min-height: 1em;
+ background-image: none;
+}
+
+/* Toolbar icons */
+
+#ToolbarMode {
+ -moz-box-pack: center;
+}
+
+#ToolbarMode toolbarbutton > .toolbarbutton-icon {
+ display: none;
+}
+
+#Console\:clear {
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+ -moz-appearance: toolbarbutton;
+ font: menu;
+ text-shadow: @loweredShadow@;
+ margin: 4px 0 9px;
+ padding: 0 1px;
+}
diff --git a/toolkit/themes/osx/global/customizeToolbar.css b/toolkit/themes/osx/global/customizeToolbar.css
new file mode 100644
index 0000000000..bcedb2b99a
--- /dev/null
+++ b/toolkit/themes/osx/global/customizeToolbar.css
@@ -0,0 +1,38 @@
+/* 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/. */
+
+#palette-box {
+ margin-top: 2px;
+ -moz-appearance: listbox;
+ margin: 0 0 10px;
+}
+
+#palette-box > toolbarpaletteitem {
+ padding: 8px 2px;
+ margin: 0 8px;
+}
+
+#main-box {
+ padding: 12px;
+}
+
+#main-box > separator {
+ -moz-appearance: none;
+ border-bottom: none;
+}
+
+#instructions {
+ font: menu;
+ font-weight: bold;
+ line-height: 16pt;
+}
+
+hbox button {
+ font: menu;
+}
+
+#main-box > box > button {
+ min-height: 19px; /* aqua size for small buttons */
+ font: message-box;
+}
diff --git a/toolkit/themes/osx/global/datetimepicker.css b/toolkit/themes/osx/global/datetimepicker.css
new file mode 100644
index 0000000000..3d7b201f2e
--- /dev/null
+++ b/toolkit/themes/osx/global/datetimepicker.css
@@ -0,0 +1,126 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+datepicker, timepicker {
+ padding: 0 0 1px;
+ margin: 4px;
+ border: none;
+}
+
+.datetimepicker-input-box {
+ -moz-appearance: textfield;
+ cursor: text;
+ margin-right: 4px;
+ margin-bottom: 2px;
+ border: 3px solid;
+ -moz-border-top-colors: transparent #888888 #000000;
+ -moz-border-right-colors: transparent #FFFFFF #000000;
+ -moz-border-bottom-colors: transparent #FFFFFF #000000;
+ -moz-border-left-colors: transparent #888888 #000000;
+ border-top-right-radius: 2px;
+ border-bottom-left-radius: 2px;
+ padding: 0px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+}
+
+.datetimepicker-input-subbox {
+ width: 1.6em;
+}
+
+html|*.datetimepicker-input {
+ text-align: end;
+}
+
+.datetimepicker-separator {
+ margin: 0 !important;
+}
+
+.datetimepicker-year {
+ width: 3.2em;
+}
+
+.datepicker-dropmarker {
+ margin-bottom: 2px;
+}
+
+datepicker[readonly="true"],
+timepicker[readonly="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+datepicker[disabled="true"],
+timepicker[disabled="true"] {
+ cursor: default;
+ -moz-border-top-colors: transparent ThreeDShadow -moz-Dialog;
+ -moz-border-right-colors: transparent ThreeDShadow -moz-Dialog;
+ -moz-border-bottom-colors: transparent ThreeDShadow -moz-Dialog;
+ -moz-border-left-colors: transparent ThreeDShadow -moz-Dialog;
+ background-color: -moz-Dialog;
+ color: GrayText;
+}
+
+.datepicker-mainbox {
+ margin: 2px 4px;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ background-color: #EEEEEE;
+ color: -moz-DialogText;
+}
+
+.datepicker-popupgrid > .datepicker-mainbox {
+ margin: 0;
+ border: none;
+}
+
+.datepicker-gridlabel, .datepicker-weeklabel {
+ text-align: center;
+}
+
+.datepicker-gridlabel[today="true"] {
+ background-color: darkgrey;
+ color: white;
+}
+
+.datepicker-gridlabel[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.datepicker-button {
+ -moz-appearance: none;
+ min-width: 8px;
+ padding: 0px;
+}
+
+.datepicker-previous {
+ list-style-image: url("chrome://global/skin/arrow/arrow-lft.gif");
+}
+
+.datepicker-next {
+ list-style-image: url("chrome://global/skin/arrow/arrow-rit.gif");
+}
+
+.datepicker-previous:hover {
+ list-style-image: url("chrome://global/skin/arrow/arrow-lft-hov.gif");
+}
+
+.datepicker-next:hover {
+ list-style-image: url("chrome://global/skin/arrow/arrow-rit-hov.gif");
+}
+
+.datepicker-previous[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-lft-dis.gif");
+}
+
+.datepicker-next[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-rit-dis.gif");
+}
diff --git a/toolkit/themes/osx/global/dialog.css b/toolkit/themes/osx/global/dialog.css
new file mode 100644
index 0000000000..98ed3ca207
--- /dev/null
+++ b/toolkit/themes/osx/global/dialog.css
@@ -0,0 +1,77 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+#commonDialog > image {
+ margin-inline-end: 14px !important;
+}
+
+#commonDialog > .dialog-button-box {
+ margin-inline-start: 80px;
+}
+
+dialog {
+ -moz-appearance: dialog;
+ padding: 14px;
+}
+
+/* ::::: dialog buttons ::::: */
+
+.dialog-button {
+ font: menu;
+}
+
+/* ::::: dialog header ::::: */
+
+dialogheader {
+ margin: 0 5px 5px;
+ padding: 5px 8px;
+}
+
+.dialogheader-title {
+ margin: 0 !important;
+ font-size: larger;
+ font-weight: bold;
+ display: none;
+}
+
+/* ::::: large dialog header ::::: */
+
+.header-large {
+ -moz-box-orient: vertical;
+ margin: -14px -14px 0;
+ padding: 12px;
+ padding-inline-end: 5px;
+ padding-inline-start: 25px;
+}
+
+.header-large > .dialogheader-title {
+ font: inherit;
+ font-weight: bold;
+}
+
+.header-large > .dialogheader-description {
+ margin-left: 12px !important;
+}
+
+.dialogheader-description {
+ font-weight: bold !important;
+}
+
+.dialogheader-title {
+ font-weight: bold !important;
+}
+
+/*XXX - belongs to toolkit/content/finddialog.xul: */
+
+#findDialog,
+#findDialog > menu,
+#findDialog > groupbox {
+ font: menu !important;
+}
+
+#dialog\.caseSensitive {
+ margin-top: 8px;
+}
diff --git a/toolkit/themes/osx/global/dirListing/dirListing.css b/toolkit/themes/osx/global/dirListing/dirListing.css
new file mode 100644
index 0000000000..de881a5e4b
--- /dev/null
+++ b/toolkit/themes/osx/global/dirListing/dirListing.css
@@ -0,0 +1,104 @@
+/* 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/. */
+
+:root {
+ background-color: -moz-dialog;
+ color: -moz-dialogtext;
+ font: message-box;
+ padding-left: 2em;
+ padding-right: 2em;
+}
+
+body {
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ padding: 3em;
+ min-width: 30em;
+ max-width: 65em;
+ margin: 4em auto;
+ background-color: -moz-field;
+ color: -moz-fieldtext;
+}
+
+h1 {
+ font-size: 160%;
+ margin: 0 0 .6em;
+ border-bottom: 1px solid ThreeDLightShadow;
+ font-weight: normal;
+}
+
+a {
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+p {
+ font-size: 110%;
+}
+
+#UI_goUp {
+ margin-top: 0;
+ float: left;
+}
+
+#UI_goUp:dir(rtl) {
+ float: right;
+}
+
+#UI_showHidden {
+ margin-top: 0;
+ float: right;
+}
+
+#UI_showHidden:dir(rtl) {
+ float: left;
+}
+
+table {
+ clear: both;
+ width: 90%;
+ margin: 0 auto;
+}
+
+thead {
+ font-size: 130%;
+}
+
+/* last modified */
+th:last-child {
+ text-align: center;
+}
+
+th:hover > a {
+ text-decoration: underline;
+}
+
+body > table > tbody > tr:hover {
+ outline: 1px solid ThreeDLightShadow;
+ -moz-outline-radius: .3em;
+}
+
+/* let 'Size' and 'Last Modified' take only as much space as they need and 'Name' all the rest */
+td:not(:first-child) {
+ width: 0;
+}
+
+.up {
+ padding: 0 .5em;
+ margin-inline-start: 20px;
+}
+
+.up::before {
+ margin-inline-end: 4px;
+ margin-inline-start: -20px;
+ vertical-align: middle;
+ content: url(chrome://global/skin/dirListing/up.png);
+}
+
+.dir::before {
+ content: url(chrome://global/skin/dirListing/folder.png);
+}
diff --git a/toolkit/themes/osx/global/dirListing/folder.png b/toolkit/themes/osx/global/dirListing/folder.png
new file mode 100644
index 0000000000..eb3a607e03
--- /dev/null
+++ b/toolkit/themes/osx/global/dirListing/folder.png
Binary files differ
diff --git a/toolkit/themes/osx/global/dirListing/remote.png b/toolkit/themes/osx/global/dirListing/remote.png
new file mode 100644
index 0000000000..d854bd9d9f
--- /dev/null
+++ b/toolkit/themes/osx/global/dirListing/remote.png
Binary files differ
diff --git a/toolkit/themes/osx/global/dirListing/up.png b/toolkit/themes/osx/global/dirListing/up.png
new file mode 100644
index 0000000000..7af8949ad3
--- /dev/null
+++ b/toolkit/themes/osx/global/dirListing/up.png
Binary files differ
diff --git a/toolkit/themes/osx/global/dropmarker.css b/toolkit/themes/osx/global/dropmarker.css
new file mode 100644
index 0000000000..701eea75c4
--- /dev/null
+++ b/toolkit/themes/osx/global/dropmarker.css
@@ -0,0 +1,31 @@
+/* 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/. */
+
+dropmarker {
+ -moz-appearance: menulist-button;
+ width: 16px;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDLightShadow ThreeDHighlight;
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-left-colors: ThreeDLightShadow ThreeDHighlight;
+ background-color: -moz-Dialog;
+ padding: 1px;
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+ -moz-image-region: auto;
+}
+
+dropmarker:hover:active:not([disabled="true"]) {
+ -moz-border-top-colors: ThreeDShadow ThreeDFace;
+ -moz-border-right-colors: ThreeDShadow ThreeDFace;
+ -moz-border-bottom-colors: ThreeDShadow ThreeDFace;
+ -moz-border-left-colors: ThreeDShadow ThreeDFace;
+ padding: 2px 0 0 2px;
+}
+
+dropmarker[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
+}
diff --git a/toolkit/themes/osx/global/filefield.css b/toolkit/themes/osx/global/filefield.css
new file mode 100644
index 0000000000..8ae3fdb52a
--- /dev/null
+++ b/toolkit/themes/osx/global/filefield.css
@@ -0,0 +1,38 @@
+/*
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# 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/.
+*/
+
+.fileFieldIcon {
+ width: 16px;
+ height: 16px;
+}
+
+.fileFieldIcon[disabled="true"] {
+ opacity: 0.5;
+}
+
+filefield {
+ margin: 4px;
+ margin-inline-start: 27px;
+ -moz-appearance: textfield;
+}
+
+.fileFieldContentBox {
+ margin: -3px;
+ background-color: rgba(230, 230, 230, 0.6);
+ color: -moz-DialogText;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-inline-start: 5px;
+ padding-inline-end: 3px;
+}
+
+.fileFieldLabel {
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+ margin: 0 4px;
+}
diff --git a/toolkit/themes/osx/global/filters.svg b/toolkit/themes/osx/global/filters.svg
new file mode 100644
index 0000000000..d3ad6a76b8
--- /dev/null
+++ b/toolkit/themes/osx/global/filters.svg
@@ -0,0 +1,14 @@
+<!-- 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/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="iconPressed" color-interpolation-filters="sRGB">
+ <!-- Multiply all components with 0.55. -->
+ <feComponentTransfer>
+ <feFuncR type="linear" slope=".55"/>
+ <feFuncG type="linear" slope=".55"/>
+ <feFuncB type="linear" slope=".55"/>
+ </feComponentTransfer>
+ </filter>
+</svg>
diff --git a/toolkit/themes/osx/global/findBar.css b/toolkit/themes/osx/global/findBar.css
new file mode 100644
index 0000000000..869a624322
--- /dev/null
+++ b/toolkit/themes/osx/global/findBar.css
@@ -0,0 +1,270 @@
+/* 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 shared.inc
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+findbar {
+ background: @scopeBarBackground@;
+ border-top: @scopeBarSeparatorBorder@;
+ min-width: 1px;
+ padding: 4px 2px;
+ transition-property: margin-bottom, opacity, visibility;
+ transition-duration: 150ms, 150ms, 0s;
+ transition-timing-function: ease-in-out, ease-in-out, linear;
+}
+
+findbar[hidden] {
+ /* Override display:none to make the transition work. */
+ display: -moz-box;
+ visibility: collapse;
+ margin-bottom: -1em;
+ opacity: 0;
+ transition-delay: 0s, 0s, 150ms;
+}
+
+findbar:-moz-lwtheme {
+ -moz-appearance: none;
+ background: none;
+ border-style: none;
+}
+
+label.findbar-find-fast {
+ margin: 1px 3px 0 !important;
+ color: @scopeBarTitleColor@;
+ font-weight: bold;
+ text-shadow: @loweredShadow@;
+}
+
+label.findbar-find-fast:-moz-lwtheme,
+.findbar-find-status:-moz-lwtheme {
+ color: inherit;
+ text-shadow: inherit;
+}
+
+.findbar-closebutton {
+ padding: 0;
+ margin: 0 4px;
+ border: none;
+}
+
+.findbar-closebutton:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .findbar-closebutton:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+
+ .findbar-closebutton > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+.findbar-find-next,
+.findbar-find-previous,
+.findbar-highlight {
+ margin: 0 4px;
+ padding: 1px 3px;
+ -moz-appearance: none;
+ border-radius: 10000px;
+ border: @roundButtonBorder@;
+ background: @roundButtonBackground@;
+ box-shadow: @roundButtonShadow@;
+ color: buttontext;
+}
+
+.findbar-container > toolbarbutton:-moz-focusring {
+ position: relative;
+ box-shadow: @focusRingShadow@, @roundButtonShadow@;
+}
+
+.findbar-container > toolbarbutton > .toolbarbutton-text {
+ margin: 0 6px !important;
+}
+
+.findbar-container > toolbarbutton[disabled] {
+ color: GrayText !important;
+}
+
+.findbar-find-next:not([disabled]):hover:active,
+.findbar-find-previous:not([disabled]):hover:active,
+.findbar-highlight:not([disabled]):hover:active {
+ text-shadow: @loweredShadow@;
+ background: @roundButtonPressedBackground@;
+ box-shadow: @roundButtonPressedShadow@;
+}
+
+.findbar-container > toolbarbutton:hover:active:-moz-focusring {
+ text-shadow: @loweredShadow@;
+ background: @roundButtonPressedBackground@;
+ box-shadow: @focusRingShadow@, @roundButtonPressedShadow@;
+}
+
+.findbar-closebutton > .toolbarbutton-text {
+ display: none;
+}
+
+/* Match case checkbox */
+
+.findbar-container > checkbox {
+ list-style-image: url("chrome://global/skin/icons/checkbox.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+ -moz-appearance: none;
+ margin: 0 2px;
+ -moz-margin-start: 7px;
+}
+
+.findbar-container > checkbox:hover:active {
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+.findbar-container > checkbox[checked] {
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+.findbar-container > checkbox[checked]:hover:active {
+ -moz-image-region: rect(0px 64px 16px 48px);
+}
+
+@media (min-resolution: 2dppx) {
+ .findbar-container > checkbox {
+ list-style-image: url("chrome://global/skin/icons/checkbox@2x.png");
+ -moz-image-region: rect(0px 32px 32px 0px);
+ }
+
+ .findbar-container > checkbox:hover:active {
+ -moz-image-region: rect(0px 64px 32px 32px);
+ }
+ .findbar-container > checkbox[checked] {
+ -moz-image-region: rect(0px 96px 32px 64px);
+ }
+ .findbar-container > checkbox[checked]:hover:active {
+ -moz-image-region: rect(0px 128px 32px 96px);
+ }
+}
+
+
+
+.findbar-container > checkbox > .checkbox-check {
+ display: none;
+}
+
+.findbar-container > checkbox > .checkbox-label-box > .checkbox-label {
+ margin: 0 !important;
+ padding: 2px 0 0;
+}
+
+.findbar-container > checkbox > .checkbox-label-box > .checkbox-icon {
+ -moz-padding-start: 1px;
+ padding-bottom: 1px;
+}
+@media (min-resolution: 2dppx) {
+ .findbar-container > checkbox > .checkbox-label-box > .checkbox-icon {
+ width: 17px;
+ height: 17px;
+ }
+}
+
+.findbar-container > checkbox:-moz-focusring > .checkbox-label-box > .checkbox-icon {
+ border-radius: 4px;
+ box-shadow: @focusRingShadow@;
+}
+
+/* Search field */
+
+.findbar-textbox {
+ -moz-appearance: none;
+ border-radius: 10000px;
+ border: none;
+ box-shadow: 0 1px 1.5px rgba(0, 0, 0, .7) inset,
+ 0 0 0 1px rgba(0, 0, 0, .17) inset;
+ background: url("chrome://global/skin/icons/search-textbox.png") -moz-Field no-repeat 5px center;
+ margin: 0 4px -1px;
+ padding: 3px 8px 2px;
+ -moz-padding-start: 19px;
+}
+
+.findbar-textbox:not([focused="true"]):-moz-lwtheme {
+ opacity: 0.9;
+}
+
+.findbar-textbox[focused="true"] {
+ box-shadow: @focusRingShadow@,
+ 0 1px 1.5px rgba(0, 0, 0, .8) inset;
+}
+
+.findbar-textbox[flash="true"] {
+ background-color: #F7E379;
+ color: #000;
+}
+
+.findbar-textbox[status="notfound"] {
+ background-color: #FD919B;
+ color: #FFF;
+}
+
+/* find-next button */
+
+.findbar-find-next {
+ -moz-border-end: none;
+ -moz-margin-end: 0 !important;
+}
+
+.findbar-find-next:-moz-locale-dir(ltr),
+.findbar-find-previous:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0px;
+ border-bottom-right-radius: 0px;
+}
+
+/* find-previous button */
+
+.findbar-find-previous {
+ -moz-margin-start: 0 !important;
+}
+
+.findbar-find-previous:-moz-locale-dir(ltr),
+.findbar-find-next:-moz-locale-dir(rtl) {
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
+}
+
+/* highlight button */
+
+.findbar-highlight {
+ -moz-margin-start: 8px;
+}
+
+.findbar-highlight > .toolbarbutton-icon {
+ width: 13px;
+ height: 8px;
+ margin: 0 4px;
+ -moz-margin-end: 0;
+ border: 1px solid #818181;
+ border-radius: 4px;
+ background-color: #F4F4F3;
+}
+
+
+.findbar-highlight[checked="true"] > .toolbarbutton-icon {
+ background-color: #FFFF00;
+ border-color: #818100;
+}
+
+.find-status-icon {
+ display: none;
+}
+
+.find-status-icon[status="pending"] {
+ display: block;
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.findbar-find-status,
+.found-matches {
+ color: #436599;
+ font-weight: bold;
+ text-shadow: 0 1px rgba(255, 255, 255, .4);
+ margin: 1px 1px 0 !important;
+ -moz-margin-start: 12px !important;
+}
diff --git a/toolkit/themes/osx/global/global.css b/toolkit/themes/osx/global/global.css
new file mode 100644
index 0000000000..261abe3138
--- /dev/null
+++ b/toolkit/themes/osx/global/global.css
@@ -0,0 +1,378 @@
+/* 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/. */
+
+/* all localizable skin settings shall live here */
+@import url("chrome://global/locale/intl.css");
+
+%include shared.inc
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* ::::: XBL bindings ::::: */
+
+menulist > menupopup {
+ -moz-binding: url("chrome://global/content/bindings/popup.xml#popup-scrollbars");
+}
+
+/* ::::: Variables ::::: */
+:root {
+ --arrowpanel-padding: 16px;
+ --arrowpanel-background: linear-gradient(hsla(0,0%,99%,1), hsla(0,0%,99%,.975) 10%, hsla(0,0%,98%,.975));
+ --arrowpanel-color: hsl(0,0%,10%);
+ --arrowpanel-border-color: hsla(210,4%,10%,.05);
+ --arrowpanel-border-radius: 3.5px;
+}
+
+/* ::::: root elements ::::: */
+
+window,
+page,
+dialog,
+wizard,
+prefwindow {
+ -moz-appearance: dialog;
+ background-color: #FFFFFF;
+ color: -moz-DialogText;
+ font: message-box;
+}
+
+prefwindow[type="child"] {
+ padding-top: 18px;
+ padding-bottom: 15px;
+ padding-inline-start: 18px;
+ padding-inline-end: 20px;
+}
+
+/* deprecated */
+window.dialog {
+ padding-top: 8px;
+ padding-bottom: 10px;
+ padding-inline-start: 8px;
+ padding-inline-end: 10px;
+}
+
+/* ::::: alert icons :::::*/
+
+.message-icon,
+.alert-icon,
+.error-icon,
+.question-icon {
+ width: 64px;
+ height: 64px;
+ margin: 6px;
+ margin-inline-end: 20px;
+}
+
+.message-icon {
+ list-style-image: url("chrome://global/skin/icons/information-64.png");
+}
+
+.alert-dialog #info\.icon,
+.alert-icon {
+ list-style-image: url("chrome://global/skin/icons/warning-64.png");
+}
+
+.error-icon {
+ list-style-image: url("chrome://global/skin/icons/error-64.png");
+}
+
+.question-icon {
+ list-style-image: url("chrome://global/skin/icons/question-64.png");
+}
+
+/* ::::: iframe ::::: */
+
+iframe {
+ border: none;
+ width: 100px;
+ height: 100px;
+ min-width: 10px;
+ min-height: 10px;
+}
+
+/* ::::: statusbar ::::: */
+
+statusbar {
+ min-width: 1px; /* DON'T DELETE!
+ Prevents hiding of scrollbars in browser when window is made smaller.*/
+ min-height: 15px !important;
+ margin: 0px !important;
+ /* need to use padding-inline-end when/if bug 631729 gets fixed: */
+ padding: 0px 16px 1px 1px;
+ -moz-appearance: statusbar;
+ text-shadow: rgba(255, 255, 255, 0.4) 0 1px;
+}
+
+statusbarpanel {
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ padding: 0 4px;
+}
+
+.statusbarpanel-iconic {
+ padding: 0px;
+}
+
+/* ::::: miscellaneous formatting ::::: */
+
+:root:-moz-lwtheme,
+[lwthemefooter="true"] {
+ -moz-appearance: none;
+}
+
+:root:-moz-lwtheme-darktext {
+ text-shadow: 0 -0.5px 1.5px white;
+}
+
+:root:-moz-lwtheme-brighttext {
+ text-shadow: 1px 1px 1.5px black;
+}
+
+statusbar:-moz-lwtheme {
+ -moz-appearance: none;
+ background: none;
+ border-style: none;
+ text-shadow: inherit;
+}
+
+.inset {
+ border: 1px solid ThreeDShadow;
+ border-right-color: ThreeDHighlight;
+ border-bottom-color: ThreeDHighlight;
+ margin: 0 5px 5px;
+}
+
+.outset {
+ border: 1px solid ThreeDShadow;
+ border-left-color: ThreeDHighlight;
+ border-top-color: ThreeDHighlight;
+}
+
+separator:not([orient="vertical"]) {
+ height: 1.5em;
+}
+separator[orient="vertical"] {
+ width: 1.5em;
+}
+
+separator.thin:not([orient="vertical"]) {
+ height: 0.5em;
+}
+separator.thin[orient="vertical"] {
+ width: 0.5em;
+}
+
+separator.groove:not([orient="vertical"]) {
+ border-top: 1px solid #A3A3A3;
+ height: 0;
+ margin-top: 0.4em;
+ margin-bottom: 0.4em;
+}
+separator.groove[orient="vertical"] {
+ border-left: 1px solid #A3A3A3;
+ width: 0;
+ margin-left: 0.4em;
+ margin-right: 0.4em;
+}
+
+.plain {
+ -moz-appearance: none;
+ margin: 0 !important;
+ border: none;
+ padding: 0;
+}
+
+description,
+label {
+ cursor: default;
+ margin-top: 1px;
+ margin-bottom: 2px;
+ margin-inline-start: 6px;
+ margin-inline-end: 5px;
+}
+
+description {
+ margin-bottom: 4px;
+}
+
+label[disabled="true"] {
+ color: GrayText;
+}
+
+.tooltip-label {
+ margin: 0;
+}
+
+.header {
+ font-weight: bold;
+}
+
+.monospace {
+ font-family: monospace;
+}
+
+.indent {
+ margin-inline-start: 23px;
+}
+
+.box-padded {
+ padding: 5px;
+}
+
+.spaced {
+ margin: 3px 5px 4px;
+}
+
+.wizard-box {
+ padding: 20px 44px 10px;
+}
+
+.text-link {
+ color: -moz-nativehyperlinktext;
+ cursor: pointer;
+}
+
+.text-link:hover {
+ text-decoration: underline;
+}
+
+.text-link:-moz-focusring {
+ box-shadow: @focusRingShadow@;
+}
+
+.toolbar-focustarget {
+ -moz-user-focus: ignore !important;
+}
+
+notification > button {
+ margin: 0 3px;
+ padding: 1px 10px;
+ min-width: 60px;
+ min-height: 16px;
+ -moz-appearance: none;
+ border-radius: 10000px;
+ border: @roundButtonBorder@;
+ text-shadow: @loweredShadow@;
+ background: @roundButtonBackground@;
+ box-shadow: @roundButtonShadow@;
+}
+
+notification > button:active:hover {
+ background: @roundButtonPressedBackground@;
+ box-shadow: @roundButtonPressedShadow@;
+}
+
+notification > button:-moz-focusring {
+ box-shadow: @focusRingShadow@, @roundButtonShadow@;
+}
+
+notification > button:active:hover:-moz-focusring {
+ box-shadow: @focusRingShadow@, @roundButtonPressedShadow@;
+}
+
+notification > button > .button-box > .button-text {
+ margin: 0 !important;
+}
+
+popupnotificationcontent {
+ margin-top: .5em;
+}
+
+/* :::::: autoscroll popup ::::: */
+
+.autoscroller {
+ height: 28px;
+ width: 28px;
+ border: none;
+ margin: -14px;
+ padding: 0;
+ background-image: url("chrome://global/skin/icons/autoscroll.png");
+ background-color: transparent;
+ background-position: right top;
+ -moz-appearance: none;
+ -moz-window-shadow: none;
+}
+
+.autoscroller[scrolldir="NS"] {
+ background-position: right center;
+}
+
+.autoscroller[scrolldir="EW"] {
+ background-position: right bottom;
+}
+
+/* autorepeatbuttons in menus */
+
+.popup-internal-box > autorepeatbutton {
+ height: 15px;
+ position: relative;
+ list-style-image: none;
+ /* Here we're using a little magic.
+ * The arrow button is supposed to overlay the scrollbox, blocking
+ * everything under it from reaching the screen. However, the menu background
+ * is slightly transparent, so how can we block something completely without
+ * messing up the transparency? It's easy: The native theming of the
+ * "menuitem" appearance uses CGContextClearRect before drawing, which
+ * clears everything under it.
+ * Without help from native theming this effect wouldn't be achievable.
+ */
+ -moz-appearance: menuitem;
+}
+
+.popup-internal-box > .autorepeatbutton-up {
+ padding-top: 1px; /* 4px padding-top from the .popup-internal-box. */
+ margin-bottom: -15px;
+}
+
+.popup-internal-box > .autorepeatbutton-up > .autorepeatbutton-icon {
+ -moz-appearance: button-arrow-up;
+}
+
+.popup-internal-box > .autorepeatbutton-down {
+ padding-top: 5px;
+ margin-top: -15px;
+}
+
+.popup-internal-box > .autorepeatbutton-down > .autorepeatbutton-icon {
+ -moz-appearance: button-arrow-down;
+}
+
+.popup-internal-box > autorepeatbutton[disabled="true"] {
+ visibility: collapse;
+}
+
+/* :::::: Close button icons ::::: */
+
+.close-icon {
+ list-style-image: url("chrome://global/skin/icons/close.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+.close-icon:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+.close-icon:hover:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+@media (min-resolution: 2dppx) {
+ .close-icon > .button-icon,
+ .close-icon > .button-box > .button-icon,
+ .close-icon > .toolbarbutton-icon {
+ width: 16px;
+ }
+
+ .close-icon {
+ list-style-image: url("chrome://global/skin/icons/close@2x.png");
+ -moz-image-region: rect(0, 32px, 32px, 0);
+ }
+
+ .close-icon:hover {
+ -moz-image-region: rect(0, 64px, 32px, 32px);
+ }
+
+ .close-icon:hover:active {
+ -moz-image-region: rect(0, 96px, 32px, 64px);
+ }
+}
diff --git a/toolkit/themes/osx/global/groupbox.css b/toolkit/themes/osx/global/groupbox.css
new file mode 100644
index 0000000000..8406458278
--- /dev/null
+++ b/toolkit/themes/osx/global/groupbox.css
@@ -0,0 +1,30 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+groupbox {
+ padding: 5px 1px 1px;
+ padding-inline-start: 0;
+ margin: 6px;
+}
+
+.groupbox-body {
+ -moz-appearance: groupbox;
+ padding: 8px 8px 3px;
+ margin: 0;
+}
+
+caption {
+ padding-inline-start: 4px;
+ padding-bottom: 1px;
+ font: caption;
+}
+
+/* !important is needed to override label in global.css */
+.caption-text {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+ margin-inline-start: 1px !important;
+}
diff --git a/toolkit/themes/osx/global/icons/Error.png b/toolkit/themes/osx/global/icons/Error.png
new file mode 100644
index 0000000000..424ebfd4ad
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/Error.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/autocomplete-dropmarker.png b/toolkit/themes/osx/global/icons/autocomplete-dropmarker.png
new file mode 100644
index 0000000000..e48d044526
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/autocomplete-dropmarker.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/autocomplete-search.svg b/toolkit/themes/osx/global/icons/autocomplete-search.svg
new file mode 100644
index 0000000000..3d1795d29d
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/autocomplete-search.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ use {
+ fill: GrayText;
+ }
+ use[id$="-inverted"] {
+ fill: highlighttext;
+ }
+ </style>
+ <defs>
+ <path id="search" fill-rule="evenodd" d="M14.517,12.884l-3.279-3.287c0.545-0.851,0.864-1.861,0.864-2.947 c0-3.022-2.444-5.472-5.458-5.472c-3.014,0-5.458,2.45-5.458,5.472c0,3.022,2.444,5.471,5.458,5.471 c1.093,0,2.108-0.325,2.962-0.88l3.275,3.283c0.396,0.397,1.039,0.397,1.435,0l0.202-0.202 C14.913,13.925,14.913,13.281,14.517,12.884z M6.644,10.001c-1.846,0-3.344-1.501-3.344-3.352c0-1.851,1.497-3.352,3.344-3.352 c1.847,0,3.344,1.501,3.344,3.352C9.987,8.501,8.49,10.001,6.644,10.001z"/>
+ </defs>
+ <use id="search-icon" xlink:href="#search"/>
+ <use id="search-icon-inverted" xlink:href="#search"/>
+</svg>
diff --git a/toolkit/themes/osx/global/icons/autoscroll.png b/toolkit/themes/osx/global/icons/autoscroll.png
new file mode 100644
index 0000000000..c21e067d9a
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/autoscroll.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/blacklist_64.png b/toolkit/themes/osx/global/icons/blacklist_64.png
new file mode 100644
index 0000000000..90555a93bf
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/blacklist_64.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/blacklist_favicon.png b/toolkit/themes/osx/global/icons/blacklist_favicon.png
new file mode 100644
index 0000000000..e67d3d34f0
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/blacklist_favicon.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/checkbox.png b/toolkit/themes/osx/global/icons/checkbox.png
new file mode 100644
index 0000000000..137e4e801f
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/checkbox.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/checkbox@2x.png b/toolkit/themes/osx/global/icons/checkbox@2x.png
new file mode 100644
index 0000000000..61bcc59c7d
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/checkbox@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/chevron-inverted.png b/toolkit/themes/osx/global/icons/chevron-inverted.png
new file mode 100644
index 0000000000..8ad164baaf
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/chevron-inverted.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/chevron-inverted@2x.png b/toolkit/themes/osx/global/icons/chevron-inverted@2x.png
new file mode 100644
index 0000000000..4327a1a457
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/chevron-inverted@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/chevron.png b/toolkit/themes/osx/global/icons/chevron.png
new file mode 100644
index 0000000000..b2d31e38f5
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/chevron.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/chevron@2x.png b/toolkit/themes/osx/global/icons/chevron@2x.png
new file mode 100644
index 0000000000..dd91178030
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/chevron@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/close.png b/toolkit/themes/osx/global/icons/close.png
new file mode 100644
index 0000000000..9bba044ce5
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/close.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/close@2x.png b/toolkit/themes/osx/global/icons/close@2x.png
new file mode 100755
index 0000000000..01c5ef4232
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/close@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/error-16.png b/toolkit/themes/osx/global/icons/error-16.png
new file mode 100644
index 0000000000..41514d0806
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/error-16.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/error-64.png b/toolkit/themes/osx/global/icons/error-64.png
new file mode 100644
index 0000000000..972abaff3b
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/error-64.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/error-large.png b/toolkit/themes/osx/global/icons/error-large.png
new file mode 100644
index 0000000000..5a1479e28b
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/error-large.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/glyph-dropdown.png b/toolkit/themes/osx/global/icons/glyph-dropdown.png
new file mode 100644
index 0000000000..fa08515836
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/glyph-dropdown.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/glyph-dropdown@2x.png b/toolkit/themes/osx/global/icons/glyph-dropdown@2x.png
new file mode 100644
index 0000000000..653039a3e6
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/glyph-dropdown@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/information-16.png b/toolkit/themes/osx/global/icons/information-16.png
new file mode 100644
index 0000000000..6fb2e3a804
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/information-16.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/information-24.png b/toolkit/themes/osx/global/icons/information-24.png
new file mode 100644
index 0000000000..6907d02e5f
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/information-24.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/information-32.png b/toolkit/themes/osx/global/icons/information-32.png
new file mode 100644
index 0000000000..4501bc813a
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/information-32.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/information-64.png b/toolkit/themes/osx/global/icons/information-64.png
new file mode 100644
index 0000000000..8d9b72498e
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/information-64.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/information-large.png b/toolkit/themes/osx/global/icons/information-large.png
new file mode 100644
index 0000000000..3912f1c79a
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/information-large.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/loading_16.png b/toolkit/themes/osx/global/icons/loading_16.png
new file mode 100644
index 0000000000..1b2df8093d
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/loading_16.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/menulist-dropmarker.png b/toolkit/themes/osx/global/icons/menulist-dropmarker.png
new file mode 100644
index 0000000000..689a1fe877
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/menulist-dropmarker.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/notfound.png b/toolkit/themes/osx/global/icons/notfound.png
new file mode 100644
index 0000000000..694dae910e
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/notfound.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/notloading_16.png b/toolkit/themes/osx/global/icons/notloading_16.png
new file mode 100644
index 0000000000..ece0ee18a1
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/notloading_16.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/panebutton-active.png b/toolkit/themes/osx/global/icons/panebutton-active.png
new file mode 100644
index 0000000000..ca241c7b8a
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/panebutton-active.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/panebutton-inactive.png b/toolkit/themes/osx/global/icons/panebutton-inactive.png
new file mode 100644
index 0000000000..de527b6627
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/panebutton-inactive.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/panel-dropmarker.png b/toolkit/themes/osx/global/icons/panel-dropmarker.png
new file mode 100644
index 0000000000..e605e835c7
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/panel-dropmarker.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/question-16.png b/toolkit/themes/osx/global/icons/question-16.png
new file mode 100644
index 0000000000..8d8311fced
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/question-16.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/question-32.png b/toolkit/themes/osx/global/icons/question-32.png
new file mode 100644
index 0000000000..7c80831b04
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/question-32.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/question-64.png b/toolkit/themes/osx/global/icons/question-64.png
new file mode 100644
index 0000000000..96fd746409
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/question-64.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/question-large.png b/toolkit/themes/osx/global/icons/question-large.png
new file mode 100644
index 0000000000..dd2b21874b
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/question-large.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/resizer-rtl.png b/toolkit/themes/osx/global/icons/resizer-rtl.png
new file mode 100644
index 0000000000..6ab7d33457
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/resizer-rtl.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/resizer-rtl@2x.png b/toolkit/themes/osx/global/icons/resizer-rtl@2x.png
new file mode 100644
index 0000000000..6c85d4f332
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/resizer-rtl@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/resizer.png b/toolkit/themes/osx/global/icons/resizer.png
new file mode 100644
index 0000000000..efed0240b8
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/resizer.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/resizer@2x.png b/toolkit/themes/osx/global/icons/resizer@2x.png
new file mode 100644
index 0000000000..2304cc65fe
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/resizer@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/search-textbox.svg b/toolkit/themes/osx/global/icons/search-textbox.svg
new file mode 100644
index 0000000000..12be833c40
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/search-textbox.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <style>
+ path {
+ fill: #4c4c4c;
+ fill-rule: evenodd;
+ }
+ </style>
+ <path id="glyph-search" d="M11.354,10.646l-0.707.707L7.295,8A4.483,4.483,0,1,1,9,4.5,4.458,4.458,0,0,1,8,7.295ZM4.5,1A3.5,3.5,0,1,0,8,4.5,3.5,3.5,0,0,0,4.5,1Z"/>
+</svg>
diff --git a/toolkit/themes/osx/global/icons/searchfield-cancel.svg b/toolkit/themes/osx/global/icons/searchfield-cancel.svg
new file mode 100644
index 0000000000..9899144e95
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/searchfield-cancel.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 14 14">
+ <style>
+ circle {
+ fill: #808080;
+ }
+
+ line {
+ stroke: #fff;
+ stroke-width: 1.5px;
+ }
+ </style>
+
+ <circle cx="7" cy="7" r="7" />
+ <line x1="4" y1="4" x2="10" y2="10" />
+ <line x1="10" y1="4" x2="4" y2="10" />
+</svg> \ No newline at end of file
diff --git a/toolkit/themes/osx/global/icons/sslWarning.png b/toolkit/themes/osx/global/icons/sslWarning.png
new file mode 100644
index 0000000000..e8ad586b6e
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/sslWarning.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/tabprompts-bgtexture.png b/toolkit/themes/osx/global/icons/tabprompts-bgtexture.png
new file mode 100644
index 0000000000..caffc241cf
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/tabprompts-bgtexture.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/warning-16.png b/toolkit/themes/osx/global/icons/warning-16.png
new file mode 100644
index 0000000000..2ab4b3915e
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/warning-16.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/warning-32.png b/toolkit/themes/osx/global/icons/warning-32.png
new file mode 100644
index 0000000000..750abaa220
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/warning-32.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/warning-64.png b/toolkit/themes/osx/global/icons/warning-64.png
new file mode 100644
index 0000000000..37d2120538
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/warning-64.png
Binary files differ
diff --git a/toolkit/themes/osx/global/icons/warning-large.png b/toolkit/themes/osx/global/icons/warning-large.png
new file mode 100644
index 0000000000..73fd65f6fa
--- /dev/null
+++ b/toolkit/themes/osx/global/icons/warning-large.png
Binary files differ
diff --git a/toolkit/themes/osx/global/in-content/common.css b/toolkit/themes/osx/global/in-content/common.css
new file mode 100644
index 0000000000..a987cbfe1f
--- /dev/null
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -0,0 +1,121 @@
+/* - 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 ../shared.inc
+%include ../../../shared/in-content/common.inc.css
+
+xul|tabs {
+ padding-right: 0;
+ padding-left: 0;
+}
+
+xul|tab[visuallyselected] {
+ text-shadow: none;
+}
+
+xul|button,
+html|button,
+xul|colorpicker[type="button"],
+xul|menulist {
+ margin-top: 3px;
+}
+
+xul|button,
+html|button {
+ /* use the same margin of other elements for the alignment */
+ margin-left: 4px;
+ margin-right: 4px;
+}
+
+xul|caption {
+ padding-inline-start: 0;
+}
+
+xul|groupbox > xul|*.groupbox-body {
+ padding: 0;
+}
+
+xul|menulist:not([editable="true"]) > xul|menupopup > xul|menuitem[checked="true"]::before,
+xul|menulist:not([editable="true"]) > xul|menupopup > xul|menuitem[selected="true"]::before {
+ display: none;
+}
+
+xul|menulist:not([editable="true"]) > xul|*.menulist-dropmarker {
+ display: -moz-box;
+ margin-top: 1px;
+ margin-bottom: 1px;
+}
+
+xul|menulist > xul|menupopup xul|menu,
+xul|menulist > xul|menupopup xul|menuitem,
+xul|button[type="menu"] > xul|menupopup xul|menu,
+xul|button[type="menu"] > xul|menupopup xul|menuitem {
+ padding-inline-end: 34px;
+}
+
+xul|*.help-button > xul|*.button-box > xul|*.button-icon {
+ margin-inline-start: 0;
+}
+
+xul|*.checkbox-icon {
+ margin-right: 0;
+}
+
+xul|*.radio-icon {
+ margin-inline-end: 0;
+}
+
+xul|*.numberbox-input-box {
+ -moz-appearance: none;
+ border-width: 0;
+}
+
+xul|description {
+ font-size: 1.25rem;
+ line-height: 22px;
+}
+
+xul|*.text-link:-moz-focusring {
+ color: var(--in-content-link-highlight);
+ text-decoration: underline;
+ box-shadow: none;
+}
+
+xul|button:-moz-focusring,
+xul|menulist:-moz-focusring,
+xul|checkbox:-moz-focusring > .checkbox-check,
+html|input[type="checkbox"]:-moz-focusring + html|label:before,
+xul|radio[focused="true"] > .radio-check,
+xul|tab:-moz-focusring > .tab-middle > .tab-text {
+ outline: 2px solid rgba(0,149,221,0.5);
+ outline-offset: 1px;
+ -moz-outline-radius: 2px;
+}
+
+xul|radio[focused="true"] > .radio-check {
+ -moz-outline-radius: 100%;
+}
+
+xul|spinbuttons {
+ -moz-appearance: none;
+}
+
+xul|*.spinbuttons-up {
+ margin-top: 0 !important;
+ border-radius: 4px 4px 0 0;
+}
+
+xul|*.spinbuttons-down {
+ margin-bottom: 0 !important;
+ border-radius: 0 0 4px 4px;
+}
+
+xul|*.spinbuttons-button > xul|*.button-box {
+ padding-inline-start: 2px !important;
+ padding-inline-end: 3px !important;
+}
+
+xul|*.spinbuttons-button > xul|*.button-box > xul|*.button-text {
+ display: none;
+}
diff --git a/toolkit/themes/osx/global/in-content/info-pages.css b/toolkit/themes/osx/global/in-content/info-pages.css
new file mode 100644
index 0000000000..a25b9f6a3d
--- /dev/null
+++ b/toolkit/themes/osx/global/in-content/info-pages.css
@@ -0,0 +1 @@
+%include ../../../shared/in-content/info-pages.inc.css \ No newline at end of file
diff --git a/toolkit/themes/osx/global/inContentUI.css b/toolkit/themes/osx/global/inContentUI.css
new file mode 100644
index 0000000000..17e2e6ae33
--- /dev/null
+++ b/toolkit/themes/osx/global/inContentUI.css
@@ -0,0 +1,144 @@
+/* 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 shared.inc
+
+/*
+ * The default namespace for this file is XUL. Be sure to prefix rules that
+ * are applicable to both XUL and HTML with '*|'.
+ */
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/* Page background */
+*|*:root {
+ -moz-appearance: none;
+ padding: 18px;
+ background-image: /* Texture */
+ url("chrome://global/skin/inContentUI/background-texture.png"),
+ /* Gradient */
+ linear-gradient(#ADB5C2, #BFC6D1);
+}
+
+/* Use the new in-content colors for #contentAreaDownloadsView. After landing
+ of bug 989469 the colors can be moved to *|*:root */
+*|*#contentAreaDownloadsView {
+ background: #f1f1f1;
+ color: #424e5a;
+}
+
+html|html {
+ font: message-box;
+}
+
+/* Content */
+*|*.main-content {
+ /* Needed to allow the radius to clip the inner content, see bug 595656 */
+ overflow: hidden;
+ background-image: linear-gradient(rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.25) 50%, rgba(255, 255, 255, 0.05));
+ border: 1px solid rgba(50, 65, 92, 0.4);
+ border-radius: 5px;
+}
+
+/* Buttons */
+*|button,
+menulist,
+colorpicker[type="button"] {
+ -moz-appearance: none;
+ padding: 1px 4px;
+ min-width: 60px;
+ border-radius: 3px;
+ border: 1px solid rgba(60,73,97,0.5);
+ box-shadow: inset 0 1px rgba(255,255,255,0.25), 0 1px rgba(255,255,255,0.25);
+ background-color: transparent;
+ background-image: linear-gradient(rgba(255,255,255,0.45), rgba(255,255,255,0.2));
+ background-clip: padding-box;
+ color: #252F3B;
+ text-shadow: @loweredShadow@;
+}
+
+button:-moz-focusring > .button-box,
+menulist:-moz-focusring:not([open="true"]) > .menulist-label-box,
+colorpicker[type="button"]:-moz-focusring:not([open="true"]) > .colorpicker-button-colorbox {
+ outline: 1px dotted #252F3B;
+}
+
+html|button[disabled],
+button[disabled="true"],
+menulist[disabled="true"],
+colorpicker[type="button"][disabled="true"] {
+ opacity: 0.8;
+ color: #505050;
+}
+
+html|button:not([disabled]):active:hover,
+button:not([disabled="true"]):active:hover,
+menulist[open="true"]:not([disabled="true"]),
+colorpicker[type="button"][open="true"]:not([disabled="true"]) {
+ box-shadow: inset 0 1px 3px rgba(0,0,0,.2), 0 1px rgba(255,255,255,0.25);
+ background-image: linear-gradient(rgba(45,54,71,0.3), rgba(45,54,71,0.1));
+ border-color: rgba(60,73,97,0.7);
+}
+
+menulist {
+ -moz-padding-end: 0;
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+/* Tweak margins so the focus ring is in the right place. */
+menulist > .menulist-label-box {
+ -moz-margin-end: 3px;
+ margin-top: 1px;
+}
+
+menulist > .menulist-label-box > .menulist-label {
+ margin-top: 0px !important;
+ margin-bottom: 0px !important;
+}
+
+menulist > .menulist-dropmarker {
+ -moz-appearance: none;
+ display: -moz-box;
+ background: transparent;
+ border: none;
+ -moz-border-start: 1px solid rgba(60,73,97,0.5);
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+colorpicker[type="button"] {
+ margin: 1px 5px 2px 5px;
+ padding: 3px;
+ height: 25px;
+}
+
+spinbuttons {
+ -moz-appearance: none;
+}
+
+spinbuttons > .spinbuttons-box > .spinbuttons-button {
+ min-width: 12px;
+}
+
+.spinbuttons-button > .button-box > .button-text {
+ display: none;
+}
+
+.spinbuttons-button[disabled="true"] > .button-box > .button-icon {
+ opacity: 0.5;
+}
+
+spinbuttons > .spinbuttons-box > .spinbuttons-up {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+ border-bottom-width: 0;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+spinbuttons > .spinbuttons-box > .spinbuttons-down {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
diff --git a/toolkit/themes/osx/global/jar.mn b/toolkit/themes/osx/global/jar.mn
new file mode 100644
index 0000000000..9ca73cc6bc
--- /dev/null
+++ b/toolkit/themes/osx/global/jar.mn
@@ -0,0 +1,156 @@
+# 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 ../../shared/jar.inc.mn
+
+toolkit.jar:
+ skin/classic/global/10pct_transparent_grey.png
+ skin/classic/global/50pct_transparent_grey.png
+ skin/classic/global/arrow.css
+ skin/classic/global/autocomplete.css
+ skin/classic/global/button.css
+ skin/classic/global/checkbox.css
+ skin/classic/global/colorpicker.css
+ skin/classic/global/commonDialog.css
+ skin/classic/global/customizeToolbar.css
+ skin/classic/global/dialog.css
+ skin/classic/global/dropmarker.css
+ skin/classic/global/filefield.css
+ skin/classic/global/filters.svg
+* skin/classic/global/findBar.css
+* skin/classic/global/global.css
+ skin/classic/global/groupbox.css
+* skin/classic/global/inContentUI.css
+ skin/classic/global/linkTree.css
+ skin/classic/global/listbox.css
+ skin/classic/global/menu.css
+ skin/classic/global/menulist.css
+* skin/classic/global/notification.css
+ skin/classic/global/netError.css
+ skin/classic/global/numberbox.css
+ skin/classic/global/popup.css
+ skin/classic/global/preferences.css
+ skin/classic/global/progressmeter.css
+ skin/classic/global/radio.css
+ skin/classic/global/resizer.css
+ skin/classic/global/richlistbox.css
+ skin/classic/global/scrollbars.css (nativescrollbars.css)
+ skin/classic/global/scale.css
+ skin/classic/global/scrollbox.css
+ skin/classic/global/spinbuttons.css
+ skin/classic/global/splitter.css
+ skin/classic/global/tabprompts.css
+ skin/classic/global/tabbox.css
+ skin/classic/global/textbox.css
+ skin/classic/global/datetimepicker.css
+* skin/classic/global/toolbar.css
+ skin/classic/global/toolbarbutton.css
+* skin/classic/global/tree.css
+* skin/classic/global/viewbuttons.css
+ skin/classic/global/wizard.css
+ skin/classic/global/alerts/alert.css (alerts/alert.css)
+ skin/classic/global/arrow/arrow-dn-dis.gif (arrow/arrow-dn-dis.gif)
+ skin/classic/global/arrow/arrow-dn-dis.png (arrow/arrow-dn-dis.png)
+ skin/classic/global/arrow/arrow-dn-sharp.gif (arrow/arrow-dn-sharp.gif)
+ skin/classic/global/arrow/arrow-dn.gif (arrow/arrow-dn.gif)
+ skin/classic/global/arrow/arrow-dn.png (arrow/arrow-dn.png)
+ skin/classic/global/arrow/arrow-lft-dis.gif (arrow/arrow-lft-dis.gif)
+ skin/classic/global/arrow/arrow-lft-hov.gif (arrow/arrow-lft-hov.gif)
+ skin/classic/global/arrow/arrow-lft-sharp-end.gif (arrow/arrow-lft-sharp-end.gif)
+ skin/classic/global/arrow/arrow-lft-sharp.gif (arrow/arrow-lft-sharp.gif)
+ skin/classic/global/arrow/arrow-lft.gif (arrow/arrow-lft.gif)
+ skin/classic/global/arrow/arrow-rit-dis.gif (arrow/arrow-rit-dis.gif)
+ skin/classic/global/arrow/arrow-rit-hov.gif (arrow/arrow-rit-hov.gif)
+ skin/classic/global/arrow/arrow-rit-sharp-end.gif (arrow/arrow-rit-sharp-end.gif)
+ skin/classic/global/arrow/arrow-rit-sharp.gif (arrow/arrow-rit-sharp.gif)
+ skin/classic/global/arrow/arrow-rit.gif (arrow/arrow-rit.gif)
+ skin/classic/global/arrow/arrow-up-dis.gif (arrow/arrow-up-dis.gif)
+ skin/classic/global/arrow/arrow-up-sharp.gif (arrow/arrow-up-sharp.gif)
+ skin/classic/global/arrow/arrow-up.gif (arrow/arrow-up.gif)
+ skin/classic/global/arrow/panelarrow-horizontal.png (arrow/panelarrow-horizontal.png)
+ skin/classic/global/arrow/panelarrow-horizontal@2x.png (arrow/panelarrow-horizontal@2x.png)
+ skin/classic/global/arrow/panelarrow-vertical.png (arrow/panelarrow-vertical.png)
+ skin/classic/global/arrow/panelarrow-vertical@2x.png (arrow/panelarrow-vertical@2x.png)
+ skin/classic/global/checkbox/cbox-check.gif (checkbox/cbox-check.gif)
+ skin/classic/global/checkbox/cbox-check-dis.gif (checkbox/cbox-check-dis.gif)
+ skin/classic/global/console/console-error-caret.gif (console/console-error-caret.gif)
+ skin/classic/global/console/console-error-dash.gif (console/console-error-dash.gif)
+* skin/classic/global/console/console.css (console/console.css)
+ skin/classic/global/dirListing/dirListing.css (dirListing/dirListing.css)
+ skin/classic/global/dirListing/folder.png (dirListing/folder.png)
+ skin/classic/global/dirListing/remote.png (dirListing/remote.png)
+ skin/classic/global/dirListing/up.png (dirListing/up.png)
+ skin/classic/global/icons/autocomplete-dropmarker.png (icons/autocomplete-dropmarker.png)
+ skin/classic/global/icons/autocomplete-search.svg (icons/autocomplete-search.svg)
+ skin/classic/global/icons/autoscroll.png (icons/autoscroll.png)
+ skin/classic/global/icons/blacklist_favicon.png (icons/blacklist_favicon.png)
+ skin/classic/global/icons/blacklist_64.png (icons/blacklist_64.png)
+ skin/classic/global/icons/chevron.png (icons/chevron.png)
+ skin/classic/global/icons/chevron-inverted.png (icons/chevron-inverted.png)
+ skin/classic/global/icons/chevron@2x.png (icons/chevron@2x.png)
+ skin/classic/global/icons/chevron-inverted@2x.png (icons/chevron-inverted@2x.png)
+ skin/classic/global/icons/checkbox.png (icons/checkbox.png)
+ skin/classic/global/icons/checkbox@2x.png (icons/checkbox@2x.png)
+ skin/classic/global/icons/close.png (icons/close.png)
+ skin/classic/global/icons/close@2x.png (icons/close@2x.png)
+ skin/classic/global/icons/glyph-dropdown.png (icons/glyph-dropdown.png)
+ skin/classic/global/icons/glyph-dropdown@2x.png (icons/glyph-dropdown@2x.png)
+ skin/classic/global/icons/information-16.png (icons/information-16.png)
+ skin/classic/global/icons/information-24.png (icons/information-24.png)
+ skin/classic/global/icons/information-32.png (icons/information-32.png)
+ skin/classic/global/icons/information-64.png (icons/information-64.png)
+ skin/classic/global/icons/information-large.png (icons/information-large.png)
+ skin/classic/global/icons/loading_16.png (icons/loading_16.png)
+ skin/classic/global/icons/menulist-dropmarker.png (icons/menulist-dropmarker.png)
+ skin/classic/global/icons/notfound.png (icons/notfound.png)
+ skin/classic/global/icons/notloading_16.png (icons/notloading_16.png)
+ skin/classic/global/icons/panebutton-active.png (icons/panebutton-active.png)
+ skin/classic/global/icons/panebutton-inactive.png (icons/panebutton-inactive.png)
+ skin/classic/global/icons/panel-dropmarker.png (icons/panel-dropmarker.png)
+ skin/classic/global/icons/resizer.png (icons/resizer.png)
+ skin/classic/global/icons/resizer@2x.png (icons/resizer@2x.png)
+ skin/classic/global/icons/resizer-rtl.png (icons/resizer-rtl.png)
+ skin/classic/global/icons/resizer-rtl@2x.png (icons/resizer-rtl@2x.png)
+ skin/classic/global/icons/search-textbox.svg (icons/search-textbox.svg)
+ skin/classic/global/icons/searchfield-cancel.svg (icons/searchfield-cancel.svg)
+ skin/classic/global/icons/tabprompts-bgtexture.png (icons/tabprompts-bgtexture.png)
+ skin/classic/global/icons/warning-16.png (icons/warning-16.png)
+ skin/classic/global/icons/warning-32.png (icons/warning-32.png)
+ skin/classic/global/icons/warning-64.png (icons/warning-64.png)
+ skin/classic/global/icons/warning-large.png (icons/warning-large.png)
+ skin/classic/global/icons/error-16.png (icons/error-16.png)
+ skin/classic/global/icons/error-64.png (icons/error-64.png)
+ skin/classic/global/icons/error-large.png (icons/error-large.png)
+ skin/classic/global/icons/Error.png (icons/Error.png)
+ skin/classic/global/icons/question-16.png (icons/question-16.png)
+ skin/classic/global/icons/question-32.png (icons/question-32.png)
+ skin/classic/global/icons/question-64.png (icons/question-64.png)
+ skin/classic/global/icons/question-large.png (icons/question-large.png)
+ skin/classic/global/icons/sslWarning.png (icons/sslWarning.png)
+ skin/classic/global/notification/close.png (notification/close.png)
+ skin/classic/global/notification/error-icon.png (notification/error-icon.png)
+ skin/classic/global/notification/info-icon.png (notification/info-icon.png)
+ skin/classic/global/notification/warning-icon.png (notification/warning-icon.png)
+* skin/classic/global/in-content/common.css (in-content/common.css)
+* skin/classic/global/in-content/info-pages.css (in-content/info-pages.css)
+ skin/classic/global/scale/scale-tray-horiz.gif (scale/scale-tray-horiz.gif)
+ skin/classic/global/scale/scale-tray-vert.gif (scale/scale-tray-vert.gif)
+ skin/classic/global/splitter/dimple.png (splitter/dimple.png)
+ skin/classic/global/splitter/grip-bottom.gif (splitter/grip-bottom.gif)
+ skin/classic/global/splitter/grip-top.gif (splitter/grip-top.gif)
+ skin/classic/global/splitter/grip-left.gif (splitter/grip-left.gif)
+ skin/classic/global/splitter/grip-right.gif (splitter/grip-right.gif)
+ skin/classic/global/toolbar/spring.png (toolbar/spring.png)
+ skin/classic/global/toolbar/toolbar-separator.png (toolbar/toolbar-separator.png)
+ skin/classic/global/tree/arrow-disclosure.svg (tree/arrow-disclosure.svg)
+ skin/classic/global/tree/columnpicker.gif (tree/columnpicker.gif)
+ skin/classic/global/tree/folder.png (tree/folder.png)
+ skin/classic/global/tree/folder@2x.png (tree/folder@2x.png)
+
+#ifdef MOZ_PHOENIX
+[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
+#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
+#endif
+% override chrome://global/skin/dirListing/local.png chrome://global/skin/dirListing/folder.png
diff --git a/toolkit/themes/osx/global/linkTree.css b/toolkit/themes/osx/global/linkTree.css
new file mode 100644
index 0000000000..d83c5bfd9d
--- /dev/null
+++ b/toolkit/themes/osx/global/linkTree.css
@@ -0,0 +1,32 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/**
+ * All the properties in this rule are important to avoid having to create
+ * a special type of tree. This stylesheet can be loaded into a document with
+ * a single tree that is a link tree. Hardly elegant but it's efficient.
+ */
+treeitem[selected="true"] > treerow
+ {
+ background : transparent !important;
+ border : none !important;
+ color : -moz-FieldText !important;
+ }
+
+treecell:hover
+ {
+ text-decoration : underline !important;
+ color : #000080 !important;
+ cursor : pointer;
+ }
+
+treecell:hover:active
+ {
+ text-decoration : underline !important;
+ color : red !important;
+ }
+
+
diff --git a/toolkit/themes/osx/global/listbox.css b/toolkit/themes/osx/global/listbox.css
new file mode 100644
index 0000000000..90928c7699
--- /dev/null
+++ b/toolkit/themes/osx/global/listbox.css
@@ -0,0 +1,113 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+listbox {
+ -moz-appearance: listbox;
+ margin: 2px 4px;
+ background-color: #FFFFFF;
+ color: -moz-FieldText;
+}
+
+.listcell-label {
+ margin: 0px !important;
+ padding-bottom: 1px;
+ padding-inline-start: 4px;
+ white-space: nowrap;
+}
+
+/* ::::: listitem ::::: */
+
+listitem {
+ border: 1px solid transparent;
+}
+
+listitem[selected="true"] {
+ background-color: -moz-mac-secondaryhighlight;
+ color: -moz-DialogText;
+}
+
+listbox:focus > listitem[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+/* ::::: listheader ::::: */
+
+listheader {
+ -moz-appearance: treeheadercell;
+ -moz-box-align: center;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+ padding: 0 4px;
+}
+
+listheader[sortable="true"]:hover:active {
+ border-top: 2px solid;
+ border-right: 1px solid;
+ border-bottom: 1px solid;
+ border-left: 2px solid;
+ -moz-border-top-colors: ThreeDShadow -moz-Dialog;
+ -moz-border-right-colors: ThreeDShadow;
+ -moz-border-bottom-colors: ThreeDShadow;
+ -moz-border-left-colors: ThreeDShadow -moz-Dialog;
+ padding-top: 1px;
+ padding-inline-start: 5px;
+ padding-inline-end: 4px;
+}
+
+.listheader-icon {
+ margin-inline-end: 2px;
+}
+
+.listheader-label {
+ margin: 0px !important;
+}
+
+/* ::::: listcell ::::: */
+
+.listcell-label {
+ margin: 0px !important;
+ padding-bottom: 1px;
+ padding-inline-start: 4px;
+ white-space: nowrap;
+}
+
+.listcell-icon {
+ margin-inline-end: 2px;
+}
+
+.listcell-label[disabled="true"] {
+ color: GrayText;
+}
+
+/* ::::: listcell checkbox ::::: */
+
+.listcell-check {
+ -moz-appearance: checkbox;
+ -moz-box-align: center;
+ margin: 0px 2px;
+ border: 1px solid -moz-DialogText;
+ min-width: 13px;
+ min-height: 13px;
+ background: -moz-Field no-repeat 50% 50%;
+}
+
+.listcell-check[checked="true"] {
+ background-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+
+.listcell-check[disabled="true"] {
+ border-color: GrayText;
+}
+
+.listcell-check[disabled="true"][checked="true"] {
+ background-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
diff --git a/toolkit/themes/osx/global/menu.css b/toolkit/themes/osx/global/menu.css
new file mode 100644
index 0000000000..49ca168a64
--- /dev/null
+++ b/toolkit/themes/osx/global/menu.css
@@ -0,0 +1,187 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+menu,
+menuitem,
+menucaption {
+ -moz-appearance: menuitem;
+ -moz-box-align: center;
+ color: MenuText;
+ font: -moz-pull-down-menu;
+ list-style-image: none;
+ -moz-image-region: auto;
+ padding: 0 21px 2px;
+}
+
+menu[disabled="true"], menuitem[disabled="true"],
+menu[_moz-menuactive="true"][disabled="true"],
+menuitem[_moz-menuactive="true"][disabled="true"] {
+ color: -moz-mac-menutextdisable;
+}
+
+/* ..... internal content .... */
+
+.menu-text,
+.menu-iconic-text,
+.menu-accel,
+.menu-iconic-accel {
+ margin: 0 !important;
+}
+
+.menu-text,
+.menu-iconic-text {
+ font-weight: inherit;
+ color: inherit;
+}
+
+menucaption > .menu-text,
+menucaption > .menu-iconic-text {
+ font-weight: bold;
+}
+
+.menu-description {
+ font-style: italic;
+ color: -moz-mac-menutextdisable;
+ margin-inline-start: 1ex !important;
+}
+
+.menu-iconic-icon {
+ height: 16px;
+ margin-top: -2px;
+ margin-bottom: -2px;
+ margin-inline-end: 5px;
+ /* Empty icons shouldn't take up room, so we need to compensate
+ * the 5px margin-end with a negative margin-start.
+ */
+ margin-inline-start: -5px;
+}
+
+/* menuitems with icons */
+.menuitem-iconic,
+.menu-iconic,
+menuitem[image],
+menuitem[src] {
+ /* 2px higher than those without icons */
+ padding-top: 1px;
+ padding-bottom: 3px;
+}
+
+.menuitem-iconic > .menu-iconic-left > .menu-iconic-icon,
+.menu-iconic > .menu-iconic-left > .menu-iconic-icon,
+menuitem[image] > .menu-iconic-left > .menu-iconic-icon,
+menuitem[src] > .menu-iconic-left > .menu-iconic-icon {
+ margin-inline-start: 0;
+ width: 16px;
+}
+
+/* ..... menu arrow box ..... */
+
+.menu-right,
+.menu-accel-container {
+ margin-inline-start: 21px;
+ margin-inline-end: -9px;
+ -moz-box-pack: end;
+}
+
+.menu-right {
+ list-style-image: none;
+ -moz-appearance: menuarrow;
+}
+
+/* ::::: menu/menuitems in menubar ::::: */
+
+menubar > menu {
+ -moz-appearance: none;
+ padding: 2px 5px 2px 7px;
+ margin: 1px 0;
+}
+
+menubar > menu[_moz-menuactive="true"] {
+ color: inherit;
+ background-color: transparent;
+}
+
+menubar > menu[_moz-menuactive="true"][open="true"] {
+ -moz-appearance: menuitem;
+ color: -moz-mac-menutextselect;
+}
+
+/* ..... internal content .... */
+
+.menubar-left {
+ margin: 0 2px;
+ color: inherit;
+}
+
+.menubar-text {
+ margin: 0 1px !important;
+ color: inherit;
+}
+
+/* ::::: menu/menuitems in popups ::::: */
+
+menupopup > menu,
+menupopup > menuitem,
+menupopup > menucaption {
+ max-width: 42em;
+}
+
+menu[_moz-menuactive="true"],
+menuitem[_moz-menuactive="true"] {
+ color: -moz-mac-menutextselect;
+ background-color: Highlight;
+}
+
+/* ::::: menu/menuitems in menulist popups ::::: */
+
+menulist > menupopup > menuitem,
+menulist > menupopup > menucaption,
+menulist > menupopup > menu {
+ max-width: none;
+ font: inherit;
+ color: -moz-FieldText;
+}
+
+/* ::::: menuitems in editable menulist popups ::::: */
+
+menulist[editable="true"] > menupopup > menuitem,
+menulist[editable="true"] > menupopup > menucaption {
+ -moz-appearance: none;
+}
+
+menulist[editable="true"] > menupopup > :-moz-any(menuitem, menucaption) > .menu-iconic-left {
+ display: none;
+}
+
+/* ::::: checked menuitems ::::: */
+
+:not(menulist) > menupopup > menuitem[checked="true"],
+:not(menulist) > menupopup > menuitem[selected="true"] {
+ -moz-appearance: checkmenuitem;
+}
+
+menulist:not([editable="true"]) > menupopup > menuitem[checked="true"]::before,
+menulist:not([editable="true"]) > menupopup > menuitem[selected="true"]::before {
+ content: '\2713'; /* a checkmark */
+ display: block;
+ width: 15px;
+ margin-inline-start: -15px;
+}
+
+/* ::::: menuseparator ::::: */
+
+menuseparator {
+ -moz-appearance: menuseparator;
+ margin: 5px 0;
+ padding: 1px 0;
+}
+
+/* ::::: autocomplete ::::: */
+
+.autocomplete-history-popup > menuitem {
+ max-width: none !important;
+ font: message-box;
+}
diff --git a/toolkit/themes/osx/global/menulist.css b/toolkit/themes/osx/global/menulist.css
new file mode 100644
index 0000000000..a4feca947e
--- /dev/null
+++ b/toolkit/themes/osx/global/menulist.css
@@ -0,0 +1,65 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+menulist {
+ -moz-appearance: menulist;
+ margin: 5px 2px 3px;
+ color: -moz-DialogText;
+ text-shadow: none;
+}
+
+menulist:not([popuponly="true"]) {
+ min-height: 20px;
+}
+
+.menulist-label-box {
+ -moz-appearance: menulist-text;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ margin-bottom: 1px;
+}
+
+.menulist-label {
+ margin: 1px 3px !important;
+}
+
+.menulist-description {
+ font-style: italic;
+ color: GrayText;
+ margin-inline-start: 1ex !important;
+}
+
+/* ..... dropmarker ..... */
+
+.menulist-dropmarker {
+ display: none;
+}
+
+/* ..... disabled state ..... */
+
+menulist[disabled="true"] {
+ color: GrayText;
+}
+
+menulist[disabled="true"] > .menulist-dropmarker {
+ padding-inline-start: 7px !important;
+}
+
+/* ::::: editable menulists ::::: */
+
+menulist[editable="true"] {
+ -moz-appearance: menulist-textfield;
+ margin: 4px 2px;
+}
+
+html|*.menulist-editable-input {
+ margin: 0px !important;
+ border: none !important;
+ padding: 0px !important;
+ background: inherit;
+ font: inherit;
+}
diff --git a/toolkit/themes/osx/global/moz.build b/toolkit/themes/osx/global/moz.build
new file mode 100644
index 0000000000..635fa39c99
--- /dev/null
+++ b/toolkit/themes/osx/global/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/toolkit/themes/osx/global/nativescrollbars.css b/toolkit/themes/osx/global/nativescrollbars.css
new file mode 100644
index 0000000000..82ef4d2ac1
--- /dev/null
+++ b/toolkit/themes/osx/global/nativescrollbars.css
@@ -0,0 +1,89 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+scrollbar {
+ -moz-appearance: scrollbar;
+ -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#scrollbar);
+ cursor: default;
+ background-color: white;
+}
+
+scrollbar[root="true"] {
+ position: relative;
+ z-index: 2147483647; /* largest positive value of a signed 32-bit integer */
+}
+
+html|select[size]:not([size="0"]):not([size="1"]) > scrollbar,
+html|select[multiple] > scrollbar {
+ -moz-appearance: scrollbar-small;
+}
+
+@media all and (-moz-overlay-scrollbars) {
+ scrollbar:not([active="true"]),
+ scrollbar[disabled="true"] {
+ visibility: hidden;
+ }
+}
+
+/* ..... track ..... */
+
+slider {
+ -moz-appearance: scrollbartrack-horizontal;
+}
+
+slider[orient="vertical"] {
+ -moz-appearance: scrollbartrack-vertical;
+}
+
+/* ..... thumb ..... */
+
+thumb {
+ -moz-appearance: scrollbarthumb-horizontal;
+}
+
+thumb[orient="vertical"] {
+ -moz-appearance: scrollbarthumb-vertical;
+}
+
+/* ..... increment ..... */
+
+scrollbarbutton[type="increment"] {
+ -moz-appearance: scrollbarbutton-right;
+}
+
+scrollbar[orient="vertical"] > scrollbarbutton[type="increment"] {
+ -moz-appearance: scrollbarbutton-down;
+}
+
+/* ..... decrement ..... */
+
+scrollbarbutton[type="decrement"] {
+ -moz-appearance: scrollbarbutton-left;
+}
+
+scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"] {
+ -moz-appearance: scrollbarbutton-up;
+}
+
+/* ::::: square at the corner of two scrollbars ::::: */
+
+scrollcorner {
+ /* XXX -moz-appearance: scrollcorner; */
+ -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#scrollbar-base);
+ width: 16px;
+ cursor: default;
+ background-color: white;
+}
+
+/* ::::::::::::::::::::: MEDIA PRINT :::::::::::::::::::::: */
+@media print {
+ html|div scrollbar {
+ -moz-appearance: scrollbar;
+ -moz-binding: url(chrome://global/content/bindings/scrollbar.xml#scrollbar);
+ cursor: default;
+ }
+}
diff --git a/toolkit/themes/osx/global/netError.css b/toolkit/themes/osx/global/netError.css
new file mode 100644
index 0000000000..9255f958e3
--- /dev/null
+++ b/toolkit/themes/osx/global/netError.css
@@ -0,0 +1,145 @@
+/* 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/. */
+
+/*
+ * This defines the look-and-feel styling of the error pages.
+ * (see: netError.xhtml)
+ *
+ * Original styling by William Price <bugzilla@mob.rice.edu>
+ * Updated by: Steven Garrity <steven@silverorange.com>
+ * Henrik Skupin <mozilla@hskupin.info>
+ */
+
+html {
+ background: -moz-Dialog;
+}
+
+body {
+ margin: 0;
+ padding: 0 1em;
+ color: -moz-FieldText;
+ font: message-box;
+}
+
+h1 {
+ margin: 0 0 .6em 0;
+ border-bottom: 1px solid ThreeDLightShadow;
+ font-size: 160%;
+}
+
+ul, ol {
+ margin: 0;
+ margin-inline-start: 1.5em;
+ padding: 0;
+}
+
+ul > li, ol > li {
+ margin-bottom: .5em;
+}
+
+ul {
+ list-style: square;
+}
+
+#errorPageContainer {
+ position: relative;
+ min-width: 13em;
+ max-width: 52em;
+ margin: 4em auto;
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ padding: 3em;
+ padding-inline-start: 30px;
+ background: url("chrome://global/skin/icons/warning-large.png") left 0 no-repeat -moz-Field;
+ background-origin: content-box;
+}
+
+#errorPageContainer.certerror {
+ background-image: url("chrome://global/skin/icons/sslWarning.png");
+}
+
+#errorPageContainer:dir(rtl) {
+ background-position: right 0;
+}
+
+#errorTitle {
+ margin-inline-start: 80px;
+}
+
+#errorLongContent {
+ margin-inline-start: 80px;
+}
+
+#errorShortDesc > p {
+ overflow: auto;
+ border-bottom: 1px solid ThreeDLightShadow;
+ padding-bottom: 1em;
+ font-size: 130%;
+ white-space: pre-wrap;
+}
+
+#errorLongDesc {
+ padding-inline-end: 3em;
+ font-size: 110%;
+}
+
+#errorLongDesc > p {
+}
+
+#errorTryAgain {
+ margin-top: 2em;
+ margin-inline-start: 80px;
+}
+
+#brand {
+ position: absolute;
+ right: 0;
+ bottom: -1.5em;
+ margin-inline-end: 10px;
+ opacity: .4;
+}
+
+#brand:dir(rtl) {
+ right: auto;
+ left: 0;
+}
+
+#brand > p {
+ margin: 0;
+}
+
+#errorContainer {
+ display: none;
+}
+
+#securityOverrideDiv {
+ padding-top: 10px;
+}
+
+#securityOverrideContent {
+ background-color: #FFF090; /* Pale yellow */
+ padding: 10px;
+ border-radius: 10px;
+}
+
+/* Custom styling for 'blacklist' error class */
+:root.blacklist #errorTitle, :root.blacklist #errorLongContent,
+:root.blacklist #errorShortDesc, :root.blacklist #errorLongDesc,
+:root.blacklist a {
+ background-color: #722; /* Dark red */
+ color: white;
+}
+
+:root.blacklist #errorPageContainer {
+ background-image: url("chrome://global/skin/icons/blacklist_64.png");
+ background-color: #722;
+}
+
+:root.blacklist {
+ background: #333;
+}
+
+:root.blacklist #errorTryAgain {
+ display: none;
+}
diff --git a/toolkit/themes/osx/global/notification.css b/toolkit/themes/osx/global/notification.css
new file mode 100644
index 0000000000..6d22cf9c86
--- /dev/null
+++ b/toolkit/themes/osx/global/notification.css
@@ -0,0 +1,206 @@
+/* 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 shared.inc
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+notification {
+ padding: 3px 3px 4px;
+ text-shadow: none;
+}
+
+notification[type="info"] {
+ color: rgba(255,255,255,0.95);
+ background: linear-gradient(#606060, #404040);
+ border-top: 1px solid #2a2a2a;
+ border-bottom: 1px solid #2a2a2a;
+}
+
+notification[type="warning"] {
+ color: rgba(0,0,0,0.95);
+ background: linear-gradient(#ffe13e, #ffc703);
+ border-top: 1px solid #bf8a01;
+ border-bottom: 1px solid #bf8a01;
+}
+
+notification[type="critical"] {
+ color: rgba(255,255,255,0.95);
+ background: linear-gradient(#d40000, #980000);
+ border-top: 1px solid #5d0000;
+ border-bottom: 1px solid #5d0000;
+}
+
+notificationbox[notificationside="top"] > notification {
+ border-top-style: none;
+}
+
+notificationbox[notificationside="bottom"] > notification {
+ border-bottom-style: none;
+}
+
+.messageText > .text-link {
+ color: inherit !important;
+ text-decoration: underline;
+}
+
+.messageImage {
+ width: 16px;
+ height: 16px;
+ margin: 0 4px;
+}
+
+/* Default icons for notifications */
+
+.messageImage[type="info"] {
+ list-style-image: url("chrome://global/skin/notification/info-icon.png");
+}
+
+.messageImage[type="warning"] {
+ list-style-image: url("chrome://global/skin/notification/warning-icon.png");
+}
+
+.messageImage[type="critical"] {
+ list-style-image: url("chrome://global/skin/notification/error-icon.png");
+}
+
+.messageText {
+ margin: 0 3px !important;
+ padding: 0;
+ font-weight: bold;
+}
+
+.messageCloseButton {
+ -moz-appearance: none;
+ padding: 0;
+ margin: 0 2px;
+ border: none;
+}
+
+/*
+ Invert the close icon for @type=info since both are normally dark. It's unclear
+ why !important is necessary here so remove it if it's no longer needed.
+*/
+notification[type="info"] .close-icon:not(:hover) {
+ -moz-image-region: rect(0, 64px, 16px, 48px) !important;
+}
+
+@media (min-resolution: 2dppx) {
+ notification[type="info"] .close-icon:not(:hover) {
+ -moz-image-region: rect(0, 128px, 32px, 96px) !important;
+ }
+}
+
+.messageCloseButton:-moz-focusring > .toolbarbutton-icon {
+ border-radius: 10000px;
+ box-shadow: 0 0 2px 1px -moz-mac-focusring,
+ 0 0 0 2px -moz-mac-focusring inset;
+}
+
+@media (min-resolution: 2dppx) {
+ .messageCloseButton > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+/* Popup notification */
+
+.popup-notification-body {
+ max-width: 25em;
+}
+
+.popup-notification-origin:not([value]),
+.popup-notification-learnmore-link:not([href]) {
+ display: none;
+}
+
+.popup-notification-origin {
+ margin-bottom: .3em !important;
+}
+
+.popup-notification-learnmore-link {
+ margin-top: .5em !important;
+}
+
+.popup-notification-button-container {
+ margin-top: 17px;
+}
+
+.popup-notification-menubutton {
+ -moz-appearance: none;
+}
+
+.popup-notification-menubutton:not([type="menu-button"]):-moz-focusring,
+.popup-notification-menubutton:-moz-focusring > .button-menubutton-dropmarker,
+.popup-notification-menubutton > .button-menubutton-button:-moz-focusring {
+ box-shadow: @focusRingShadow@;
+ position: relative;
+}
+
+.popup-notification-menubutton:not([type="menu-button"]),
+.popup-notification-menubutton > .button-menubutton-button,
+.popup-notification-menubutton > .button-menubutton-dropmarker {
+ -moz-appearance: none;
+ color: #434343;
+ border-radius: 4px;
+ border: 1px solid #b5b5b5;
+ background: linear-gradient(#fff, #f2f2f2);
+ box-shadow: inset 0 1px rgba(255,255,255,.8),
+ inset 0 0 1px rgba(255,255,255,.25),
+ 0 1px rgba(255,255,255,.3);
+ background-clip: padding-box;
+ background-origin: padding-box;
+ padding: 2px 6px;
+}
+
+.popup-notification-menubutton > .button-menubutton-button {
+ -moz-appearance: none;
+ margin: 0;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ padding-inline-start: 8px;
+ padding-inline-end: 5px;
+}
+
+.popup-notification-menubutton > .button-menubutton-dropmarker {
+ padding: 7px 8px;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-inline-start: -1px;
+ list-style-image: url("chrome://global/skin/icons/panel-dropmarker.png");
+}
+
+.popup-notification-menubutton > .button-menubutton-button:-moz-locale-dir(ltr),
+.popup-notification-menubutton > .button-menubutton-dropmarker:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.popup-notification-menubutton > .button-menubutton-button:-moz-locale-dir(rtl),
+.popup-notification-menubutton > .button-menubutton-dropmarker:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.popup-notification-menubutton:not([type="menu-button"]):hover:active,
+.popup-notification-menubutton > .button-menubutton-button:hover:active,
+.popup-notification-menubutton[open="true"] > .button-menubutton-dropmarker {
+ box-shadow: inset 0 1px 4px -3px #000, 0 1px rgba(255, 255, 255, 0.3);
+}
+
+.popup-notification-closebutton {
+ margin-inline-end: -12px;
+ margin-top: -13px;
+}
+
+.popup-notification-closeitem > .menu-iconic-left {
+ display: none;
+}
+
+.popup-notification-menubutton > .button-menubutton-button[disabled] {
+ opacity: 0.5;
+}
+
+.popup-notification-warning {
+ color: #aa1b08;
+}
diff --git a/toolkit/themes/osx/global/notification/close.png b/toolkit/themes/osx/global/notification/close.png
new file mode 100644
index 0000000000..3300a4d61e
--- /dev/null
+++ b/toolkit/themes/osx/global/notification/close.png
Binary files differ
diff --git a/toolkit/themes/osx/global/notification/error-icon.png b/toolkit/themes/osx/global/notification/error-icon.png
new file mode 100644
index 0000000000..54cc7e663b
--- /dev/null
+++ b/toolkit/themes/osx/global/notification/error-icon.png
Binary files differ
diff --git a/toolkit/themes/osx/global/notification/info-icon.png b/toolkit/themes/osx/global/notification/info-icon.png
new file mode 100644
index 0000000000..55d45f165c
--- /dev/null
+++ b/toolkit/themes/osx/global/notification/info-icon.png
Binary files differ
diff --git a/toolkit/themes/osx/global/notification/warning-icon.png b/toolkit/themes/osx/global/notification/warning-icon.png
new file mode 100644
index 0000000000..13cf79d6df
--- /dev/null
+++ b/toolkit/themes/osx/global/notification/warning-icon.png
Binary files differ
diff --git a/toolkit/themes/osx/global/numberbox.css b/toolkit/themes/osx/global/numberbox.css
new file mode 100644
index 0000000000..e5de22d218
--- /dev/null
+++ b/toolkit/themes/osx/global/numberbox.css
@@ -0,0 +1,33 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+textbox[type="number"] {
+ -moz-appearance: none;
+ -moz-box-align: center;
+ padding: 0 !important;
+ border: none;
+ background-color: transparent;
+ cursor: default;
+}
+
+html|*.numberbox-input {
+ text-align: right;
+ padding: 0 1px !important;
+}
+
+.numberbox-input-box {
+ -moz-appearance: textfield;
+ margin-right: 4px;
+ border: 3px solid;
+ -moz-border-top-colors: transparent #888888 #000000;
+ -moz-border-right-colors: transparent #FFFFFF #000000;
+ -moz-border-bottom-colors: transparent #FFFFFF #000000;
+ -moz-border-left-colors: transparent #888888 #000000;
+ border-top-right-radius: 2px;
+ border-bottom-left-radius: 2px;
+ background-color: -moz-Field;
+}
diff --git a/toolkit/themes/osx/global/popup.css b/toolkit/themes/osx/global/popup.css
new file mode 100644
index 0000000000..cf0266a3a6
--- /dev/null
+++ b/toolkit/themes/osx/global/popup.css
@@ -0,0 +1,141 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+menupopup,
+panel {
+ -moz-appearance: menupopup;
+ background-color: Menu;
+}
+
+menupopup > menu > menupopup {
+ margin-top: -4px;
+}
+
+.popup-internal-box {
+ padding: 4px 0;
+}
+
+panel[titlebar] {
+ -moz-appearance: none; /* to disable rounded corners */
+}
+
+panel[type="arrow"] {
+ -moz-appearance: none;
+ background: transparent;
+}
+
+panel[type="arrow"][side="top"],
+panel[type="arrow"][side="bottom"] {
+ margin-left: -25px;
+ margin-right: -25px;
+}
+
+panel[type="arrow"][side="left"],
+panel[type="arrow"][side="right"] {
+ margin-top: -25px;
+ margin-bottom: -25px;
+}
+
+.panel-arrowcontent {
+ -moz-appearance: none;
+ background: var(--arrowpanel-background);
+ border-radius: var(--arrowpanel-border-radius);
+ box-shadow: 0 0 0 1px var(--arrowpanel-border-color);
+ color: var(--arrowpanel-color);
+ border: none;
+ padding: var(--arrowpanel-padding);
+ margin: 1px;
+}
+
+.panel-arrow[side="top"] {
+ list-style-image: var(--panel-arrow-image-vertical,
+ url("chrome://global/skin/arrow/panelarrow-vertical.png"));
+ margin-left: 16px;
+ margin-right: 16px;
+ margin-bottom: -1px;
+}
+
+.panel-arrow[side="bottom"] {
+ list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.png");
+ -moz-transform: scaleY(-1);
+ margin-left: 16px;
+ margin-right: 16px;
+ margin-top: -1px;
+}
+
+.panel-arrow[side="left"] {
+ list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.png");
+ margin-top: 16px;
+ margin-bottom: 16px;
+ margin-right: -1px;
+}
+
+.panel-arrow[side="right"] {
+ list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.png");
+ transform: scaleX(-1);
+ margin-top: 16px;
+ margin-bottom: 16px;
+ margin-left: -1px;
+}
+
+@media (min-resolution: 2dppx) {
+ .panel-arrow[side="top"],
+ .panel-arrow[side="bottom"] {
+ list-style-image: var(--panel-arrow-image-vertical,
+ url("chrome://global/skin/arrow/panelarrow-vertical@2x.png"));
+ width: 18px;
+ height: 10px;
+ }
+
+ .panel-arrow[side="left"],
+ .panel-arrow[side="right"] {
+ list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal@2x.png");
+ width: 10px;
+ height: 18px;
+ }
+}
+
+/* ::::: tooltip ::::: */
+
+tooltip {
+ -moz-appearance: tooltip;
+ margin-top: 18px;
+ padding: 2px 3px;
+ max-width: 40em;
+ color: InfoText;
+ font: message-box;
+ cursor: default;
+}
+
+tooltip[titletip="true"] {
+ /* See bug 32157 comment 128
+ * margin: -2px 0px 0px -3px;
+ */
+ max-width: none;
+}
+
+/* rules for popups associated with menulists */
+
+menulist > menupopup {
+ min-width: 0px;
+}
+
+menulist > menupopup:not([position]) {
+ margin-inline-start: -13px;
+ margin-top: -2px;
+}
+
+menulist[editable="true"] > menupopup {
+ -moz-appearance: none;
+}
+
+menulist > menupopup > .popup-internal-box {
+ padding: 0;
+}
+
+menulist:not([editable="true"]) > menupopup {
+ padding: 4px 0;
+}
diff --git a/toolkit/themes/osx/global/preferences.css b/toolkit/themes/osx/global/preferences.css
new file mode 100644
index 0000000000..d0b82a819c
--- /dev/null
+++ b/toolkit/themes/osx/global/preferences.css
@@ -0,0 +1,64 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+prefwindow {
+ padding: 0;
+ font: -moz-dialog !important;
+}
+
+prefpane {
+ padding: 12px 12px 0 12px;
+}
+
+prefwindow[type="child"] > prefpane {
+ padding: 0;
+}
+
+.prefWindow-dlgbuttons {
+ margin: 0 12px 12px;
+ padding-top: 0 !important;
+}
+
+.paneSelector {
+ font: message-box;
+ padding: 1px 4px;
+ -moz-appearance: toolbar;
+ margin: 0;
+}
+
+radio[pane] {
+ border: solid transparent;
+ border-width: 0 2px;
+ padding: 5px 4px 3px;
+ margin: 0;
+ -moz-appearance: none;
+ text-shadow: rgba(255, 255, 255, 0.4) 0 1px;
+}
+
+radio[pane]:active:hover {
+ text-shadow: none;
+}
+
+radio[pane] > .paneButtonIcon {
+ /* preload external filter file */
+ background-image: url("chrome://global/skin/filters.svg");
+}
+
+radio[pane]:active:hover > .paneButtonIcon {
+ filter: url("chrome://global/skin/filters.svg#iconPressed");
+}
+
+radio[pane][selected="true"] {
+ -moz-border-image: url("chrome://global/skin/icons/panebutton-active.png") 0 2 fill repeat stretch;
+}
+
+radio[pane][selected="true"]:-moz-window-inactive {
+ -moz-border-image: url("chrome://global/skin/icons/panebutton-inactive.png") 0 2 fill repeat stretch;
+}
+
+.paneButtonLabel {
+ margin: 0 !important;
+}
diff --git a/toolkit/themes/osx/global/progressmeter.css b/toolkit/themes/osx/global/progressmeter.css
new file mode 100644
index 0000000000..13fce252ab
--- /dev/null
+++ b/toolkit/themes/osx/global/progressmeter.css
@@ -0,0 +1,22 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+progressmeter {
+ -moz-appearance: progressbar;
+ margin: 2px 4px;
+ min-width: 128px;
+ height: 12px;
+}
+
+.progress-remainder[flex="100"], .progress-remainder[flex="0"] {
+ background-image: none !important;
+ -moz-appearance: none;
+}
+
+.progressmeter-statusbar {
+ margin: 0;
+ border-width: 1px;
+}
diff --git a/toolkit/themes/osx/global/radio.css b/toolkit/themes/osx/global/radio.css
new file mode 100644
index 0000000000..e21d93011b
--- /dev/null
+++ b/toolkit/themes/osx/global/radio.css
@@ -0,0 +1,43 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+radiogroup {
+ margin: 1px 0px 1px 0px;
+}
+
+radio {
+ -moz-appearance: radio-container;
+ -moz-box-align: center;
+ margin: 4px 2px;
+ -moz-user-focus: ignore;
+}
+
+.radio-label-box {
+ margin-inline-start: 0px;
+ padding: 0px;
+}
+
+.radio-icon {
+ margin-inline-end: 2px;
+}
+
+.radio-label {
+ margin: 1px 0 !important;
+}
+
+radio[disabled="true"] {
+ color: GrayText !important;
+}
+
+.radio-check, .radio-check-box1 {
+ -moz-appearance: radio;
+ margin: 0 1px 1px;
+ /* vertical-align tells native theming where to snap to. However, this doesn't
+ * always work reliably because of bug 503833. */
+ vertical-align: bottom;
+ width: 1.3em;
+ height: 1.3em;
+}
diff --git a/toolkit/themes/osx/global/resizer.css b/toolkit/themes/osx/global/resizer.css
new file mode 100644
index 0000000000..18cdd2bc93
--- /dev/null
+++ b/toolkit/themes/osx/global/resizer.css
@@ -0,0 +1,69 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+resizer {
+ -moz-appearance: resizer;
+ background: url("chrome://global/skin/icons/resizer.png") no-repeat;
+ background-size: 100% 100%;
+ cursor: se-resize;
+ min-width: 15px;
+ width: 15px;
+ min-height: 15px;
+ height: 15px;
+}
+@media (min-resolution: 2dppx) {
+ resizer {
+ background-image: url("chrome://global/skin/icons/resizer@2x.png");
+ background-size: 100% 100%;
+ }
+}
+
+resizer[type="window"] {
+ display: none;
+}
+
+resizer[rtl="true"],
+resizer[dir="bottomend"]:-moz-locale-dir(rtl) {
+ background: url("chrome://global/skin/icons/resizer-rtl.png") no-repeat;
+}
+@media (min-resolution: 2dppx) {
+ resizer[rtl="true"],
+ resizer[dir="bottomend"]:-moz-locale-dir(rtl) {
+ background-image: url("chrome://global/skin/icons/resizer-rtl@2x.png");
+ background-size: 100% 100%;
+ }
+}
+
+
+resizer[dir="left"],
+resizer[dir="bottomleft"],
+resizer[dir="bottomstart"] {
+ transform: scaleX(-1);
+}
+
+resizer[dir="bottomleft"],
+resizer[dir="bottomstart"]:not([rtl="true"]):not(:-moz-locale-dir(rtl)),
+resizer[dir="bottomend"][rtl="true"] {
+ cursor: sw-resize;
+}
+
+resizer[dir="top"],
+resizer[dir="bottom"] {
+ cursor: ns-resize;
+}
+
+resizer[dir="left"],
+resizer[dir="right"] {
+ cursor: ew-resize;
+}
+
+resizer[dir="topleft"] {
+ cursor: nw-resize;
+}
+
+resizer[dir="topright"] {
+ cursor: ne-resize;
+}
diff --git a/toolkit/themes/osx/global/richlistbox.css b/toolkit/themes/osx/global/richlistbox.css
new file mode 100644
index 0000000000..605c89abb3
--- /dev/null
+++ b/toolkit/themes/osx/global/richlistbox.css
@@ -0,0 +1,27 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+richlistbox {
+ -moz-appearance: listbox;
+ margin: 2px 4px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+}
+
+richlistbox[disabled="true"] {
+ color: GrayText;
+}
+
+richlistitem[selected="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+richlistbox:focus > richlistitem[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
diff --git a/toolkit/themes/osx/global/scale.css b/toolkit/themes/osx/global/scale.css
new file mode 100644
index 0000000000..2e090bf28d
--- /dev/null
+++ b/toolkit/themes/osx/global/scale.css
@@ -0,0 +1,46 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.scale-slider {
+ -moz-appearance: scale-horizontal;
+ background: url("chrome://global/skin/scale/scale-tray-horiz.gif") 0% 50% repeat-x;
+ margin: 2px 4px;
+ width: 100px;
+}
+
+.scale-slider[orient="vertical"]
+{
+ -moz-appearance: scale-vertical;
+ background: url("chrome://global/skin/scale/scale-tray-vert.gif") 50% 0% repeat-y;
+ margin: 4px 2px;
+ width: auto;
+ height: 100px;
+}
+
+.scale-thumb {
+ -moz-appearance: scalethumb-horizontal;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDLightShadow ThreeDHighlight;
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-left-colors: ThreeDLightShadow ThreeDHighlight;
+ background-color: -moz-Dialog;
+ min-width: 30px;
+ min-height: 15px;
+}
+
+.scale-thumb[orient="vertical"] {
+ -moz-appearance: scalethumb-vertical;
+ min-width: 15px;
+ min-height: 30px;
+}
+
+.scale-thumb[disabled="true"] {
+ -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow !important;
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow !important;
+ -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow !important;
+ -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow !important;
+}
diff --git a/toolkit/themes/osx/global/scale/scale-tray-horiz.gif b/toolkit/themes/osx/global/scale/scale-tray-horiz.gif
new file mode 100644
index 0000000000..b87fe68c15
--- /dev/null
+++ b/toolkit/themes/osx/global/scale/scale-tray-horiz.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/scale/scale-tray-vert.gif b/toolkit/themes/osx/global/scale/scale-tray-vert.gif
new file mode 100644
index 0000000000..97687b2e22
--- /dev/null
+++ b/toolkit/themes/osx/global/scale/scale-tray-vert.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/scrollbox.css b/toolkit/themes/osx/global/scrollbox.css
new file mode 100644
index 0000000000..c9b7276698
--- /dev/null
+++ b/toolkit/themes/osx/global/scrollbox.css
@@ -0,0 +1,62 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* Horizontal enabled */
+.autorepeatbutton-up[orient="horizontal"],
+.scrollbutton-up[orient="horizontal"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-lft-sharp.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+.autorepeatbutton-down[orient="horizontal"],
+.scrollbutton-down[orient="horizontal"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-rit-sharp.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+/* Horizontal disabled */
+.autorepeatbutton-up[orient="horizontal"][disabled="true"],
+.scrollbutton-up[orient="horizontal"][disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-lft-dis.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+.autorepeatbutton-down[orient="horizontal"][disabled="true"],
+.scrollbutton-down[orient="horizontal"][disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-rit-dis.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+/* Vertical enabled */
+.autorepeatbutton-up:not([orient="horizontal"]),
+.scrollbutton-up {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up-sharp.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+.autorepeatbutton-down:not([orient="horizontal"]),
+.scrollbutton-down {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-sharp.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+/* Vertical disabled */
+.autorepeatbutton-up[disabled="true"]:not([orient="horizontal"]),
+.scrollbutton-up[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up-dis.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+.autorepeatbutton-down[disabled="true"]:not([orient="horizontal"]),
+.scrollbutton-down[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.gif");
+ -moz-image-region: auto; /* cut off inheritance */
+}
+
+.scrollbutton-up > .toolbarbutton-text,
+.scrollbutton-down > .toolbarbutton-text {
+ display: none;
+}
diff --git a/toolkit/themes/osx/global/shared.inc b/toolkit/themes/osx/global/shared.inc
new file mode 100644
index 0000000000..350fed1721
--- /dev/null
+++ b/toolkit/themes/osx/global/shared.inc
@@ -0,0 +1,20 @@
+%filter substitution
+
+%define loweredShadow 0 1px rgba(255, 255, 255, .4)
+%define focusRingShadow 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 1.5px 1px -moz-mac-focusring
+%define yosemiteFocusRingShadow 0 0 0 0.5px -moz-mac-focusring inset, 0 0 0 2px -moz-mac-focusring
+
+%define roundButtonBorder 1px solid rgba(0,0,0,.35)
+%define roundButtonBackground linear-gradient(#f6f6f6, #e9e9e9)
+%define roundButtonShadow 0 1px rgba(255,255,255,.5), inset 0 1px 1px rgba(255,255,255,.5)
+%define roundButtonPressedBackground #dadada
+%define roundButtonPressedShadow 0 1px rgba(255,255,255,.4), inset 0 1px 3px rgba(0,0,0,.2)
+
+%define scopeBarBackground linear-gradient(#E8E8E8, #D0D0D0) repeat-x
+%define scopeBarSeparatorBorder 1px solid #888
+%define scopeBarTitleColor #6D6D6D
+
+%define toolbarbuttonCornerRadius 3px
+%define toolbarbuttonBackground linear-gradient(#FFF, #ADADAD) repeat-x
+%define toolbarbuttonPressedInnerShadow inset rgba(0, 0, 0, 0.3) 0 -6px 10px, inset #000 0 1px 3px, inset rgba(0, 0, 0, 0.2) 0 1px 3px
+%define toolbarbuttonInactiveBorderColor rgba(146, 146, 146, 0.84)
diff --git a/toolkit/themes/osx/global/spinbuttons.css b/toolkit/themes/osx/global/spinbuttons.css
new file mode 100644
index 0000000000..bf89520f68
--- /dev/null
+++ b/toolkit/themes/osx/global/spinbuttons.css
@@ -0,0 +1,31 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+spinbuttons {
+ height: 24px;
+ min-height: 24px;
+ -moz-appearance: spinner;
+ cursor: default;
+}
+
+.spinbuttons-up {
+ -moz-appearance: none;
+ -moz-box-flex: 1;
+ min-width: 1px;
+ min-height: 1px;
+ margin: 0;
+ padding: 0;
+}
+
+.spinbuttons-down {
+ -moz-appearance: none;
+ -moz-box-flex: 1;
+ min-width: 1px;
+ min-height: 1px;
+ margin: 0;
+ padding: 0;
+}
+
diff --git a/toolkit/themes/osx/global/splitter.css b/toolkit/themes/osx/global/splitter.css
new file mode 100644
index 0000000000..caaa83ad0a
--- /dev/null
+++ b/toolkit/themes/osx/global/splitter.css
@@ -0,0 +1,124 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* ::::: splitter (vertical) ::::: */
+
+splitter {
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ cursor: ew-resize;
+ min-width: 9px;
+ min-height: 9px;
+ background: url("chrome://global/skin/splitter/dimple.png") transparent no-repeat center;
+}
+
+splitter[state="collapsed"][collapse="before"],
+splitter[state="collapsed"][substate="before"],
+splitter[state="collapsed"][collapse="after"]:-moz-locale-dir(rtl),
+splitter[state="collapsed"][substate="after"]:-moz-locale-dir(rtl) {
+ cursor: e-resize;
+}
+
+splitter[state="collapsed"][collapse="after"],
+splitter[state="collapsed"][substate="after"],
+splitter[state="collapsed"][collapse="before"]:-moz-locale-dir(rtl),
+splitter[state="collapsed"][substate="before"]:-moz-locale-dir(rtl) {
+ cursor: w-resize;
+}
+
+splitter:-moz-lwtheme {
+ background: none;
+}
+
+/* ::::: splitter (horizontal) ::::: */
+
+splitter[orient="vertical"] {
+ cursor: ns-resize;
+ min-width: 0px;
+ min-height: 9px;
+ min-width: 9px;
+ background: url("chrome://global/skin/splitter/dimple.png") transparent no-repeat center;
+}
+
+splitter[orient="vertical"][state="collapsed"][collapse="before"],
+splitter[orient="vertical"][state="collapsed"][substate="before"] {
+ cursor: s-resize;
+}
+
+splitter[orient="vertical"][state="collapsed"][collapse="after"],
+splitter[orient="vertical"][state="collapsed"][substate="after"] {
+ cursor: n-resize;
+}
+
+splitter[disabled="true"] {
+ cursor: default !important;
+}
+
+/* ::::: splitter grippy ::::: */
+
+grippy {
+ cursor: pointer;
+ margin: 0px 1px;
+ min-width: 4px;
+ min-height: 115px;
+ background-color: transparent;
+ background-repeat: no-repeat;
+}
+
+grippy:hover {
+ background-color: ThreeDHighlight;
+}
+
+splitter[orient="vertical"] > grippy {
+ margin: 1px 0px;
+ min-width: 115px;
+ min-height: 4px;
+}
+
+/* ..... normal state ..... */
+
+/* vertical grippies */
+splitter[collapse="before"] > grippy,
+splitter[collapse="after"] > grippy:-moz-locale-dir(rtl) {
+ background-image: url("chrome://global/skin/splitter/grip-left.gif");
+}
+
+splitter[collapse="after"] > grippy,
+splitter[collapse="before"] > grippy:-moz-locale-dir(rtl) {
+ background-image: url("chrome://global/skin/splitter/grip-right.gif");
+}
+
+/* horizontal grippies */
+splitter[collapse="before"][orient="vertical"] > grippy {
+ background-image: url("chrome://global/skin/splitter/grip-top.gif");
+}
+
+splitter[collapse="after"][orient="vertical"] > grippy {
+ background-image: url("chrome://global/skin/splitter/grip-bottom.gif");
+}
+
+/* ..... collapsed state ..... */
+
+/* vertical grippies */
+splitter[collapse="before"][state="collapsed"] > grippy,
+splitter[collapse="after"][state="collapsed"] > grippy:-moz-locale-dir(rtl) {
+ background-image: url("chrome://global/skin/splitter/grip-right.gif");
+}
+
+splitter[collapse="after"][state="collapsed"] > grippy,
+splitter[collapse="before"][state="collapsed"] > grippy:-moz-locale-dir(rtl) {
+ background-image: url("chrome://global/skin/splitter/grip-left.gif");
+}
+
+/* horizontal grippies */
+splitter[collapse="before"][state="collapsed"][orient="vertical"] > grippy {
+ background-image: url("chrome://global/skin/splitter/grip-bottom.gif");
+}
+
+splitter[collapse="after"][state="collapsed"][orient="vertical"] > grippy {
+ background-image: url("chrome://global/skin/splitter/grip-top.gif");
+}
+
diff --git a/toolkit/themes/osx/global/splitter/dimple.png b/toolkit/themes/osx/global/splitter/dimple.png
new file mode 100644
index 0000000000..4d0b91bfea
--- /dev/null
+++ b/toolkit/themes/osx/global/splitter/dimple.png
Binary files differ
diff --git a/toolkit/themes/osx/global/splitter/grip-bottom.gif b/toolkit/themes/osx/global/splitter/grip-bottom.gif
new file mode 100644
index 0000000000..af6290fe9d
--- /dev/null
+++ b/toolkit/themes/osx/global/splitter/grip-bottom.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/splitter/grip-left.gif b/toolkit/themes/osx/global/splitter/grip-left.gif
new file mode 100644
index 0000000000..6be9bc4f40
--- /dev/null
+++ b/toolkit/themes/osx/global/splitter/grip-left.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/splitter/grip-right.gif b/toolkit/themes/osx/global/splitter/grip-right.gif
new file mode 100644
index 0000000000..71be69083e
--- /dev/null
+++ b/toolkit/themes/osx/global/splitter/grip-right.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/splitter/grip-top.gif b/toolkit/themes/osx/global/splitter/grip-top.gif
new file mode 100644
index 0000000000..3cba005946
--- /dev/null
+++ b/toolkit/themes/osx/global/splitter/grip-top.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/tabbox.css b/toolkit/themes/osx/global/tabbox.css
new file mode 100644
index 0000000000..4fcbac486d
--- /dev/null
+++ b/toolkit/themes/osx/global/tabbox.css
@@ -0,0 +1,148 @@
+/* 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/. */
+
+/*
+ The default style of these tabs is that of an NSTabView with tabs at
+ the top in the "regular" size. These tabs can be used with or without
+ a tabbox element.
+ For bottom tabs you should use the "tabs-bottom" class on the tabbox
+ or the tabs element. Bottom tabs use a style that's similar to the
+ one used in Adium.
+*/
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+tabbox {
+ margin: 0 5px;
+}
+
+tabpanels {
+ -moz-appearance: tabpanels;
+ padding: 33px 15px 15px;
+}
+
+tabs {
+ -moz-box-align: center;
+ font: menu;
+}
+
+tabbox > tabs {
+ padding: 0 10px;
+ margin-bottom: -12px;
+ position: relative;
+}
+
+tab {
+ -moz-appearance: tab;
+}
+
+tab:-moz-focusring {
+ /* Tab focus rings need to overlay adjacent tabs. */
+ position: relative;
+}
+
+tab:first-of-type {
+ padding-inline-start: 2px;
+}
+
+tab:last-of-type {
+ padding-inline-end: 2px;
+}
+
+tab[visuallyselected="true"] {
+ color: #FFF;
+ text-shadow: rgba(0, 0, 0, 0.4) 0 1px;
+}
+
+.tab-middle {
+ padding: 1px 6px 2px;
+}
+
+.tabs-left,
+.tabs-right {
+ -moz-box-flex: 1;
+}
+
+/* Tabs at the bottom
+ * These tabs are smaller, left aligned and don't extend into the tabpanel.
+ */
+
+tabbox.tabs-bottom > tabpanels {
+ padding: 10px;
+}
+
+tabbox.tabs-bottom > tabs,
+tabs.tabs-bottom {
+ background-color: rgba(0, 0, 0, 0.1);
+ padding: 0;
+ margin: 0;
+ border-top: 2px solid;
+ -moz-border-top-colors: #888 rgba(0, 0, 0, 0.08);
+ -moz-box-align: start;
+ font: message-box;
+}
+
+tabbox.tabs-bottom > tabs > .tabs-left,
+tabs.tabs-bottom > .tabs-left {
+ -moz-box-flex: 0;
+}
+
+tabbox.tabs-bottom > tabs > tab,
+tabs.tabs-bottom > tab {
+ -moz-appearance: none;
+ margin: -1px 0 0;
+ padding: 0 0 2px 0;
+ position: relative;
+ border-inline-end: 1px solid rgba(0, 0, 0, 0.19);
+}
+
+tabbox.tabs-bottom > tabs > tab > .tab-middle,
+tabs.tabs-bottom > tab > .tab-middle {
+ padding: 1px 2px 0 2px;
+}
+
+tabbox.tabs-bottom > tabs > tab:not([visuallyselected=true]):hover,
+tabs.tabs-bottom > tab:not([visuallyselected=true]):hover {
+ background-color: rgba(0, 0, 0, 0.1);
+ border-inline-end-color: rgba(0, 0, 0, 0.1);
+}
+
+tabbox.tabs-bottom > tabs > tab[visuallyselected=true],
+tabs.tabs-bottom > tab[visuallyselected=true] {
+ color: #000;
+ text-shadow: none;
+ border: solid #888;
+ border-width: 0 2px 2px;
+ border-radius: 2px;
+ -moz-border-left-colors: rgba(0, 0, 0, 0.08) #888;
+ -moz-border-right-colors: rgba(0, 0, 0, 0.08) #888;
+ -moz-border-bottom-colors: rgba(0, 0, 0, 0.08) #888;
+ margin-inline-end: -1px;
+ margin-top: -2px;
+ margin-bottom: 1px;
+ padding: 0;
+}
+
+tabbox.tabs-bottom > tabs > tab[beforeselected=true],
+tabs.tabs-bottom > tab[beforeselected=true] {
+ border-inline-end-color: transparent;
+ margin-inline-end: -2px;
+}
+
+tabbox.tabs-bottom > tabs > tab:first-of-type:not([visuallyselected=true]),
+tabs.tabs-bottom > tab:first-of-type:not([visuallyselected=true]) {
+ border-inline-start: 4px solid transparent;
+}
+
+tabbox.tabs-bottom > tabs > tab:first-of-type[visuallyselected=true],
+tabs.tabs-bottom > tab:first-of-type[visuallyselected=true] {
+ margin-inline-start: 2px;
+}
+
+tabbox.tabs-bottom,
+tabbox.tabs-bottom > tabpanels,
+tabbox.tabs-bottom > tabs > tab[visuallyselected=true] > .tab-middle,
+tabs.tabs-bottom > tab[visuallyselected=true] > .tab-middle {
+ -moz-appearance: dialog;
+}
diff --git a/toolkit/themes/osx/global/tabprompts.css b/toolkit/themes/osx/global/tabprompts.css
new file mode 100644
index 0000000000..14a23f2696
--- /dev/null
+++ b/toolkit/themes/osx/global/tabprompts.css
@@ -0,0 +1,67 @@
+/* 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/. */
+
+/* Tab Modal Prompt boxes */
+tabmodalprompt {
+ background-image: url(chrome://global/skin/icons/tabprompts-bgtexture.png);
+ background-color: hsla(0,0%,10%,.5);
+ font-family: sans-serif; /* use content font not system UI font */
+ font-size: 110%;
+}
+
+.mainContainer {
+ color: black;
+ background-color: hsla(0,0%,100%,.95);
+ background-clip: padding-box;
+ border-radius: 2px;
+ border: 1px solid hsla(0,0%,0%,.5);
+}
+
+.topContainer {
+ padding: 20px;
+}
+
+.buttonContainer {
+ padding: 12px 20px 15px;
+ background-color: hsla(0,0%,0%,.05);
+ border-top: 1px solid hsla(0,0%,0%,.05);
+}
+
+button {
+ -moz-appearance: none;
+ padding: 2px 0;
+ margin: 0;
+ margin-inline-start: 8px;
+ border-radius: 2px;
+ color: black !important;
+ background-color: hsl(0,0%,90%);
+ background-image: linear-gradient(hsla(0,0%,100%,.7), transparent);
+ background-clip: padding-box;
+ border: 1px solid;
+ border-color: hsl(0,0%,65%) hsl(0,0%,60%) hsl(0,0%,50%);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.9) inset,
+ 0 1px 2px hsla(0,0%,0%,.1);
+}
+
+
+button[default=true] {
+ background-color: hsl(0,0%,79%);
+}
+
+button:hover {
+ background-color: hsl(0,0%,96%);
+}
+
+button:hover:active {
+ background-image: linear-gradient(hsla(0,0%,100%,.2), transparent);
+ background-color: hsl(0,0%,70%);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 1px 3px hsla(0,0%,0%,.2);
+}
+
+button:focus {
+ box-shadow: 0 0 1px -moz-mac-focusring inset,
+ 0 0 4px 1px -moz-mac-focusring,
+ 0 0 1.5px 1px -moz-mac-focusring;
+}
diff --git a/toolkit/themes/osx/global/textbox.css b/toolkit/themes/osx/global/textbox.css
new file mode 100644
index 0000000000..d7a31c7acc
--- /dev/null
+++ b/toolkit/themes/osx/global/textbox.css
@@ -0,0 +1,102 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+textbox {
+ -moz-appearance: textfield;
+ cursor: text;
+ margin: 4px;
+ border: 3px solid;
+ -moz-border-top-colors: transparent #888888 #000000;
+ -moz-border-right-colors: transparent #FFFFFF #000000;
+ -moz-border-bottom-colors: transparent #FFFFFF #000000;
+ -moz-border-left-colors: transparent #888888 #000000;
+ border-top-right-radius: 2px;
+ border-bottom-left-radius: 2px;
+ padding: 0px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+}
+
+html|*.textbox-input,
+html|*.textbox-textarea {
+ margin: 0px !important;
+ border: none !important;
+ padding: 0px 1px !important;
+ background-color: inherit;
+ color: inherit;
+ font: inherit;
+}
+
+.textbox-contextmenu {
+ cursor: default;
+}
+
+textbox[focused="true"] {
+ -moz-border-top-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+ -moz-border-right-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+ -moz-border-bottom-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+ -moz-border-left-colors: -moz-mac-focusring -moz-mac-focusring #000000;
+}
+
+textbox[readonly="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+textbox[disabled="true"] {
+ cursor: default;
+ -moz-border-top-colors: transparent ThreeDShadow -moz-Dialog;
+ -moz-border-right-colors: transparent ThreeDShadow -moz-Dialog;
+ -moz-border-bottom-colors: transparent ThreeDShadow -moz-Dialog;
+ -moz-border-left-colors: transparent ThreeDShadow -moz-Dialog;
+ background-color: -moz-Dialog;
+ color: GrayText;
+}
+
+textbox.plain {
+ -moz-appearance: none !important;
+ background-color: transparent;
+ padding: 0px !important;
+ margin: 0px !important;
+ border: none !important;
+}
+
+textbox.plain html|*.textbox-input,
+textbox.plain html|*.textbox-textarea {
+ padding: 0px !important;
+}
+
+/* ::::: search box ::::: */
+
+textbox[type="search"] {
+ -moz-appearance: searchfield;
+ padding: 1px;
+ font-size: 12px;
+}
+
+.textbox-search-clear {
+ list-style-image: url(chrome://global/skin/icons/searchfield-cancel.svg);
+ -moz-image-region: rect(0, 14px, 14px, 0);
+ margin-bottom: 1px;
+}
+
+textbox[type="search"].compact {
+ padding: 0;
+ font-size: 11px;
+}
+
+textbox[type="search"].compact > .textbox-input-box > .textbox-search-icons > .textbox-search-clear {
+ width: 11px;
+}
+
+.textbox-search-clear:not([disabled]) {
+ cursor: default;
+}
+
+.textbox-search-icons:not([selectedIndex="1"]) {
+ visibility: hidden;
+}
diff --git a/toolkit/themes/osx/global/toolbar.css b/toolkit/themes/osx/global/toolbar.css
new file mode 100644
index 0000000000..f07332d2f3
--- /dev/null
+++ b/toolkit/themes/osx/global/toolbar.css
@@ -0,0 +1,127 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+toolbox {
+ /* Setting -moz-appearance:toolbox causes sheets to attach under the
+ * toolbox and has no other effects. It doesn't render anything. */
+ -moz-appearance: toolbox;
+}
+
+toolbar {
+ min-width: 1px;
+ min-height: 20px;
+ -moz-appearance: toolbar;
+}
+
+%ifdef MOZ_AUSTRALIS
+menubar:-moz-lwtheme,
+toolbar:-moz-lwtheme {
+ -moz-appearance: none;
+ background: none;
+ border-color: transparent;
+}
+%else
+menubar:-moz-lwtheme,
+toolbar:-moz-lwtheme {
+ -moz-appearance: none;
+ background: none;
+ border-style: none;
+}
+%endif
+
+menubar {
+ -moz-appearance: dialog; /* For content menubars, "toolbar" is too dark, so we use "dialog". */
+ min-width: 1px;
+}
+
+toolbarseparator {
+ -moz-appearance: none;
+ margin: 3px 4px;
+ background: url("chrome://global/skin/toolbar/toolbar-separator.png") transparent repeat-y;
+ padding: 0;
+ width: 1px !important;
+}
+
+/* ::::: toolbarpaletteitem ::::: */
+
+toolbarpaletteitem {
+ cursor: grab;
+}
+
+toolbar[iconsize="small"] toolbarpaletteitem[type="spacer"] {
+ min-width: 24px !important;
+}
+
+toolbarpaletteitem[type="spacer"] {
+ min-width: 32px !important;
+}
+
+.toolbarpaletteitem-box[type="spacer"] {
+ border: 1px solid #A3A3A3;
+ background: url("chrome://global/skin/10pct_transparent_grey.png") repeat;
+ width: 32px;
+ margin-top: 18px;
+}
+
+.toolbarpaletteitem-box[type="spring"] {
+ border: 1px solid #A3A3A3;
+ background: url("chrome://global/skin/toolbar/spring.png") #FFFFFF no-repeat;
+ width: 32px;
+ margin-top: 18px;
+}
+
+.toolbarpaletteitem-box[type="spring"][place="toolbar"] {
+ background: url("chrome://global/skin/10pct_transparent_grey.png") repeat;
+}
+
+.toolbarpaletteitem-box[type="spacer"][place="toolbar"],
+.toolbarpaletteitem-box[type="spring"][place="toolbar"] {
+ margin: 2px;
+}
+
+.toolbarpaletteitem-box[type="separator"][place="palette"] {
+ width: 2px;
+ height: 50px;
+}
+
+.toolbarpaletteitem-box[type="spacer"][place="palette"],
+.toolbarpaletteitem-box[type="spring"][place="palette"] {
+ margin-top: 0;
+ margin-bottom: 2px;
+ height: 32px;
+}
+
+.toolbarpaletteitem-box[type="spring"][place="palette"] {
+ background-position: center;
+ margin-left: 8px;
+ margin-right: 8px;
+}
+
+/* ..... drag and drop feedback ..... */
+
+toolbarpaletteitem[place="toolbar"] {
+ margin-left: -2px;
+ margin-right: -2px;
+ border-left: 2px solid transparent;
+ border-right: 2px solid transparent;
+}
+
+toolbarpaletteitem[dragover="left"] {
+ border-left-color: #000000;
+}
+
+toolbarpaletteitem[dragover="right"] {
+ border-right-color: #000000;
+}
+
+toolbar[iconsize="small"] toolbarspacer {
+ min-width: 24px !important;
+}
+
+toolbarspacer {
+ min-width: 32px !important;
+}
+
diff --git a/toolkit/themes/osx/global/toolbar/spring.png b/toolkit/themes/osx/global/toolbar/spring.png
new file mode 100644
index 0000000000..807e1f5e54
--- /dev/null
+++ b/toolkit/themes/osx/global/toolbar/spring.png
Binary files differ
diff --git a/toolkit/themes/osx/global/toolbar/toolbar-separator.png b/toolkit/themes/osx/global/toolbar/toolbar-separator.png
new file mode 100644
index 0000000000..21e17543a0
--- /dev/null
+++ b/toolkit/themes/osx/global/toolbar/toolbar-separator.png
Binary files differ
diff --git a/toolkit/themes/osx/global/toolbarbutton.css b/toolkit/themes/osx/global/toolbarbutton.css
new file mode 100644
index 0000000000..ab24387e3c
--- /dev/null
+++ b/toolkit/themes/osx/global/toolbarbutton.css
@@ -0,0 +1,124 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+toolbarbutton {
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ margin: 0 2px;
+ padding: 3px 2px;
+ background-color: transparent;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
+}
+
+toolbarbutton[open="true"],
+toolbarbutton:not([disabled="true"]):active:hover {
+ text-shadow: none;
+}
+
+.toolbarbutton-text {
+ margin: 0 !important; /* !important for overriding global.css */
+ padding: 0px;
+ text-align: center;
+ vertical-align: middle;
+}
+
+toolbarbutton[disabled="true"],
+toolbarbutton[disabled="true"]:hover,
+toolbarbutton[disabled="true"]:hover:active,
+toolbarbutton[disabled="true"][open="true"] {
+ color: -moz-mac-disabledtoolbartext !important;
+}
+
+/* ::::: toolbarbutton menu ::::: */
+
+.toolbarbutton-menu-dropmarker {
+ -moz-appearance: none !important;
+ border: none !important;
+ background-color: transparent !important;
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.png");
+ padding: 0;
+ padding-inline-start: 2px;
+ width: auto;
+}
+
+.toolbarbutton-menu-dropmarker[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.png");
+ padding: 0;
+ padding-inline-start: 2px;
+}
+
+/* ::::: toolbarbutton menu-button ::::: */
+
+toolbarbutton[type="menu-button"] {
+ -moz-box-align: stretch;
+ -moz-box-orient: horizontal !important;
+}
+
+toolbarbutton[type="menu-button"],
+toolbarbutton[type="menu-button"]:hover,
+toolbarbutton[type="menu-button"]:hover:active,
+toolbarbutton[type="menu-button"][open="true"],
+toolbarbutton[type="menu-button"][disabled="true"],
+toolbarbutton[type="menu-button"][disabled="true"]:hover,
+toolbarbutton[type="menu-button"][disabled="true"]:hover:active {
+ background-color: transparent;
+}
+
+.toolbarbutton-menubutton-button {
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ -moz-box-orient: vertical;
+ text-shadow: inherit;
+}
+
+/* ::::: toolbarbutton badged ::::: */
+
+.toolbarbutton-badge {
+ background-color: #d90000;
+ font-size: 9px;
+ padding: 1px 2px;
+ color: #fff;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 hsla(0, 100%, 100%, .2) inset,
+ 0 -1px 0 hsla(0, 0%, 0%, .1) inset,
+ 0 1px 0 hsla(206, 50%, 10%, .2);
+ margin: -6px 0 0 !important;
+ margin-inline-end: -6px !important;
+ min-width: 14px;
+ max-width: 28px;
+ line-height: 10px;
+ text-align: center;
+ -moz-stack-sizing: ignore;
+}
+
+.toolbarbutton-badge:-moz-window-inactive {
+ background-color: rgb(230,230,230);
+ box-shadow: none;
+ color: rgb(192,192,192);
+}
+
+toolbar[mode="icons"] > *|* > .toolbarbutton-badge {
+ margin-inline-end: -10px !important;
+}
+
+/* .......... dropmarker .......... */
+
+.toolbarbutton-menubutton-dropmarker {
+ -moz-appearance: none;
+ border: none;
+ background-color: transparent !important;
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.png");
+ width: auto;
+ padding: 0 5px;
+}
+
+.toolbarbutton-menubutton-dropmarker[disabled="true"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.png");
+}
+
+toolbarbutton.tabbable {
+ -moz-user-focus: normal !important;
+}
diff --git a/toolkit/themes/osx/global/tree.css b/toolkit/themes/osx/global/tree.css
new file mode 100644
index 0000000000..472a51fc91
--- /dev/null
+++ b/toolkit/themes/osx/global/tree.css
@@ -0,0 +1,296 @@
+/* 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 shared.inc
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+tree {
+ margin: 0px 4px;
+ color: -moz-DialogText;
+ background-color: #FFFFFF;
+ -moz-appearance: listbox;
+}
+
+/* ::::: tree focusring ::::: */
+
+.focusring > .tree-stack > .tree-rows > .tree-bodybox {
+ border: 1px solid transparent;
+}
+
+.focusring:focus > .tree-stack > .tree-rows > .tree-bodybox {
+ border: 1px solid -moz-mac-focusring;
+}
+
+
+/* ::::: tree rows ::::: */
+
+treechildren::-moz-tree-row {
+ border-top: 1px solid transparent;
+ height: 18px;
+ background-color: -moz-field;
+}
+
+treechildren:not(.autocomplete-treebody)::-moz-tree-row(multicol, odd) {
+ background-color: -moz-oddtreerow;
+}
+
+treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected) {
+ background-color: -moz-mac-secondaryhighlight;
+}
+
+treechildren:not(.autocomplete-treebody)::-moz-tree-row(selected, focus) {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+tree[seltype="cell"] > treechildren::-moz-tree-row,
+tree[seltype="text"] > treechildren::-moz-tree-row {
+ border-top: none;
+ background-color: transparent;
+}
+
+/* ::::: tree cells ::::: */
+
+treechildren::-moz-tree-cell {
+ padding: 0px 2px 0px 2px;
+}
+
+tree[seltype="cell"] > treechildren::-moz-tree-cell-text,
+tree[seltype="text"] > treechildren::-moz-tree-cell-text,
+treechildren::-moz-tree-cell-text {
+ color: inherit;
+}
+
+tree[seltype="cell"] > treechildren::-moz-tree-cell {
+ padding: 0px 1px 0px 1px;
+}
+
+tree[seltype="text"] > treechildren::-moz-tree-cell-text {
+ padding: 0px 1px 1px 1px;
+}
+
+treechildren::-moz-tree-cell-text(selected) {
+ color: -moz-DialogText;
+}
+
+tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected) {
+ background-color: -moz-mac-secondaryhighlight;
+
+}
+tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected) {
+ color: -moz-DialogText;
+}
+
+tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected) {
+ background-color: -moz-mac-secondaryhighlight;
+ color: -moz-DialogText;
+}
+
+treechildren::-moz-tree-cell-text(selected, focus) {
+ color: HighlightText;
+}
+
+tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, focus) {
+ background-color: Highlight;
+}
+tree[seltype="cell"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
+ color: HighlightText;
+}
+
+tree[seltype="text"] > treechildren::-moz-tree-cell-text(active, selected, focus) {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+/* ::::: lines connecting cells ::::: */
+
+treechildren::-moz-tree-line {
+ /* XXX there should be no border on Mac, but trees currently
+ paint the line black by default, so I'll just leave this
+ for now. */
+ visibility: hidden;
+ border: 1px dotted grey;
+}
+
+
+/* ::::: tree separator ::::: */
+
+treechildren::-moz-tree-separator {
+ border-top: 1px dashed #C7C7C7;
+ margin: 0 2px;
+}
+
+
+/* ::::: drop feedback ::::: */
+
+tree[seltype="cell"] > treechildren::-moz-tree-cell(primary, dropOn),
+tree[seltype="text"] > treechildren::-moz-tree-cell(primary, dropOn),
+treechildren::-moz-tree-cell(primary, dropOn) {
+ background-color: #A1A1A1 !important;
+ color: #FFF !important;
+ background-image: none;
+}
+tree[seltype="cell"] > treechildren::-moz-tree-cell-text(primary, dropOn),
+tree[seltype="text"] > treechildren::-moz-tree-cell-text(primary, dropOn),
+treechildren::-moz-tree-cell-text(primary, dropOn) {
+ color: #FFF !important;
+}
+
+treechildren::-moz-tree-drop-feedback {
+ background-color: #A1A1A1;
+ width: 50px;
+ height: 2px;
+ margin-inline-start: 5px;
+}
+
+/* ::::: tree progress meter ::::: */
+
+treechildren::-moz-tree-progressmeter {
+ margin: 2px 4px;
+ border: 2px solid;
+ -moz-border-top-colors: #AAAAAA #000000;
+ -moz-border-right-colors: #FFFFFF #000000;
+ -moz-border-bottom-colors: #FFFFFF #000000;
+ -moz-border-left-colors: #AAAAAA #000000;
+}
+
+treechildren::-moz-tree-cell-text(progressmeter) {
+ margin: 2px 4px;
+ -moz-appearance: progressbar;
+}
+
+/* ::::: tree columns ::::: */
+
+treecol,
+treecolpicker {
+ -moz-appearance: treeheadercell;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+ padding: 0px 4px;
+}
+
+.treecol-image {
+ padding: 0px 1px;
+}
+
+.treecol-text {
+ margin: 0px !important;
+}
+
+treecol[hideheader="true"] {
+ -moz-appearance: none;
+ border: none;
+ padding: 0;
+ max-height: 0px;
+}
+
+/* ..... internal box ..... */
+
+treecol:hover:active,
+treecolpicker:hover:active {
+ border-top: 2px solid;
+ border-bottom: 1px solid;
+ border-inline-start: 2px solid;
+ border-inline-end: 1px solid;
+ -moz-border-top-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-right-colors: ThreeDDarkShadow;
+ -moz-border-bottom-colors: ThreeDDarkShadow;
+ -moz-border-left-colors: ThreeDDarkShadow ThreeDShadow;
+ background-color: #666666;
+}
+
+/* ::::: column drag and drop styles ::::: */
+
+treecol[dragging="true"] {
+ -moz-border-top-colors: ThreeDDarkShadow ThreeDShadow !important;
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow!important;
+ -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow !important;
+ -moz-border-left-colors: ThreeDDarkShadow ThreeDShadow !important;
+ padding: 0px 4px !important;
+ background-color: ThreeDShadow !important;
+ color: ThreeDHighlight !important;
+}
+
+treecol[insertafter="true"]:-moz-locale-dir(ltr),
+treecol[insertbefore="true"]:-moz-locale-dir(rtl) {
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
+}
+
+treecol[insertafter="true"]:-moz-locale-dir(rtl),
+treecol[insertbefore="true"]:-moz-locale-dir(ltr) {
+ -moz-border-left-colors: ThreeDDarkShadow ThreeDShadow;
+}
+
+treechildren::-moz-tree-column(insertbefore) {
+ border-inline-start: 1px solid ThreeDShadow;
+}
+
+treechildren::-moz-tree-column(insertafter) {
+ border-inline-end: 1px solid ThreeDShadow;
+}
+
+/* ::::: column picker ::::: */
+
+.tree-columnpicker-icon {
+ list-style-image: url("chrome://global/skin/tree/columnpicker.gif");
+}
+
+/* ::::: twisty ::::: */
+
+treechildren::-moz-tree-twisty {
+ -moz-appearance: treetwisty;
+ padding-inline-end: 2px;
+}
+
+treechildren::-moz-tree-twisty(open) {
+ -moz-appearance: treetwistyopen;
+}
+
+treechildren::-moz-tree-twisty(Name, separator) {
+ -moz-appearance: none;
+}
+
+treechildren::-moz-tree-indentation {
+ width: 16px;
+}
+
+/* ::::: gridline style ::::: */
+
+treechildren.gridlines::-moz-tree-cell {
+ border-inline-end: 1px solid GrayText;
+ border-bottom: 1px solid GrayText;
+}
+
+treechildren.gridlines::-moz-tree-row {
+ border: none;
+}
+
+/* ::::: editable tree ::::: */
+
+.tree-input {
+ -moz-appearance: none;
+ border-width: 0;
+ box-shadow: @focusRingShadow@;
+ margin: 0;
+ margin-inline-start: -2px;
+ padding: 2px 1px 1px;
+}
+
+treechildren::-moz-tree-cell(active, selected, focus, editing),
+tree[seltype="cell"] > treechildren::-moz-tree-cell(active, selected, focus, editing),
+tree[seltype="text"] > treechildren::-moz-tree-cell(active, selected, focus, editing) {
+ background-color: transparent;
+ border: none;
+}
+
+treechildren::-moz-tree-cell-text(active, selected, editing) {
+ opacity: 0;
+}
diff --git a/toolkit/themes/osx/global/tree/arrow-disclosure.svg b/toolkit/themes/osx/global/tree/arrow-disclosure.svg
new file mode 100644
index 0000000000..0fee858071
--- /dev/null
+++ b/toolkit/themes/osx/global/tree/arrow-disclosure.svg
@@ -0,0 +1,28 @@
+<!-- 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/. -->
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+
+ <style>
+ .icon:not(:target) {
+ display: none;
+ }
+
+ .icon {
+ fill: #8c8c8c;
+ }
+
+ .icon.white {
+ fill: #fff;
+ }
+ </style>
+
+ <polygon id="arrow-disclosure-collapsed" class="icon" points="4,4 12,8.5 4,13" />
+ <polygon id="arrow-disclosure-collapsed-rtl" class="icon" points="4,8.5 12,4 12,13" />
+ <polygon id="arrow-disclosure-collapsed-inverted" class="icon white" points="4,4 12,8.5 4,13" />
+ <polygon id="arrow-disclosure-collapsed-inverted-rtl" class="icon white" points="4,8.5 12,4 12,13" />
+
+ <polygon id="arrow-disclosure-expanded" class="icon" points="3,5 12,5 7.5,13" />
+ <polygon id="arrow-disclosure-expanded-inverted" class="icon white" points="3,5 12,5 7.5,13" />
+
+</svg>
diff --git a/toolkit/themes/osx/global/tree/columnpicker.gif b/toolkit/themes/osx/global/tree/columnpicker.gif
new file mode 100644
index 0000000000..167f3789af
--- /dev/null
+++ b/toolkit/themes/osx/global/tree/columnpicker.gif
Binary files differ
diff --git a/toolkit/themes/osx/global/tree/folder.png b/toolkit/themes/osx/global/tree/folder.png
new file mode 100644
index 0000000000..8f21ff790a
--- /dev/null
+++ b/toolkit/themes/osx/global/tree/folder.png
Binary files differ
diff --git a/toolkit/themes/osx/global/tree/folder@2x.png b/toolkit/themes/osx/global/tree/folder@2x.png
new file mode 100644
index 0000000000..c07acf5ffb
--- /dev/null
+++ b/toolkit/themes/osx/global/tree/folder@2x.png
Binary files differ
diff --git a/toolkit/themes/osx/global/viewbuttons.css b/toolkit/themes/osx/global/viewbuttons.css
new file mode 100644
index 0000000000..bb407a64ed
--- /dev/null
+++ b/toolkit/themes/osx/global/viewbuttons.css
@@ -0,0 +1,36 @@
+/* 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 shared.inc
+
+#topBar {
+ -moz-appearance: toolbar;
+}
+
+.viewGroupWrapper {
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+#viewGroup {
+ margin: 4px 0 9px;
+}
+
+#viewGroup > radio,
+#viewGroup > toolbarbutton {
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+ -moz-appearance: toolbarbutton;
+ font: menu;
+ text-shadow: @loweredShadow@;
+ margin: 0;
+ padding: 0 1px;
+ height: 22px;
+}
+
+#viewGroup > radio[selected=true],
+#viewGroup > toolbarbutton[checked=true] {
+ color: #FFF !important;
+ text-shadow: rgba(0, 0, 0, 0.4) 0 1px;
+}
diff --git a/toolkit/themes/osx/global/wizard.css b/toolkit/themes/osx/global/wizard.css
new file mode 100644
index 0000000000..9c5e6200ce
--- /dev/null
+++ b/toolkit/themes/osx/global/wizard.css
@@ -0,0 +1,62 @@
+/* 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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+wizard {
+ padding: 14px;
+}
+
+#header {
+ display: inline !important;
+}
+
+.wizard-header {
+ -moz-appearance: dialog;
+}
+
+.wizard-header-box-1 {
+ color: #000;
+}
+
+.wizard-header-box-text {
+ padding: 6px 10px;
+ font: menu;
+ font-weight: bold;
+}
+
+.wizard-header-label {
+ margin-left: 23px;
+ font-weight: bold;
+}
+
+.wizard-header-box-icon {
+ margin-top: 3px;
+ margin-inline-end: 20px;
+ margin-bottom: 0;
+ margin-inline-start: 3px;
+}
+
+wizard[branded="true"] .wizard-header-icon {
+ list-style-image: url("chrome://branding/content/icon48.png");
+}
+
+.wizard-page-box {
+ padding: 15px 23px;
+ -moz-appearance: dialog;
+}
+
+.wizard-buttons-separator {
+ margin: 0 !important;
+ border-bottom: 1px solid #fff !important;
+}
+
+.wizard-buttons-btm {
+ padding: 3px 6px 6px;
+}
+
+.wizard-button {
+ font: menu !important;
+}
+
diff --git a/toolkit/themes/osx/mochitests/.eslintrc.js b/toolkit/themes/osx/mochitests/.eslintrc.js
new file mode 100644
index 0000000000..2c669d844e
--- /dev/null
+++ b/toolkit/themes/osx/mochitests/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/chrome.eslintrc.js"
+ ]
+};
diff --git a/toolkit/themes/osx/mochitests/chrome.ini b/toolkit/themes/osx/mochitests/chrome.ini
new file mode 100644
index 0000000000..b7c425bc70
--- /dev/null
+++ b/toolkit/themes/osx/mochitests/chrome.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_bug510426.xul]
diff --git a/toolkit/themes/osx/mochitests/test_bug510426.xul b/toolkit/themes/osx/mochitests/test_bug510426.xul
new file mode 100644
index 0000000000..4294ce42de
--- /dev/null
+++ b/toolkit/themes/osx/mochitests/test_bug510426.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=510426
+-->
+<window title="Mozilla Bug 510426"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ align="start">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=510426">Mozilla Bug 510426</a>
+</body>
+
+<notificationbox id="nb" width="300" height="100">
+ <box width="100" height="100" id="overflowGenerator"/>
+</notificationbox>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 510426 **/
+SimpleTest.waitForExplicitFinish();
+
+function openNotification() {
+ var nb = document.getElementById("nb");
+ var n = nb.appendNotification("Notification", "", null,
+ nb.PRIORITY_WARNING_LOW, [{
+ label: "Button",
+ accesskey: "u",
+ callback: null,
+ popup: null
+ }]);
+ n.addEventListener("transitionend", function (event) {
+ if (event.propertyName == "margin-top") {
+ setTimeout(function () {
+ is(n.getBoundingClientRect().height, 27, "notification bar has wrong height");
+ SimpleTest.finish();
+ }, 0);
+ }
+ }, false);
+}
+
+window.onload = openNotification;
+
+]]>
+</script>
+</window>
diff --git a/toolkit/themes/osx/moz.build b/toolkit/themes/osx/moz.build
new file mode 100644
index 0000000000..fab1daff25
--- /dev/null
+++ b/toolkit/themes/osx/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+DIRS += ['global', 'mozapps']
+
+MOCHITEST_CHROME_MANIFESTS += ['mochitests/chrome.ini']
diff --git a/toolkit/themes/osx/mozapps/downloads/buttons.png b/toolkit/themes/osx/mozapps/downloads/buttons.png
new file mode 100644
index 0000000000..04da26a252
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/downloads/buttons.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/downloads/downloadIcon.png b/toolkit/themes/osx/mozapps/downloads/downloadIcon.png
new file mode 100644
index 0000000000..42dc4943d7
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/downloads/downloadIcon.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/downloads/downloads.css b/toolkit/themes/osx/mozapps/downloads/downloads.css
new file mode 100644
index 0000000000..3ba246c1fc
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/downloads/downloads.css
@@ -0,0 +1,123 @@
+/* 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 ../../global/shared.inc
+
+#downloadView {
+ -moz-appearance: none;
+ margin: 0;
+ padding: 0;
+ border-width: 0;
+}
+
+/* Download View Items */
+richlistitem[type="download"] {
+ padding: 5px;
+ min-height: 44px !important;
+ border: 1px solid transparent;
+}
+
+richlistitem[type="download"]:not([selected="true"]):nth-child(odd) {
+ background-color: -moz-oddtreerow;
+}
+
+richlistitem[type="download"] .dateTime,
+richlistitem[type="download"] .status {
+ font-size: smaller;
+ color: #555;
+}
+
+richlistitem[selected="true"][type="download"] {
+ outline: none;
+}
+
+richlistbox:focus > richlistitem[selected="true"][type="download"] .dateTime,
+richlistbox:focus > richlistitem[selected="true"][type="download"] .status {
+ color: highlighttext;
+}
+
+
+richlistitem[type="download"] button {
+ -moz-appearance: none;
+ min-height: 16px;
+ min-width: 16px;
+ max-height: 16px;
+ max-width: 16px;
+ padding: 0;
+ margin: 0 1px 0 1px;
+}
+
+/**
+ * Images for buttons in the interface
+ */
+richlistitem[type="download"] button {
+ list-style-image: url(chrome://mozapps/skin/downloads/buttons.png);
+}
+.cancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+.cancel:hover {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+.cancel:hover:active {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+.pause {
+ -moz-image-region: rect(48px, 16px, 64px, 0px);
+}
+.pause:hover {
+ -moz-image-region: rect(48px, 32px, 64px, 16px);
+}
+.pause:not([disabled="true"]):hover:active {
+ -moz-image-region: rect(48px, 48px, 64px, 32px);
+}
+.pause[disabled="true"] {
+ -moz-image-region: rect(48px, 16px, 64px, 0px);
+}
+
+.resume {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+.resume:hover {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+.resume:hover:active {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+
+.retry {
+ -moz-image-region: rect(32px, 16px, 48px, 0px);
+}
+.retry:hover {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+.retry:hover:active {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+
+.blockedIcon {
+ list-style-image: url(chrome://global/skin/icons/Error.png);
+}
+
+/* prevent flickering when changing states */
+.downloadTypeIcon {
+ height: 32px;
+ width: 32px;
+ padding-inline-end: 2px;
+}
+
+#search {
+ -moz-box-pack: end;
+ padding-inline-end: 4px;
+ -moz-appearance: statusbar;
+}
+
+#clearListButton {
+ -moz-appearance: toolbarbutton;
+ min-height: 18px;
+ min-width: 0;
+ margin: 0 6px;
+ text-shadow: @loweredShadow@;
+}
diff --git a/toolkit/themes/osx/mozapps/downloads/unknownContentType.css b/toolkit/themes/osx/mozapps/downloads/unknownContentType.css
new file mode 100644
index 0000000000..28d01b57af
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/downloads/unknownContentType.css
@@ -0,0 +1,30 @@
+/* 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/. */
+
+#unknownContentType {
+ font: menu;
+}
+
+description {
+ font-weight: bold;
+}
+
+.plain {
+ background-color: transparent;
+ background-image: none;
+ border: none;
+}
+
+#contentTypeImage {
+ margin-right: 3px;
+ width: 16px;
+}
+
+#container > .small-indent {
+ margin-left: 0px;
+}
+
+.small-indent label {
+ margin-left: 0px;
+}
diff --git a/toolkit/themes/osx/mozapps/extensions/about.css b/toolkit/themes/osx/mozapps/extensions/about.css
new file mode 100644
index 0000000000..cfabd47dba
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/about.css
@@ -0,0 +1,78 @@
+/* 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/. */
+
+#genericAbout {
+ padding: 0px;
+ min-height: 200px;
+ max-height: 400px;
+ width: 30em;
+}
+
+#clientBox {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+.basic-info {
+ padding: 10px;
+}
+
+#extensionIcon {
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.png");
+ max-width: 64px;
+ max-height: 64px;
+ -moz-margin-end: 6px;
+}
+
+#genericAbout[addontype="theme"] #extensionIcon {
+ list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
+#genericAbout[addontype="locale"] #extensionIcon {
+ list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
+}
+
+#genericAbout[addontype="plugin"] #extensionIcon {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
+}
+
+#genericAbout[addontype="dictionary"] #extensionIcon {
+ list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
+}
+
+#extensionName {
+ font-size: 200%;
+ font-weight: bolder;
+}
+
+#extensionVersion {
+ font-weight: bold;
+}
+
+#extensionDescription {
+ margin-top: 4px;
+}
+
+#groove {
+ margin-top: 8px;
+}
+
+#extensionDetailsBox {
+ overflow: auto;
+ min-height: 100px;
+}
+
+.boxIndent {
+ -moz-margin-start: 18px;
+}
+
+#extensionCreator, .contributor {
+ margin: 0px;
+}
+
+.sectionTitle {
+ padding: 2px 0px 3px 0px;
+ margin-top: 3px;
+ font-weight: bold;
+}
diff --git a/toolkit/themes/osx/mozapps/extensions/alerticon-error.png b/toolkit/themes/osx/mozapps/extensions/alerticon-error.png
new file mode 100644
index 0000000000..8740e4911a
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/alerticon-error.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/alerticon-info-negative.png b/toolkit/themes/osx/mozapps/extensions/alerticon-info-negative.png
new file mode 100644
index 0000000000..2c5f628ab6
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/alerticon-info-negative.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/alerticon-info-positive.png b/toolkit/themes/osx/mozapps/extensions/alerticon-info-positive.png
new file mode 100644
index 0000000000..a186c6b7ad
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/alerticon-info-positive.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/alerticon-warning.png b/toolkit/themes/osx/mozapps/extensions/alerticon-warning.png
new file mode 100644
index 0000000000..75ea826f91
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/alerticon-warning.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/blocklist.css b/toolkit/themes/osx/mozapps/extensions/blocklist.css
new file mode 100644
index 0000000000..b241c94468
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/blocklist.css
@@ -0,0 +1,20 @@
+/* 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/. */
+
+richlistitem {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ -moz-padding-start: 7px;
+ -moz-padding-end: 7px;
+ border-bottom: 1px solid #C0C0C0;
+}
+
+.addon-name-version {
+ font-size: 110%;
+}
+
+.blockedLabel {
+ font-weight: bold;
+ font-style: italic;
+}
diff --git a/toolkit/themes/osx/mozapps/extensions/cancel.png b/toolkit/themes/osx/mozapps/extensions/cancel.png
new file mode 100644
index 0000000000..0d98ab2359
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/cancel.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-available.png b/toolkit/themes/osx/mozapps/extensions/category-available.png
new file mode 100644
index 0000000000..d1b737ab05
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-available.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-dictionaries.png b/toolkit/themes/osx/mozapps/extensions/category-dictionaries.png
new file mode 100644
index 0000000000..54ae4f93fc
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-dictionaries.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-discover.png b/toolkit/themes/osx/mozapps/extensions/category-discover.png
new file mode 100644
index 0000000000..a6f5b49b37
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-discover.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-experiments.png b/toolkit/themes/osx/mozapps/extensions/category-experiments.png
new file mode 100644
index 0000000000..a9d00545ef
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-experiments.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-plugins.png b/toolkit/themes/osx/mozapps/extensions/category-plugins.png
new file mode 100644
index 0000000000..5c4d8bf471
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-plugins.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-recent.png b/toolkit/themes/osx/mozapps/extensions/category-recent.png
new file mode 100644
index 0000000000..7ecfc7d4c8
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-recent.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-search.png b/toolkit/themes/osx/mozapps/extensions/category-search.png
new file mode 100644
index 0000000000..52e91a7cea
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-search.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-searchengines.png b/toolkit/themes/osx/mozapps/extensions/category-searchengines.png
new file mode 100644
index 0000000000..b893cb48a2
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-searchengines.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/category-service.png b/toolkit/themes/osx/mozapps/extensions/category-service.png
new file mode 100644
index 0000000000..997c8541ca
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/category-service.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png b/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png
new file mode 100644
index 0000000000..4ad1a1a825
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png b/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png
new file mode 100644
index 0000000000..54ae4f93fc
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/discover-logo.png b/toolkit/themes/osx/mozapps/extensions/discover-logo.png
new file mode 100644
index 0000000000..cd50735a89
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/discover-logo.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/eula.css b/toolkit/themes/osx/mozapps/extensions/eula.css
new file mode 100644
index 0000000000..5fb2c52df2
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/eula.css
@@ -0,0 +1,47 @@
+/* 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/. */
+
+#icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.png");
+ max-width: 48px;
+ max-height: 48px;
+ -moz-margin-end: 6px;
+}
+
+#eula-dialog[addontype="theme"] #icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
+#eula-dialog[addontype="locale"] #icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
+}
+
+#eula-dialog[addontype="plugin"] #icon {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
+}
+
+#eula-dialog[addontype="dictionary"] #icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
+}
+
+#heading-container {
+ -moz-box-align: center;
+}
+
+#heading {
+ font-size: 120%;
+}
+
+#eula {
+ -moz-appearance: none;
+ color: -moz-FieldText;
+ background-color: -moz-Field;
+ margin: 1em;
+ border: 1px solid;
+ -moz-border-top-colors: ActiveBorder;
+ -moz-border-right-colors: ActiveBorder;
+ -moz-border-bottom-colors: ActiveBorder;
+ -moz-border-left-colors: ActiveBorder;
+}
+
diff --git a/toolkit/themes/osx/mozapps/extensions/experimentGeneric.png b/toolkit/themes/osx/mozapps/extensions/experimentGeneric.png
new file mode 100644
index 0000000000..a9d00545ef
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/experimentGeneric.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png b/toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png
new file mode 100644
index 0000000000..fc6c8a2583
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/extensionGeneric.png b/toolkit/themes/osx/mozapps/extensions/extensionGeneric.png
new file mode 100644
index 0000000000..6a76774c7b
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/extensionGeneric.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/extensions.css b/toolkit/themes/osx/mozapps/extensions/extensions.css
new file mode 100644
index 0000000000..474cb12d10
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/extensions.css
@@ -0,0 +1,1206 @@
+/* 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/. */
+
+@import url("chrome://global/skin/inContentUI.css");
+
+%include ../../global/shared.inc
+
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+
+/*** global warnings ***/
+
+.global-warning-container {
+ overflow-x: hidden;
+}
+
+.global-warning {
+ -moz-box-align: center;
+ padding: 0 8px;
+ color: #916D15;
+ font-weight: bold;
+}
+
+.global-warning,
+.global-warning .button-link {
+ text-shadow: @loweredShadow@;
+}
+
+#addons-page[warning] .global-warning-container {
+ background-color: rgba(255, 255, 0, 0.1);
+ background-image: url("chrome://mozapps/skin/extensions/stripes-warning.png");
+ background-repeat: repeat-x;
+}
+
+#detail-view .global-warning {
+ padding: 4px 12px;
+ min-height: 31px;
+ border-bottom: 1px solid rgba(50, 65, 92, 0.4);
+}
+
+@media (max-width: 600px) {
+ .global-warning-text {
+ display: none;
+ }
+
+ .global-warning .warning-icon {
+ background-color: rgba(255, 255, 255, 0.7);
+ box-shadow: 0px 0px 2px 4px rgba(255, 255, 255, 0.7);
+ border-radius: 10px;
+ }
+}
+
+/*** global informations ***/
+#addons-page .global-info-container {
+ background-color: #e3e6eb;
+ border-top-right-radius: 5px;
+ border-top-left-radius: 5px;
+}
+
+/* Plugins aren't yet disabled by safemode (bug 342333),
+ so don't show that warning when viewing plugins. */
+#addons-page[warning="safemode"] .view-pane[type="plugin"] .global-warning-container,
+#addons-page[warning="safemode"] #detail-view[loading="true"] .global-warning-container {
+ background-color: inherit;
+ background-image: none;
+}
+
+
+/*** notification icons ***/
+
+.warning-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.png");
+ width: 16px;
+ height: 15px;
+ margin: 3px 0;
+}
+
+.error-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/alerticon-error.png");
+ width: 16px;
+ height: 15px;
+ margin: 3px 0;
+}
+
+.pending-icon,
+.info-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-positive.png");
+ width: 16px;
+ height: 15px;
+ margin: 3px 0;
+}
+
+.addon-view[pending="disable"] .pending-icon,
+.addon-view[pending="uninstall"] .pending-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/alerticon-info-negative.png");
+ width: 16px;
+ height: 15px;
+ margin: 3px 0;
+}
+
+
+/*** view alert boxes ***/
+
+.alert-container {
+ -moz-box-align: center;
+}
+
+.alert-spacer-before {
+ -moz-box-flex: 1;
+}
+
+.alert-spacer-after {
+ -moz-box-flex: 3;
+}
+
+.alert {
+ -moz-box-align: center;
+ padding: 10px;
+ color: #373D48;
+ font-size: 12px;
+ border: 1px solid #A8B8D1;
+ border-radius: 8px;
+ background-image: linear-gradient(rgba(255, 255, 255, 0.7), rgba(236, 241, 247, 0.7));
+ background-clip: padding-box;
+ box-shadow: 0 -3px 0 rgba(58, 78, 103, 0.05) inset,
+ 0 3px 0 rgba(175, 195, 220, 0.3);
+}
+
+.alert .alert-title {
+ font-weight: bold;
+ font-size: 200%;
+ margin-bottom: 15px;
+}
+
+.alert button {
+ margin: 1em 2em;
+}
+
+.loading {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+
+
+/*** category selector ***/
+
+#categories {
+ -moz-appearance: none;
+ border: none;
+ -moz-margin-end: -1px;
+ background-color: transparent;
+ position: relative;
+ margin-top: 31px;
+}
+
+.category {
+ -moz-appearance: none;
+ color: #252F3B;
+ border-width: 1px;
+ border-style: solid;
+ border-color: transparent;
+ padding: 10px 4px;
+ -moz-box-align: center;
+ overflow: hidden;
+ min-height: 0;
+}
+
+.category:-moz-locale-dir(ltr) {
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+}
+
+.category:-moz-locale-dir(rtl) {
+ border-top-right-radius: 5px;
+ border-bottom-right-radius: 5px;
+}
+
+.category[disabled] {
+ border-top: 0;
+ border-bottom: 0;
+ height: 0;
+ opacity: 0;
+ transition-property: height, opacity;
+ transition-duration: 1s, 0.8s;
+}
+
+.category:not([disabled]) {
+ height: 52px;
+ transition-property: height, opacity;
+ transition-duration: 1s, 0.8s;
+}
+
+.category[selected] {
+ background-color: rgba(255, 255, 255, 0.35);
+ color: -moz-dialogtext;
+ border-color: rgba(50, 65, 92, 0.4);
+ -moz-border-end-color: #C9CFD7;
+}
+
+.category-name {
+ font-size: 150%;
+}
+
+/* Maximize the size of the viewport when the window is small */
+@media (max-width: 800px) {
+ .category-name {
+ display: none;
+ }
+}
+
+.category-badge {
+ background-color: #55D4FF;
+ padding: 2px 8px;
+ margin: 6px 0;
+ border-radius: 10000px;
+ color: #FFF;
+ font-weight: bold;
+ text-align: center;
+}
+
+.category-badge[value="0"] {
+ visibility: hidden;
+}
+
+.category-icon {
+ width: 32px;
+ height: 32px;
+ -moz-margin-start: 6px;
+}
+
+#category-search > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-search.png");
+}
+#category-discover > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-discover.png");
+}
+#category-locale > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-languages.png");
+}
+#category-searchengine > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-searchengines.png");
+}
+#category-extension > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-extensions.png");
+}
+#category-service > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-service.png");
+}
+#category-theme > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-themes.png");
+}
+#category-plugin > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-plugins.png");
+}
+#category-dictionary > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-dictionaries.png");
+}
+#category-experiment > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-experiments.png");
+}
+#category-availableUpdates > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-available.png");
+}
+#category-recentUpdates > .category-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/category-recent.png");
+}
+
+
+/*** header ***/
+
+#header {
+ margin-bottom: 18px;
+}
+
+.nav-button {
+ list-style-image: url(chrome://mozapps/skin/extensions/navigation.png);
+}
+
+#back-btn:-moz-locale-dir(ltr),
+#forward-btn:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ border-right: none;
+ -moz-image-region: rect(0, 20px, 20px, 0);
+ padding-right: 3px;
+}
+
+#back-btn:-moz-locale-dir(rtl),
+#forward-btn:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ -moz-image-region: rect(0, 40px, 20px, 20px);
+ padding-left: 3px;
+}
+
+#header-utils-btn {
+ list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities");
+ -moz-margin-end: 18px;
+}
+
+#header-utils-btn > .toolbarbutton-menu-dropmarker {
+ list-style-image: url("chrome://mozapps/skin/extensions/toolbarbutton-dropmarker.png");
+ padding: 0;
+ -moz-margin-start: 2px;
+}
+
+#header-search {
+ margin: 0;
+ -moz-appearance: none;
+ padding: 3px 5px 2px;
+ border: 1px solid rgba(60,73,97,0.5);
+ border-radius: 10000px;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,0.15), 0 1px rgba(255,255,255,0.25);
+ background: linear-gradient(rgba(255,255,255,0.2), rgba(255,255,255,0.3));
+ background-clip: padding-box;
+}
+
+@media (max-width: 600px) {
+ #header-search {
+ width: 12em;
+ }
+}
+
+#header-search[focused] {
+ box-shadow: @focusRingShadow@, inset 0 1px 1px rgba(0,0,0,0.15);
+ border-color: -moz-mac-focusring;
+}
+
+#header-search > .textbox-input-box {
+ -moz-padding-start: 15px;
+ background: url("chrome://mozapps/skin/extensions/search.png") left no-repeat;
+}
+
+#header-search > .textbox-input-box:-moz-locale-dir(rtl) {
+ background-position: right;
+}
+
+#header-search > .textbox-input-box > html|*.textbox-input::-moz-placeholder {
+ color: #5C6470;
+ opacity: 1.0;
+}
+
+.view-header {
+ padding: 4px;
+ margin: 0;
+ min-height: 31px;
+ border-bottom: 1px solid rgba(50, 65, 92, 0.4);
+ background-image: linear-gradient(rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.05));
+}
+
+
+/*** sorters ***/
+
+.sort-controls {
+ -moz-appearance: none;
+}
+
+.sorter {
+ -moz-appearance: none;
+ border: none;
+ color: #41434B;
+ background-color: transparent;
+ border-radius: 10000px;
+ padding: 0 6px;
+ margin: 0 6px;
+ min-width: 12px !important;
+ -moz-box-direction: reverse;
+}
+
+.sorter[checkState="1"],
+.sorter[checkState="2"],
+.sorter:active:hover {
+ text-shadow: @loweredShadow@;
+ background-color: #C0C3CB;
+ box-shadow: inset #A3A6AC 0 1px 1px, @loweredShadow@;
+}
+
+.sorter:hover {
+ text-shadow: @loweredShadow@;
+ background-color: #C0C3CB;
+}
+
+.sorter[checkState="1"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+
+.sorter[checkState="2"] {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+}
+
+.sorter .button-icon {
+ -moz-margin-start: 4px;
+}
+
+
+/*** discover view ***/
+
+.discover-spacer-before,
+.discover-spacer-after {
+ -moz-box-flex: 1;
+}
+
+#discover-error .alert {
+ max-width: 45em;
+ -moz-box-flex: 1;
+}
+
+.discover-logo {
+ list-style-image: url("chrome://mozapps/skin/extensions/discover-logo.png");
+ -moz-margin-end: 15px;
+}
+
+.discover-title {
+ font-weight: bold;
+ font-size: 24px;
+ font-family: MetaWebPro-Book, "Trebuchet MS", sans-serif;
+ margin: 0 0 15px 0;
+}
+
+.discover-description {
+ text-align: justify;
+ margin: 0 0 15px 0;
+}
+
+.discover-footer {
+ text-align: justify;
+}
+
+
+/*** list ***/
+
+.list {
+ -moz-appearance: none;
+ margin: 0;
+ border: none;
+ background-color: transparent;
+}
+
+.addon {
+ border-bottom: 1px solid #B6B1B9;
+ padding: 5px;
+ color: #373D48;
+}
+
+.details {
+ cursor: pointer;
+ margin: 0;
+ -moz-margin-start: 10px;
+}
+
+.icon-container {
+ width: 48px;
+ height: 48px;
+ margin: 3px 7px;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+.icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.png");
+ max-width: 48px;
+ max-height: 48px;
+}
+
+.addon[active="false"] .icon {
+ filter: grayscale(1);
+}
+
+.addon-view[type="theme"] .icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
+.addon-view[type="locale"] .icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
+}
+
+.addon-view[type="plugin"] .icon {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
+}
+
+.addon-view[type="dictionary"] .icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
+}
+
+.addon-view[type="experiment"] .icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/experimentGeneric.png");
+}
+
+.name-container {
+ font-size: 150%;
+ margin-bottom: 0;
+ font-weight: bold;
+ color: #000;
+ text-shadow: @loweredShadow@;
+ -moz-box-align: end;
+ -moz-box-flex: 1;
+}
+
+.creator {
+ font-weight: bold;
+}
+
+.creator .text-link {
+ color: #0066CC;
+}
+
+.description-container {
+ margin-top: 8px;
+ -moz-margin-start: 6px;
+ -moz-box-align: center;
+}
+
+.description {
+ margin: 0;
+}
+
+.warning,
+.pending,
+.error {
+ -moz-margin-start: 48px;
+ font-weight: bold;
+ text-shadow: @loweredShadow@;
+ -moz-box-align: center;
+}
+
+.content-container,
+.basicinfo-container {
+ -moz-box-align: start;
+}
+
+.addon[status="installing"] > .content-container {
+ -moz-box-align: stretch;
+}
+
+.update-info-container {
+ -moz-box-align: center;
+}
+
+.advancedinfo-container,
+.update-available {
+ -moz-box-align: end;
+}
+
+.install-status-container {
+ -moz-box-pack: end;
+ -moz-box-align: end;
+}
+
+.name-outer-container {
+ -moz-box-pack: center;
+}
+
+.relnotes-toggle-container,
+.icon-outer-container {
+ -moz-box-pack: start;
+}
+
+.status-container,
+.control-container {
+ -moz-box-pack: end;
+}
+
+.addon-view .warning {
+ color: #916D15;
+}
+
+.addon-view .error {
+ color: #864441;
+}
+
+.addon-view .pending {
+ color: #1B7123;
+}
+
+.addon-view[pending="disable"] .pending,
+.addon-view[pending="uninstall"] .pending {
+ color: #62666E;
+}
+
+.addon-view[notification="warning"] {
+ background-image: linear-gradient(rgba(255, 255, 0, 0.2), rgba(255, 255, 0, 0.1));
+}
+
+.addon-view[notification="error"] {
+ background-image: linear-gradient(rgba(255, 0, 0, 0.2), rgba(255, 0, 0, 0.1));
+}
+
+.addon-view[notification="info"] {
+ background-image: linear-gradient(rgba(0, 0, 255, 0.2), rgba(0, 0, 255, 0.1));
+}
+
+.addon-view[pending="enable"],
+.addon-view[pending="upgrade"],
+.addon-view[pending="install"] {
+ background-image: linear-gradient(rgba(0, 255, 0, 0.2), rgba(0, 255, 0, 0.1));
+}
+
+.addon-view[pending="disable"],
+.addon-view[pending="uninstall"] {
+ background-image: linear-gradient(rgba(128, 128, 128, 0.2), rgba(128, 128, 128, 0.1));
+}
+
+.addon .relnotes-container {
+ -moz-box-align: start;
+ height: 0;
+ overflow: hidden;
+ opacity: 0;
+ transition-property: height, opacity;
+ transition-duration: 0.5s, 0.5s;
+}
+
+.addon[show-relnotes] .relnotes-container {
+ opacity: 1;
+ transition-property: height, opacity;
+ transition-duration: 0.5s, 0.5s;
+}
+
+.addon .relnotes-header {
+ font-weight: bold;
+ margin: 10px 0;
+}
+
+.addon .relnotes-toggle {
+ -moz-appearance: none;
+ border: none;
+ background: transparent;
+ font-weight: bold;
+ -moz-box-direction: reverse;
+ cursor: pointer;
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+
+.addon .relnotes-toggle > .button-box > .button-icon {
+ -moz-padding-start: 4px;
+}
+
+.addon[show-relnotes] .relnotes-toggle {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+}
+
+.addon[active="false"] {
+ background-color: rgba(135, 135, 135, 0.1);
+ background-image: linear-gradient(rgba(135, 135, 135, 0),
+ rgba(135, 135, 135, 0.1));
+}
+
+.addon-view[active="false"],
+.addon-view[active="false"] .name-container {
+ color: #686A6B;
+}
+
+.addon-view[notification="warning"] {
+ background-image: url("chrome://mozapps/skin/extensions/stripes-warning.png"),
+ linear-gradient(rgba(255, 255, 0, 0.04),
+ rgba(255, 255, 0, 0));
+ background-repeat: repeat-x;
+}
+
+.addon-view[notification="warning"][native="false"] {
+ background-image: url("chrome://mozapps/skin/extensions/stripes-compatibility.png"),
+ linear-gradient(rgba(255, 128, 0, 0.04),
+ rgba(255, 128, 0, 0));
+ background-repeat: repeat-x;
+}
+
+.addon-view[notification="error"] {
+ background-image: url("chrome://mozapps/skin/extensions/stripes-error.png"),
+ linear-gradient(rgba(255, 0, 0, 0.04),
+ rgba(255, 0, 0, 0));
+ background-repeat: repeat-x;
+}
+
+.addon-view[pending="enable"],
+.addon-view[pending="upgrade"],
+.addon-view[pending="install"] {
+ background-image: url("chrome://mozapps/skin/extensions/stripes-info-positive.png"),
+ linear-gradient(rgba(0, 255, 0, 0.04),
+ rgba(0, 255, 0, 0));
+ background-repeat: repeat-x;
+}
+
+.addon-view[pending="disable"],
+.addon-view[pending="uninstall"] {
+ background-image: url("chrome://mozapps/skin/extensions/stripes-info-negative.png"),
+ linear-gradient(rgba(128, 128, 128, 0.04),
+ rgba(128, 128, 128, 0));
+ background-repeat: repeat-x;
+}
+
+.addon[selected] {
+ background-color: rgba(105, 125, 149, 0.39);
+ color: black;
+}
+
+.addon[selected] .name-container {
+ text-shadow: @loweredShadow@;
+}
+
+.addon[active="false"][selected] .name-container {
+ color: #3F3F3F;
+}
+
+
+/*** search view ***/
+
+#search-filter {
+ padding: 5px 20px;
+ font-size: 120%;
+ overflow-x: hidden;
+ border-bottom: 1px solid rgba(50, 65, 92, 0.4);
+}
+
+#search-filter-label {
+ font-weight: bold;
+ color: #666;
+}
+
+.search-filter-radio {
+ -moz-appearance: none;
+ padding: 0 10px;
+ margin: 0 3px;
+ border-radius: 10000px;
+}
+
+.search-filter-radio[selected] {
+ text-shadow: @loweredShadow@;
+ background-color: #C0C3CB;
+ box-shadow: inset #A3A6AC 0 1px 1px, @loweredShadow@;
+}
+
+.search-filter-radio:hover {
+ text-shadow: @loweredShadow@;
+ background-color: #C0C3CB;
+}
+
+.search-filter-radio .radio-check {
+ display: none;
+}
+
+.search-filter-radio .radio-icon {
+ display: none;
+}
+
+#search-allresults-link {
+ margin-top: 1em;
+ margin-bottom: 2em;
+}
+
+/*** detail view ***/
+
+#detail-view .loading {
+ opacity: 0;
+}
+
+#detail-view[loading-extended] .loading {
+ opacity: 1;
+ transition-property: opacity;
+ transition-duration: 1s;
+}
+
+.detail-view-container {
+ padding: 0 2em 2em 2em;
+ font-size: 110%;
+}
+
+#detail-notifications {
+ margin-top: 1em;
+ margin-bottom: 2em;
+}
+
+#detail-notifications .warning,
+#detail-notifications .pending,
+#detail-notifications .error {
+ -moz-margin-start: 0;
+}
+
+#detail-icon-container {
+ width: 64px;
+ -moz-margin-end: 10px;
+ margin-top: 6px;
+}
+
+#detail-icon {
+ max-width: 64px;
+ max-height: 64px;
+}
+
+#detail-summary {
+ margin-bottom: 2em;
+}
+
+#detail-name-container {
+ font-size: 200%;
+}
+
+#detail-screenshot {
+ -moz-margin-end: 2em;
+ max-width: 300px;
+ max-height: 300px;
+}
+
+#detail-screenshot[loading] {
+ background-image: url("chrome://global/skin/icons/loading_16.png"),
+ linear-gradient(rgba(255, 255, 255, 0.5), transparent);
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ border-radius: 3px;
+}
+
+#detail-screenshot[loading="error"] {
+ background-image: url("chrome://global/skin/media/error.png"),
+ linear-gradient(rgba(255, 255, 255, 0.5), transparent);
+}
+
+#detail-desc-container {
+ margin-bottom: 2em;
+}
+
+#detail-desc, #detail-fulldesc {
+ -moz-margin-start: 6px;
+ /* This is necessary to fix layout issues with multi-line descriptions, see
+ bug 592712*/
+ outline: solid transparent;
+ white-space: pre-wrap;
+ min-width: 10em;
+}
+
+#detail-fulldesc {
+ margin-top: 1em;
+}
+
+#detail-contributions {
+ border-radius: 5px;
+ border: 1px solid rgba(50, 65, 92, 0.3);
+ margin-bottom: 2em;
+ padding: 1em;
+ background-color: rgba(255, 255, 255, 0.35);
+}
+
+#detail-contrib-description {
+ font-style: italic;
+ margin-bottom: 1em;
+ color: #373D48;
+}
+
+#detail-contrib-suggested {
+ color: grey;
+ font-weight: bold;
+}
+
+#detail-contrib-btn {
+ -moz-appearance: none;
+ color: #FFF;
+ border: 1px solid #3A4EEE;
+ border-radius: 3px;
+ list-style-image: url("chrome://mozapps/skin/extensions/heart.png");
+ background-color: #2F73EF;
+ background-image: linear-gradient(rgba(251, 252, 253, 0.70), rgba(246, 247, 248, 0.27) 49%,
+ rgba(231, 232, 233, 0.25) 51%, rgba(225, 226, 229, 0.1));
+}
+
+#detail-contrib-btn .button-box {
+ padding: 0 6px 1px 6px;
+}
+
+#detail-contrib-btn .button-icon {
+ -moz-margin-end: 3px;
+}
+
+#detail-contrib-btn:not(:active):hover {
+ border-color: #4271FF;
+ background-color: #0459F7;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1),
+ 0 0 3.5px hsl(190, 90%, 80%);
+ transition: background-color .4s ease-in,
+ border-color .3s ease-in,
+ box-shadow .3s ease-in;
+}
+
+#detail-contrib-btn:active:hover {
+ background-color: #8FA1C1;
+ border-color: rgba(0, 0, 0, 0.65) rgba(0, 0, 0, 0.55) rgba(0, 0, 0, 0.5);
+ box-shadow: 0 0 6.5px rgba(0, 0, 0, 0.4) inset,
+ 0 0 2px rgba(0, 0, 0, 0.4) inset;
+}
+
+#detail-grid {
+ margin-bottom: 2em;
+}
+
+#detail-grid > columns > column:first-child {
+ min-width: 15em;
+ max-width: 25em;
+}
+
+.detail-row[first-row="true"],
+.detail-row-complex[first-row="true"],
+setting[first-row="true"] {
+ border-top: none;
+}
+
+.detail-row,
+.detail-row-complex,
+setting {
+ border-top: 2px solid;
+ -moz-border-top-colors: rgba(28, 31, 37, 0.2) rgba(255, 255, 255, 0.2);
+ -moz-box-align: center;
+ min-height: 30px;
+}
+
+#detail-controls {
+ margin-bottom: 1em;
+}
+
+#detail-view[active="false"]:not([pending]):not([notification]) {
+ background-image: linear-gradient(rgba(135, 135, 135, 0.1),
+ rgba(135, 135, 135, 0));
+}
+
+setting[first-row="true"] {
+ margin-top: 2em;
+}
+
+setting {
+ -moz-box-align: start;
+}
+
+.preferences-alignment {
+ min-height: 30px;
+ -moz-box-align: center;
+}
+
+.preferences-description {
+ font-size: 90.9%;
+ color: graytext;
+ margin-top: -2px;
+ -moz-margin-start: 2em;
+ white-space: pre-wrap;
+}
+
+.preferences-description:empty {
+ display: none;
+}
+
+setting[type="radio"] > radiogroup {
+ -moz-box-orient: horizontal;
+}
+
+
+/*** creator ***/
+
+.creator > label {
+ -moz-margin-start: 0;
+ -moz-margin-end: 0;
+}
+
+.creator > .text-link {
+ margin-top: 1px;
+ margin-bottom: 1px;
+}
+
+
+/*** rating ***/
+
+.meta-rating {
+ -moz-margin-end: 0;
+ margin-top: 2px;
+}
+
+.meta-rating > .star {
+ list-style-image: url("chrome://mozapps/skin/extensions/rating-not-won.png");
+ padding: 0 1px;
+}
+
+.meta-rating > .star[on="true"] {
+ list-style-image: url("chrome://mozapps/skin/extensions/rating-won.png");
+}
+
+
+/*** download progress ***/
+
+.download-progress {
+ background-image: linear-gradient(#DCDEE3, #CBCED6);
+ border: 1px solid #858898;
+ border-radius: 3px;
+ box-shadow: inset #E3E8EC 0 1px 1px, @loweredShadow@;
+ width: 200px;
+ height: 21px;
+ margin: 0 8px;
+}
+
+.download-progress[mode="undetermined"] .progress {
+ -moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter-undetermined");
+}
+
+.download-progress[mode="undetermined"] {
+ border-color: #2E773A;
+}
+
+.download-progress[mode="undetermined"] .status-container {
+ padding: 0 2px;
+}
+
+.download-progress .start-cap,
+.download-progress[complete] .end-cap,
+.download-progress[mode="undetermined"] .end-cap,
+.download-progress .progress .progress-bar {
+ -moz-appearance: none;
+ background-image: linear-gradient(#6AC47E, #4FAC6A);
+ margin-top: -1px;
+ margin-bottom: -1px;
+ border: 1px solid #2E773A;
+}
+
+.download-progress .start-cap {
+ -moz-margin-start: -1px;
+ -moz-border-end-width: 0;
+}
+
+.download-progress .end-cap {
+ -moz-margin-end: -1px;
+ -moz-border-start-width: 0px !important;
+}
+
+.download-progress .progress .progress-bar {
+ border-left-width: 0;
+ border-right-width: 0;
+ min-height: 21px;
+}
+
+.download-progress .progress {
+ -moz-appearance: none;
+ background-color: transparent;
+ padding: 0;
+ margin: 0;
+ border: none;
+}
+
+.download-progress .start-cap,
+.download-progress .end-cap {
+ width: 4px;
+}
+
+.download-progress .start-cap:-moz-locale-dir(ltr),
+.download-progress .end-cap:-moz-locale-dir(rtl) {
+ border-radius: 3px 0 0 3px;
+}
+
+.download-progress .end-cap:-moz-locale-dir(ltr),
+.download-progress .start-cap:-moz-locale-dir(rtl) {
+ border-radius: 0 3px 3px 0;
+}
+
+.download-progress .cancel {
+ -moz-appearance: none;
+ background-color: rgba(255, 255, 255, 0.15);
+ border: 1px solid rgba(0, 0, 0, 0.4);
+ padding: 3px;
+ border-radius: 3px;
+ min-width: 0;
+ margin: 3px;
+}
+
+.download-progress .cancel .button-text {
+ display: none;
+}
+
+.download-progress .cancel .button-icon {
+ -moz-margin-start: 0;
+}
+
+.download-progress .cancel {
+ list-style-image: url('chrome://mozapps/skin/extensions/cancel.png');
+}
+
+.download-progress .status-container {
+ -moz-box-align: center;
+}
+
+.download-progress .status {
+ text-shadow: @loweredShadow@;
+}
+
+
+/*** install status ***/
+
+.install-status {
+ -moz-box-align: center;
+}
+
+
+/*** check for updates ***/
+
+#updates-container {
+ -moz-box-align: center;
+}
+
+#updates-installed,
+#updates-downloaded {
+ color: #3C735C;
+ font-weight: bold;
+}
+
+#update-selected {
+ margin: 12px;
+}
+
+
+/*** buttons ***/
+
+.addon-control[disabled="true"]:not(.no-auto-hide) {
+ display: none;
+}
+
+.no-auto-hide .addon-control {
+ display: block !important;
+}
+
+.no-auto-hide > .menulist-dropmarker {
+ -moz-padding-start: 0px !important;
+}
+
+button.button-link {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ text-decoration: underline;
+ color: #0066CC;
+ cursor: pointer;
+ min-width: 0;
+ margin: 0 6px;
+}
+
+.text-link {
+ color: #3386D5;
+}
+
+.button-link:hover,
+.text-link:hover {
+ color: #3DA1FF;
+}
+
+/* Needed to override normal button style from inContent.css */
+button.button-link:not([disabled="true"]):active:hover {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+}
+
+.header-button {
+ -moz-appearance: none;
+ padding: 0 4px;
+ margin: 0;
+ height: 22px;
+ border: 1px solid rgba(60,73,97,0.5);
+ border-radius: @toolbarbuttonCornerRadius@;
+ box-shadow: inset 0 1px rgba(255,255,255,0.25), 0 1px rgba(255,255,255,0.25);
+ background: linear-gradient(rgba(255,255,255,0.45), transparent);
+ background-clip: padding-box;
+}
+
+.header-button .toolbarbutton-text {
+ display: none;
+}
+
+.header-button[disabled="true"] .toolbarbutton-icon {
+ opacity: 0.4;
+}
+
+.header-button:not([disabled="true"]):active:hover,
+.header-button[open="true"] {
+ border-color: rgba(45,54,71,0.7);
+ box-shadow: inset 0 0 4px rgb(45,54,71), 0 1px rgba(255,255,255,0.25);
+ background-image: linear-gradient(rgba(45,54,71,0.6), transparent);
+}
+
+/*** telemetry experiments ***/
+
+#detail-experiment-container {
+ font-size: 80%;
+ margin-bottom: 1em;
+}
+
+#detail-experiment-bullet-container,
+#detail-experiment-state,
+#detail-experiment-time,
+.experiment-bullet-container,
+.experiment-state,
+.experiment-time {
+ vertical-align: middle;
+ display: inline-block;
+}
+
+.addon .experiment-bullet,
+#detail-experiment-bullet {
+ fill: rgb(158, 158, 158);
+}
+
+.addon[active="true"] .experiment-bullet,
+#detail-view[active="true"] #detail-experiment-bullet {
+ fill: rgb(106, 201, 20);
+}
diff --git a/toolkit/themes/osx/mozapps/extensions/heart.png b/toolkit/themes/osx/mozapps/extensions/heart.png
new file mode 100644
index 0000000000..655f4c4be7
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/heart.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/localeGeneric.png b/toolkit/themes/osx/mozapps/extensions/localeGeneric.png
new file mode 100644
index 0000000000..4d9ac5ad89
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/localeGeneric.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/navigation.png b/toolkit/themes/osx/mozapps/extensions/navigation.png
new file mode 100644
index 0000000000..ffc40d7e56
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/navigation.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/newaddon.css b/toolkit/themes/osx/mozapps/extensions/newaddon.css
new file mode 100644
index 0000000000..5bf04fab1d
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/newaddon.css
@@ -0,0 +1,112 @@
+/* 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 ../../global/shared.inc
+
+@import url("chrome://global/skin/inContentUI.css");
+
+#addon-page {
+ padding: 0;
+}
+
+#addon-scrollbox {
+ overflow: auto;
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+}
+
+#spacer-start {
+ -moz-box-flex: 1;
+}
+
+#spacer-end {
+ -moz-box-flex: 3;
+}
+
+#addon-container {
+ overflow: visible;
+ max-width: 600px;
+ margin: 20px;
+ padding: 30px 90px;
+}
+
+#addon-info {
+ -moz-box-align: start;
+ margin: 25px 10px;
+}
+
+#icon {
+ -moz-margin-end: 10px;
+ max-width: 64px;
+ max-height: 64px;
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.png");
+}
+
+.addon-info[type="theme"] #icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
+.addon-info[type="locale"] #icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
+}
+
+.addon-info[type="plugin"] #icon {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
+}
+
+.addon-info[type="dictionary"] #icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
+}
+
+#name {
+ font-size: 130%;
+}
+
+#author {
+ color: GrayText;
+}
+
+#location {
+ color: GrayText;
+}
+
+#warning {
+ margin-bottom: 25px;
+ -moz-box-align: start;
+}
+
+#warning-icon {
+ list-style-image: url("chrome://mozapps/skin/extensions/alerticon-warning.png");
+ width: 16px;
+ height: 15px;
+ -moz-margin-end: 5px;
+}
+
+#allow {
+ -moz-margin-start: 84px;
+ margin-bottom: 20px;
+}
+
+#continuePanel,
+#restartPanel {
+ margin-top: 25px;
+ -moz-box-align: center;
+ -moz-box-pack: end;
+}
+
+#continuePanel {
+ -moz-box-pack: end;
+}
+
+#restartMessage {
+ text-align: right;
+}
+
+#restartSpacer {
+ -moz-box-flex: 1;
+}
+
+#later {
+ color: GrayText;
+}
diff --git a/toolkit/themes/osx/mozapps/extensions/rating-not-won.png b/toolkit/themes/osx/mozapps/extensions/rating-not-won.png
new file mode 100644
index 0000000000..2761f19255
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/rating-not-won.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/rating-won.png b/toolkit/themes/osx/mozapps/extensions/rating-won.png
new file mode 100644
index 0000000000..336dd8f6eb
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/rating-won.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/search.png b/toolkit/themes/osx/mozapps/extensions/search.png
new file mode 100644
index 0000000000..93196dbbf6
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/search.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/selectAddons.css b/toolkit/themes/osx/mozapps/extensions/selectAddons.css
new file mode 100644
index 0000000000..8682b04b51
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/selectAddons.css
@@ -0,0 +1,163 @@
+/* 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 ../../global/shared.inc
+
+.heading {
+ font-size: 270%;
+ text-align: center;
+ margin: 0 120px;
+}
+
+.progress {
+ margin: 10px 128px;
+}
+
+.progress-label,
+#errors-description {
+ text-align: center;
+ margin: 0 10px;
+}
+
+#checking-heading,
+#update-heading,
+#errors-heading {
+ margin-top: 90px;
+}
+
+#select-heading,
+#confirm-heading {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ text-align: center;
+}
+
+#select-description,
+#confirm-description {
+ margin: 10px;
+}
+
+#select-list {
+ border: 1px solid WindowFrame;
+ background-color: Window;
+ margin: 10px;
+}
+
+#select-grid column {
+ -moz-box-align: center;
+}
+
+#select-grid row {
+ -moz-box-align: stretch;
+}
+
+#select-grid row:nth-of-type(odd) {
+ background-color: -moz-oddtreerow;
+}
+
+#select-grid label,
+#select-grid checkbox {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.select-cell {
+ -moz-box-align: center;
+ -moz-box-pack: start;
+ box-sizing: border-box;
+}
+
+#select-header {
+ background-color: Window !important;
+}
+
+#select-header .select-cell {
+ -moz-appearance: treeheadercell;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
+ -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+.select-keep {
+ -moz-box-pack: center;
+}
+
+.select-icon {
+ width: 20px;
+}
+
+#select-grid separator {
+ display: none;
+}
+
+.addon-name,
+.addon-action-message,
+.addon-action-update {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 2px 6px;
+}
+
+.addon:not([active]) .addon-name,
+.addon:not([active]) .addon-action-message,
+.addon:not([active]) .addon-action-update {
+ color: GrayText;
+}
+
+.addon-icon {
+ height: 16px;
+ width: 16px;
+ margin: 2px;
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
+}
+
+.addon-icon[type="theme"] {
+ list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric-16.png");
+}
+
+.addon-icon[type="plugin"] {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
+}
+
+.addon-icon[type="dictionary"] {
+ list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric-16.png");
+}
+
+.action-list {
+ margin-top: 10px;
+ -moz-margin-start: 5em;
+}
+
+.action-header {
+ margin-bottom: 10px;
+}
+
+#confirm .addon {
+ -moz-margin-start: 3em;
+ -moz-box-align: center;
+}
+
+.addon:not([active]) .addon-icon,
+#disable-list .addon-icon,
+#incompatible-list .addon-icon {
+ filter: grayscale(1);
+}
+
+#footer {
+ padding: 15px 12px;
+ -moz-appearance: statusbar;
+ -moz-window-dragging: drag;
+}
+
+button {
+ -moz-appearance: toolbarbutton;
+ min-height: 22px;
+ margin: 0 6px;
+ padding: 0;
+ text-shadow: @loweredShadow@;
+}
diff --git a/toolkit/themes/osx/mozapps/extensions/stripes-compatibility.png b/toolkit/themes/osx/mozapps/extensions/stripes-compatibility.png
new file mode 100644
index 0000000000..dee75516b7
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/stripes-compatibility.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/stripes-error.png b/toolkit/themes/osx/mozapps/extensions/stripes-error.png
new file mode 100644
index 0000000000..1dc2d8504c
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/stripes-error.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/stripes-info-negative.png b/toolkit/themes/osx/mozapps/extensions/stripes-info-negative.png
new file mode 100644
index 0000000000..901ab1ec29
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/stripes-info-negative.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/stripes-info-positive.png b/toolkit/themes/osx/mozapps/extensions/stripes-info-positive.png
new file mode 100644
index 0000000000..370ceec0f2
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/stripes-info-positive.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/stripes-warning.png b/toolkit/themes/osx/mozapps/extensions/stripes-warning.png
new file mode 100644
index 0000000000..69463fb1af
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/stripes-warning.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png b/toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png
new file mode 100644
index 0000000000..190bb30d71
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/themeGeneric.png b/toolkit/themes/osx/mozapps/extensions/themeGeneric.png
new file mode 100644
index 0000000000..be645f76df
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/themeGeneric.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png b/toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png
new file mode 100644
index 0000000000..e7674c62a6
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/extensions/update.css b/toolkit/themes/osx/mozapps/extensions/update.css
new file mode 100644
index 0000000000..8e92556913
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/update.css
@@ -0,0 +1,26 @@
+/* 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/. */
+
+#alert {
+ list-style-image: url("chrome://mozapps/skin/update/warning.gif");
+}
+
+.throbber {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+ width: 16px;
+ height: 16px;
+ margin-top: 5px;
+ margin-bottom: 5px;
+ -moz-margin-start: 5px;
+ -moz-margin-end: 2px;
+}
+
+.alertBox {
+ background-color: InfoBackground;
+ color: InfoText;
+ border: 1px outset InfoBackground;
+ margin-left: 3px;
+ margin-right: 3px;
+ padding: 5px;
+}
diff --git a/toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css b/toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css
new file mode 100644
index 0000000000..0a1a84b249
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css
@@ -0,0 +1,90 @@
+/* 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/. */
+
+#xpinstallheader {
+ margin-bottom: 2em;
+}
+
+.alert-icon {
+ width: 48px;
+ height: 48px;
+ list-style-image: url("chrome://global/skin/icons/warning-large.png");
+ margin-top: 0 !important;
+ margin-bottom: 6px !important;
+ -moz-margin-start: 6px !important;
+ -moz-margin-end: 20px !important;
+}
+
+#itemList {
+ -moz-appearance: listbox;
+ margin: 3px 4px 10px 4px;
+ height: 14em;
+}
+
+#itemWarningIntro {
+ -moz-margin-start: 8px;
+}
+
+#dialogContentBox {
+ padding: 5px;
+}
+
+installitem {
+ padding: 5px 0 5px 5px;
+ border-bottom: 1px dotted #C0C0C0;
+ margin-bottom: 5px;
+}
+
+.warning {
+ font-weight: bold;
+ font-size: 1.25em;
+ margin-bottom: 1em;
+}
+
+.xpinstallIconContainer {
+ width: 32px;
+ height: 32px;
+ -moz-margin-end: 5px;
+}
+
+.xpinstallItemName {
+ font-weight: bold;
+}
+
+.xpinstallItemSigned {
+ font-style: italic;
+ font-size: 0.9em;
+}
+
+.xpinstallItemURL {
+ -moz-appearance: none;
+ border: none;
+ background-color: Window;
+ margin-top: 2px;
+ margin-bottom: 1px;
+ -moz-margin-start: 6px;
+ -moz-margin-end: 5px;
+}
+
+.xpinstallItemIcon {
+ max-width: 32px;
+ max-height: 32px;
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.png");
+}
+
+installitem[type="theme"] .xpinstallItemIcon {
+ list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric.png");
+}
+
+installitem[type="locale"] .xpinstallItemIcon {
+ list-style-image: url("chrome://mozapps/skin/extensions/localeGeneric.png");
+}
+
+installitem[type="plugin"] .xpinstallItemIcon {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric.png");
+}
+
+installitem[type="dictionary"] .xpinstallItemIcon {
+ list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric.png");
+}
diff --git a/toolkit/themes/osx/mozapps/handling/handling.css b/toolkit/themes/osx/mozapps/handling/handling.css
new file mode 100644
index 0000000000..9598bedaed
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/handling/handling.css
@@ -0,0 +1,30 @@
+/* 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/. */
+
+#description-image:not([src]) {
+ height: 32px;
+ width: 32px;
+}
+
+richlistitem[type] {
+ min-height: 36px; /* Don't forget to update the richlistbox height! */
+ padding-inline-start: 2px;
+ }
+
+richlistitem {
+ -moz-box-align: center;
+}
+
+richlistbox {
+ /* 3 items high, plus 4px for top and bottom margins, less 2px for border */
+ min-height: 110px;
+}
+
+.name {
+ font-weight: bold;
+}
+
+.description {
+ color: GrayText;
+}
diff --git a/toolkit/themes/osx/mozapps/jar.mn b/toolkit/themes/osx/mozapps/jar.mn
new file mode 100644
index 0000000000..eabd2eddb5
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/jar.mn
@@ -0,0 +1,79 @@
+# 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/.
+
+toolkit.jar:
+#include ../../shared/mozapps.inc.mn
+ skin/classic/mozapps/downloads/buttons.png (downloads/buttons.png)
+ skin/classic/mozapps/downloads/downloadIcon.png (downloads/downloadIcon.png)
+* skin/classic/mozapps/downloads/downloads.css (downloads/downloads.css)
+ skin/classic/mozapps/downloads/unknownContentType.css (downloads/unknownContentType.css)
+ skin/classic/mozapps/extensions/category-search.png (extensions/category-search.png)
+ skin/classic/mozapps/extensions/category-discover.png (extensions/category-discover.png)
+ skin/classic/mozapps/extensions/category-languages.png (extensions/localeGeneric.png)
+ skin/classic/mozapps/extensions/category-searchengines.png (extensions/category-searchengines.png)
+ skin/classic/mozapps/extensions/category-extensions.png (extensions/extensionGeneric.png)
+ skin/classic/mozapps/extensions/category-themes.png (extensions/themeGeneric.png)
+ skin/classic/mozapps/extensions/category-plugins.png (extensions/category-plugins.png)
+ skin/classic/mozapps/extensions/category-service.png (extensions/category-service.png)
+ skin/classic/mozapps/extensions/category-dictionaries.png (extensions/category-dictionaries.png)
+ skin/classic/mozapps/extensions/category-experiments.png (extensions/category-experiments.png)
+ skin/classic/mozapps/extensions/category-recent.png (extensions/category-recent.png)
+ skin/classic/mozapps/extensions/category-available.png (extensions/category-available.png)
+ skin/classic/mozapps/extensions/discover-logo.png (extensions/discover-logo.png)
+ skin/classic/mozapps/extensions/extensionGeneric.png (extensions/extensionGeneric.png)
+ skin/classic/mozapps/extensions/extensionGeneric-16.png (extensions/extensionGeneric-16.png)
+ skin/classic/mozapps/extensions/themeGeneric.png (extensions/themeGeneric.png)
+ skin/classic/mozapps/extensions/themeGeneric-16.png (extensions/themeGeneric-16.png)
+ skin/classic/mozapps/extensions/dictionaryGeneric.png (extensions/dictionaryGeneric.png)
+ skin/classic/mozapps/extensions/dictionaryGeneric-16.png (extensions/dictionaryGeneric-16.png)
+ skin/classic/mozapps/extensions/experimentGeneric.png (extensions/experimentGeneric.png)
+ skin/classic/mozapps/extensions/localeGeneric.png (extensions/localeGeneric.png)
+ skin/classic/mozapps/extensions/rating-won.png (extensions/rating-won.png)
+ skin/classic/mozapps/extensions/rating-not-won.png (extensions/rating-not-won.png)
+ skin/classic/mozapps/extensions/cancel.png (extensions/cancel.png)
+ skin/classic/mozapps/extensions/utilities.svg (../../shared/extensions/utilities.svg)
+ skin/classic/mozapps/extensions/toolbarbutton-dropmarker.png (extensions/toolbarbutton-dropmarker.png)
+ skin/classic/mozapps/extensions/heart.png (extensions/heart.png)
+ skin/classic/mozapps/extensions/navigation.png (extensions/navigation.png)
+ skin/classic/mozapps/extensions/stripes-warning.png (extensions/stripes-warning.png)
+ skin/classic/mozapps/extensions/stripes-error.png (extensions/stripes-error.png)
+ skin/classic/mozapps/extensions/stripes-info-positive.png (extensions/stripes-info-positive.png)
+ skin/classic/mozapps/extensions/stripes-info-negative.png (extensions/stripes-info-negative.png)
+ skin/classic/mozapps/extensions/alerticon-warning.png (extensions/alerticon-warning.png)
+ skin/classic/mozapps/extensions/alerticon-error.png (extensions/alerticon-error.png)
+ skin/classic/mozapps/extensions/alerticon-info-positive.png (extensions/alerticon-info-positive.png)
+ skin/classic/mozapps/extensions/alerticon-info-negative.png (extensions/alerticon-info-negative.png)
+ skin/classic/mozapps/extensions/search.png (extensions/search.png)
+ skin/classic/mozapps/extensions/about.css (extensions/about.css)
+* skin/classic/mozapps/extensions/extensions.css (extensions/extensions.css)
+* skin/classic/mozapps/extensions/selectAddons.css (extensions/selectAddons.css)
+ skin/classic/mozapps/extensions/update.css (extensions/update.css)
+ skin/classic/mozapps/extensions/eula.css (extensions/eula.css)
+ skin/classic/mozapps/extensions/blocklist.css (extensions/blocklist.css)
+* skin/classic/mozapps/extensions/newaddon.css (extensions/newaddon.css)
+ skin/classic/mozapps/passwordmgr/key.png (passwordmgr/key.png)
+ skin/classic/mozapps/passwordmgr/key-16.png (passwordmgr/key-16.png)
+ skin/classic/mozapps/passwordmgr/key-64.png (passwordmgr/key-64.png)
+ skin/classic/mozapps/plugins/notifyPluginGeneric.png (plugins/notifyPluginGeneric.png)
+ skin/classic/mozapps/plugins/pluginGeneric.png (plugins/pluginGeneric.png)
+ skin/classic/mozapps/plugins/pluginBlocked.png (plugins/pluginBlocked.png)
+ skin/classic/mozapps/plugins/pluginBlocked-64.png (plugins/pluginBlocked-64.png)
+ skin/classic/mozapps/plugins/pluginGeneric-16.png (plugins/pluginGeneric-16.png)
+ skin/classic/mozapps/plugins/pluginHelp-16.png (plugins/pluginHelp-16.png)
+ skin/classic/mozapps/profile/profileicon.png (profile/profileicon.png)
+ skin/classic/mozapps/profile/profileSelection.css (profile/profileSelection.css)
+ skin/classic/mozapps/profile/profileicon-selected.png (profile/profileicon-selected.png)
+ skin/classic/mozapps/update/buttons.png (update/buttons.png)
+* skin/classic/mozapps/update/updates.css (update/updates.css)
+ skin/classic/mozapps/viewsource/viewsource.css (viewsource/viewsource.css)
+ skin/classic/mozapps/xpinstall/xpinstallItemGeneric.png (extensions/extensionGeneric.png)
+ skin/classic/mozapps/xpinstall/xpinstallConfirm.css (extensions/xpinstallConfirm.css)
+ skin/classic/mozapps/handling/handling.css (handling/handling.css)
+
+#ifdef MOZ_PHOENIX
+[browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
+#elif MOZ_SEPARATE_MANIFEST_FOR_THEME_OVERRIDES
+[extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
+#endif
+% override chrome://mozapps/skin/plugins/notifyPluginCrashed.png chrome://mozapps/skin/plugins/notifyPluginGeneric.png
diff --git a/toolkit/themes/osx/mozapps/moz.build b/toolkit/themes/osx/mozapps/moz.build
new file mode 100644
index 0000000000..635fa39c99
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/toolkit/themes/osx/mozapps/passwordmgr/key-16.png b/toolkit/themes/osx/mozapps/passwordmgr/key-16.png
new file mode 100644
index 0000000000..ac135b847e
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/passwordmgr/key-16.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/passwordmgr/key-64.png b/toolkit/themes/osx/mozapps/passwordmgr/key-64.png
new file mode 100644
index 0000000000..0fb69f3828
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/passwordmgr/key-64.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/passwordmgr/key.png b/toolkit/themes/osx/mozapps/passwordmgr/key.png
new file mode 100644
index 0000000000..b5e8afefca
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/passwordmgr/key.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/plugins/notifyPluginGeneric.png b/toolkit/themes/osx/mozapps/plugins/notifyPluginGeneric.png
new file mode 100644
index 0000000000..449e081496
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/plugins/notifyPluginGeneric.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/plugins/pluginBlocked-64.png b/toolkit/themes/osx/mozapps/plugins/pluginBlocked-64.png
new file mode 100644
index 0000000000..56b8a3322d
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/plugins/pluginBlocked-64.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/plugins/pluginBlocked.png b/toolkit/themes/osx/mozapps/plugins/pluginBlocked.png
new file mode 100644
index 0000000000..6e8e1761bf
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/plugins/pluginBlocked.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/plugins/pluginGeneric-16.png b/toolkit/themes/osx/mozapps/plugins/pluginGeneric-16.png
new file mode 100644
index 0000000000..6956ffef81
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/plugins/pluginGeneric-16.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/plugins/pluginGeneric.png b/toolkit/themes/osx/mozapps/plugins/pluginGeneric.png
new file mode 100644
index 0000000000..6ada1616bb
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/plugins/pluginGeneric.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/plugins/pluginHelp-16.png b/toolkit/themes/osx/mozapps/plugins/pluginHelp-16.png
new file mode 100644
index 0000000000..9a577c08f2
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/plugins/pluginHelp-16.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/profile/profileSelection.css b/toolkit/themes/osx/mozapps/profile/profileSelection.css
new file mode 100644
index 0000000000..cc3ab451c7
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/profile/profileSelection.css
@@ -0,0 +1,29 @@
+/* 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/. */
+
+
+@import url("chrome://global/skin/global.css");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+#profiles > listitem {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
+}
+
+#profiles:focus > listitem[selected="true"] {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon-selected.png");
+}
+
+#profiles > listitem > listcell > image {
+ width: 16px;
+ height: 16px;
+}
+
+box#managebuttons > button {
+ min-width: 8em;
+}
+
+#managebuttons {
+ padding-top: 1em;
+}
diff --git a/toolkit/themes/osx/mozapps/profile/profileicon-selected.png b/toolkit/themes/osx/mozapps/profile/profileicon-selected.png
new file mode 100644
index 0000000000..f3e1f8e110
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/profile/profileicon-selected.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/profile/profileicon.png b/toolkit/themes/osx/mozapps/profile/profileicon.png
new file mode 100644
index 0000000000..f67a43714e
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/profile/profileicon.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/update/buttons.png b/toolkit/themes/osx/mozapps/update/buttons.png
new file mode 100644
index 0000000000..04da26a252
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/update/buttons.png
Binary files differ
diff --git a/toolkit/themes/osx/mozapps/update/updates.css b/toolkit/themes/osx/mozapps/update/updates.css
new file mode 100644
index 0000000000..9bd78ef6f8
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/update/updates.css
@@ -0,0 +1,171 @@
+%include ../../global/shared.inc
+
+/* General */
+/* Specify the size for the wizardpage so the billboard has a fixed size. 3rd
+ party themes should typically specify the same values. */
+wizardpage {
+ height: 360px;
+ width: 700px;
+}
+
+/* Remove margin and padding so the billboard will extend to the edge of the
+ window. 3rd party themes should typically specify the same values. */
+#updates, .wizard-page-box {
+ margin: 0;
+ padding: 0;
+}
+
+.update-content {
+ padding: 6px 12px 12px 12px;
+}
+
+.wizard-header-box-text {
+ padding: 0;
+}
+
+.wizard-header {
+ margin: 12px 12px 0 12px;
+}
+
+.wizard-buttons-btm {
+ padding: 15px 12px;
+}
+
+/* Don't use top margin - it can cause a scrollbar on some pages */
+.wizard-buttons {
+ padding: 0;
+ -moz-appearance: statusbar;
+ -moz-window-dragging: drag;
+}
+
+.wizard-buttons button {
+ -moz-appearance: toolbarbutton;
+ color: ButtonText;
+ min-height: 22px;
+ margin: 0 6px;
+ padding: 0;
+ text-shadow: @loweredShadow@;
+}
+
+#finishedBackgroundMore {
+ margin-bottom: 6px;
+}
+
+.inline-link {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.inline-link:hover {
+ text-decoration: underline;
+}
+
+/* Unsupported Page */
+#unsupportedLabel, #unsupportedLinkLabel {
+ margin-inline-start: 0;
+ padding-inline-start: 0;
+}
+
+/* Update Found Basic Page */
+#updateName, #updateFinishedName {
+ font-weight: bold;
+ font-size: larger;
+}
+
+/* Downloading Page */
+#downloadStatusLine {
+ -moz-box-align: center;
+}
+
+#downloadStatus {
+ height: 3em !important;
+}
+
+#downloadStatusProgress {
+ padding-right: 5px;
+}
+
+#pauseButton {
+ list-style-image: url(chrome://mozapps/skin/update/buttons.png);
+ -moz-image-region: rect(48px, 16px, 64px, 0px);
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+ min-height: 16px;
+ min-width: 16px;
+ max-height: 16px;
+ max-width: 16px;
+ margin: 0 1px 0 1px;
+ padding: 0;
+}
+
+/* !Important must be used otherwise this won't immediately take affect */
+#pauseButton > .button-box {
+ padding: 0 !important;
+}
+
+#pauseButton:hover {
+ -moz-image-region: rect(48px, 32px, 64px, 16px);
+}
+
+#pauseButton:not([disabled="true"]):hover:active {
+ -moz-image-region: rect(48px, 48px, 64px, 32px);
+}
+
+#pauseButton[disabled="true"] {
+ -moz-image-region: rect(48px, 16px, 64px, 0px);
+}
+
+#pauseButton[paused="true"] {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+
+#pauseButton[paused="true"]:hover {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+#pauseButton[paused="true"]:hover:active {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+
+#verificationFailedIcon {
+ margin-left: 5px;
+ list-style-image: url("chrome://global/skin/icons/notfound.png");
+}
+
+/* Error Page */
+#errorReason {
+ margin-top: 1px;
+ margin-bottom: 2px;
+ margin-inline-start: 6px !important;
+ margin-inline-end: 5px;
+ font-weight: bold;
+}
+
+/* Update History Window */
+update {
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+.update-name {
+ font-weight: bold;
+}
+
+.update-label-column {
+ -moz-box-align: end;
+}
+
+.update-type {
+ font-weight: bold;
+ color: #990000;
+}
+
+#historyItems {
+ -moz-appearance: listbox;
+ height: 200px;
+ margin: 1px 5px 4px 5px;
+}
+
+#historyItems > scrollbox {
+ margin-bottom: 1px;
+}
diff --git a/toolkit/themes/osx/mozapps/viewsource/viewsource.css b/toolkit/themes/osx/mozapps/viewsource/viewsource.css
new file mode 100644
index 0000000000..76c7d00b9d
--- /dev/null
+++ b/toolkit/themes/osx/mozapps/viewsource/viewsource.css
@@ -0,0 +1,5 @@
+/* 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/. */
+
+/* This is for styling the menus of the viewsource window */
diff --git a/toolkit/themes/osx/reftests/482681-ref.xul b/toolkit/themes/osx/reftests/482681-ref.xul
new file mode 100644
index 0000000000..62fb4bb8d5
--- /dev/null
+++ b/toolkit/themes/osx/reftests/482681-ref.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="data:text/css,
+vbox { height: 50px; }
+box {
+ -moz-appearance: button;
+}
+" type="text/css"?>
+
+<window title="Reference for mini, small and regular button sizes"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox>
+ <hbox><box width="79" height="16"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox><box width="79" height="19"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox><box width="79" height="22"/></hbox>
+ </vbox>
+</window>
diff --git a/toolkit/themes/osx/reftests/482681.xul b/toolkit/themes/osx/reftests/482681.xul
new file mode 100644
index 0000000000..6cb9aaeae4
--- /dev/null
+++ b/toolkit/themes/osx/reftests/482681.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="data:text/css,
+vbox { height: 50px; }
+button {
+ color: transparent;
+ margin: 0;
+}
+" type="text/css"?>
+
+<window title="Buttons with mini, small and regular control font"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox style="font-size: 9px">
+ <hbox><button label="Mini"/></hbox>
+ </vbox>
+ <vbox style="font: message-box">
+ <hbox><button label="Small"/></hbox>
+ </vbox>
+ <vbox style="font: -moz-dialog">
+ <hbox><button label="Regular"/></hbox>
+ </vbox>
+</window>
diff --git a/toolkit/themes/osx/reftests/baseline.xul b/toolkit/themes/osx/reftests/baseline.xul
new file mode 100644
index 0000000000..2ec6f5132f
--- /dev/null
+++ b/toolkit/themes/osx/reftests/baseline.xul
@@ -0,0 +1,175 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<!--
+ * This is a complicated test.
+ * XUL authors like to place several different widgets on the same line by
+ * putting them in a <hbox align="center">. In order for this to look good,
+ * the baselines of the text contained in the widgets should line up.
+ * This is what this test is testing.
+ * The test passes if it's completely white.
+ *
+ * It works like this:
+ * For every combination of two different widgets (where widget is one of
+ * label, radio, checkbox, button, textbox, menulist, menulist[editable="true"] or
+ * filefield), there's a stack with two layers. The back layer in the stack is
+ * just a vertically centered label with a bunch of underscores. This is the
+ * baseline that the text on the widgets should hit.
+ * On the foreground layer in the stack we've placed the pair of widgets we're
+ * testing. They also have underscores in their labels.
+ *
+ * Now we want to test whether the underscores in the foreground layer are directly
+ * on top of those in the back layer. For that we use color-keying and a tricky
+ * SVG color transformation.
+ * The back layer of the stack has a red background; the underscores of the
+ * back label are in white (and have a white text-shadow in order to fill up the
+ * gaps between the individual letters).
+ * Now we want the foreground layer to be solid white, except for those pixels
+ * that make up the labels: These should be transparent.
+ * So if the baselines line up, everything is white, since at those pixels where
+ * the foreground is transparent, only the white pixels from the back layer shine
+ * through. If the baselines don't line up, red pixels from the background will
+ * shine through, and the comparison with about:blank (completely white) will fail.
+ *
+ * So how do we get the foreground white and transparent? That's the job of the
+ * SVG color transformation filter. It's a simple matrix that makes turns opaque
+ * yellow into transparent and all other colors into white.
+ * -->
+
+<window title="Baseline test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ orient="vertical"
+ class="reftest-wait"
+ onload="loaded()">
+
+<html:style><![CDATA[
+window {
+ -moz-appearance: none;
+ background-color: white;
+}
+.regular {
+ font: -moz-dialog;
+}
+.small {
+ font: message-box;
+}
+.spacer {
+ height: 40px;
+}
+stack > hbox:first-child {
+ background: red;
+ color: white;
+ text-shadow: 5px 0 white, -5px 0 white;
+}
+stack > .foreground {
+ filter: url(#yellow2transparent);
+}
+stack > hbox:last-child > * {
+ color: yellow;
+}
+]]>
+</html:style>
+
+ <svg:svg style="visibility: collapse;">
+ <svg:filter id="yellow2transparent" color-interpolation-filters="sRGB">
+ <svg:feColorMatrix type="matrix"
+ values="0 0 0 0 1
+ 0 0 0 0 1
+ 0 0 0 0 1
+ -100 -100 100 -100 300"/>
+ </svg:filter>
+ </svg:svg>
+
+<script type="application/javascript;version=1.8"><![CDATA[
+
+function cE(elem) {
+ return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", elem);
+}
+function elWithValue(elem, val) {
+ let e = cE(elem);
+ e.setAttribute(elem == "label" || elem == "textbox" ? "value" : "label", val);
+ return e;
+}
+
+function allPairs(set) {
+ let ps = [];
+ for(let i = 0; i < set.length; ++i) {
+ for (let j = i + 1; j < set.length; ++j) {
+ ps.push([set[i], set[j]]);
+ }
+ }
+ return ps;
+}
+
+function createLabel(v) {
+ return elWithValue("label", v);
+}
+function createRadio(v) {
+ return elWithValue("radio", v);
+}
+function createCheckbox(v) {
+ return elWithValue("checkbox", v);
+}
+function createButton(v) {
+ return elWithValue("button", v);
+}
+function createTextField(v) {
+ return elWithValue("textbox", v);
+}
+function createMenulist(v) {
+ let [list, popup, item] = [cE("menulist"), cE("menupopup"), elWithValue("menuitem", v)];
+ item.setAttribute("selected", "true");
+ popup.appendChild(item);
+ list.appendChild(popup);
+ return list;
+}
+function createEditableMenulist(v) {
+ let list = createMenulist(v);
+ list.setAttribute("editable", "true");
+ return list;
+}
+function createFileField(v) {
+ let field = elWithValue("filefield", v);
+ field.setAttribute("image", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAAAXNSR0IArs4c6QAAAChJREFUSMftzUEBAAAEBLCjf2dK8NsKrCaTT51nAoFAIBAIBAKB4MoCtVsCPjrGuiwAAAAASUVORK5CYII=");
+ return field;
+}
+function loaded() {
+ let template = document.getElementById("template");
+ ["regular", "small"].forEach(function(size) {
+ let wrapper = document.querySelectorAll("#wrapper > ." + size)[0];
+ allPairs([
+ createLabel, createRadio, createCheckbox, createButton, createMenulist, createTextField,
+ /* createEditableMenulist, createFileField, */ /* These don't inherit "color" properly */
+ ]).forEach(function(elemList) {
+ let newBox = template.cloneNode(true);
+ newBox.className = "spacer";
+ let foregroundRow = newBox.firstChild.lastChild;
+ elemList.forEach(function(creator) {
+ foregroundRow.appendChild(creator("______"));
+ });
+ wrapper.appendChild(newBox);
+ });
+ });
+ document.documentElement.className = "";
+}
+
+]]></script>
+ <vbox id="template">
+ <stack>
+ <hbox align="center">
+ <label value="______________________________________________"/>
+ </hbox>
+ <hbox align="center" class="foreground">
+ </hbox>
+ </stack>
+ </vbox>
+ <hbox id="wrapper">
+ <vbox class="regular" flex="1"/>
+ <vbox class="small" flex="1"/>
+ </hbox>
+
+ <spacer flex="1"/>
+
+</window>
diff --git a/toolkit/themes/osx/reftests/checkboxsize-ref.xul b/toolkit/themes/osx/reftests/checkboxsize-ref.xul
new file mode 100644
index 0000000000..08d1e9a670
--- /dev/null
+++ b/toolkit/themes/osx/reftests/checkboxsize-ref.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="data:text/css,
+vbox { height: 50px; }
+box {
+ -moz-appearance: checkbox;
+ margin-left: 2px;
+ margin-top: 1px;
+}
+" type="text/css"?>
+
+<window title="Reference for mini, small and regular checkbox sizes"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox>
+ <hbox><box width="11" height="11"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox><box width="13" height="13"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox><box width="16" height="16"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox checked="true"><box width="11" height="11"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox checked="true"><box width="13" height="13"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox checked="true"><box width="16" height="16"/></hbox>
+ </vbox>
+</window>
diff --git a/toolkit/themes/osx/reftests/checkboxsize.xul b/toolkit/themes/osx/reftests/checkboxsize.xul
new file mode 100644
index 0000000000..55429ef8f8
--- /dev/null
+++ b/toolkit/themes/osx/reftests/checkboxsize.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="data:text/css,
+vbox { height: 50px; }
+checkbox {
+ color: transparent;
+ margin: 0;
+}
+" type="text/css"?>
+
+<window title="Checkboxes with mini, small and regular control font"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox style="font-size: 9px">
+ <hbox><checkbox label="Mini"/></hbox>
+ </vbox>
+ <vbox style="font: message-box">
+ <hbox><checkbox label="Small"/></hbox>
+ </vbox>
+ <vbox style="font: -moz-dialog">
+ <hbox><checkbox label="Regular"/></hbox>
+ </vbox>
+ <vbox style="font-size: 9px">
+ <hbox><checkbox label="Mini" checked="true"/></hbox>
+ </vbox>
+ <vbox style="font: message-box">
+ <hbox><checkbox label="Small" checked="true"/></hbox>
+ </vbox>
+ <vbox style="font: -moz-dialog">
+ <hbox><checkbox label="Regular" checked="true"/></hbox>
+ </vbox>
+</window>
diff --git a/toolkit/themes/osx/reftests/nostretch-ref.xul b/toolkit/themes/osx/reftests/nostretch-ref.xul
new file mode 100644
index 0000000000..a1aee555e6
--- /dev/null
+++ b/toolkit/themes/osx/reftests/nostretch-ref.xul
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window title="Stretched controls test reference"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ orient="vertical"
+ class="reftest-wait"
+ onload="loaded()">
+
+<html:style><![CDATA[
+.regular {
+ font: -moz-dialog;
+}
+.small {
+ font: message-box;
+}
+.spacer {
+ height: 40px;
+}
+.foreground > :nth-child(2) {
+ display: none; /* <----- This is the only difference from nostretch.xul */
+}
+]]>
+</html:style>
+
+<script type="application/javascript;version=1.8"><![CDATA[
+
+function cE(elem) {
+ return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", elem);
+}
+function elWithValue(elem, val) {
+ let e = cE(elem);
+ e.setAttribute(elem == "label" || elem == "textbox" ? "value" : "label", val);
+ return e;
+}
+
+function allPairs(set) {
+ let ps = [];
+ for(let i = 0; i < set.length; ++i) {
+ for (let j = 0; j < set.length; ++j) {
+ if (i != j)
+ ps.push([set[i], set[j]]);
+ }
+ }
+ return ps;
+}
+
+function createLabel(v) {
+ return elWithValue("label", v);
+}
+function createRadio(v) {
+ return elWithValue("radio", v);
+}
+function createCheckbox(v) {
+ return elWithValue("checkbox", v);
+}
+function createButton(v) {
+ return elWithValue("button", v);
+}
+function createTextField(v) {
+ return elWithValue("textbox", v);
+}
+function createMenulist(v) {
+ let [list, popup, item] = [cE("menulist"), cE("menupopup"), elWithValue("menuitem", v)];
+ item.setAttribute("selected", "true");
+ popup.appendChild(item);
+ list.appendChild(popup);
+ return list;
+}
+function createEditableMenulist(v) {
+ let list = createMenulist(v);
+ list.setAttribute("editable", "true");
+ return list;
+}
+function loaded() {
+ let template = document.getElementById("template");
+ ["regular", "small"].forEach(function(size) {
+ let wrapper = document.querySelectorAll("#wrapper > ." + size)[0];
+ allPairs([
+ createButton, createMenulist, createTextField, createEditableMenulist,
+ ]).forEach(function(elemList) {
+ let newBox = template.cloneNode(true);
+ newBox.className = "spacer";
+ let foregroundRow = newBox.firstChild;
+ elemList.forEach(function(creator) {
+ foregroundRow.appendChild(creator("Label"));
+ });
+ wrapper.appendChild(newBox);
+ });
+ });
+ document.documentElement.className = "";
+}
+
+]]></script>
+ <vbox id="template">
+ <hbox class="foreground"/>
+ </vbox>
+ <hbox id="wrapper">
+ <vbox class="regular" width="500"/>
+ <vbox class="small" flex="1"/>
+ </hbox>
+
+ <spacer flex="1"/>
+
+</window>
diff --git a/toolkit/themes/osx/reftests/nostretch.xul b/toolkit/themes/osx/reftests/nostretch.xul
new file mode 100644
index 0000000000..cd28fa1b7b
--- /dev/null
+++ b/toolkit/themes/osx/reftests/nostretch.xul
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<!--
+ * This test tests whether you can put different widgets in the same
+ * hbox without stretching them, even if you don't set align="center".
+ * I.e. prior to the fix that added this patch, having a button and a
+ * menulist in the same hbox next to each other would stretch the menulist
+ * vertically because the button had such a big vertical margin.
+ *
+ * The test works like this: Two widgets are placed in a hbox, and the second
+ * widget is visibility: hidden. In the reference (nostretch-ref.xul), the
+ * second widget is display: none. If test and reference look the same,
+ * adding the second widget hasn't affected the appearance of the first widget,
+ * and everything's fine.
+ * -->
+<window title="Stretched controls test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ orient="vertical"
+ class="reftest-wait"
+ onload="loaded()">
+
+<html:style><![CDATA[
+.regular {
+ font: -moz-dialog;
+}
+.small {
+ font: message-box;
+}
+.spacer {
+ height: 40px;
+}
+.foreground > :nth-child(2) {
+ visibility: hidden;
+}
+]]>
+</html:style>
+
+<script type="application/javascript;version=1.8"><![CDATA[
+
+function cE(elem) {
+ return document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", elem);
+}
+function elWithValue(elem, val) {
+ let e = cE(elem);
+ e.setAttribute(elem == "label" || elem == "textbox" ? "value" : "label", val);
+ return e;
+}
+
+function allPairs(set) {
+ let ps = [];
+ for(let i = 0; i < set.length; ++i) {
+ for (let j = 0; j < set.length; ++j) {
+ if (i != j)
+ ps.push([set[i], set[j]]);
+ }
+ }
+ return ps;
+}
+
+function createLabel(v) {
+ return elWithValue("label", v);
+}
+function createRadio(v) {
+ return elWithValue("radio", v);
+}
+function createCheckbox(v) {
+ return elWithValue("checkbox", v);
+}
+function createButton(v) {
+ return elWithValue("button", v);
+}
+function createTextField(v) {
+ return elWithValue("textbox", v);
+}
+function createMenulist(v) {
+ let [list, popup, item] = [cE("menulist"), cE("menupopup"), elWithValue("menuitem", v)];
+ item.setAttribute("selected", "true");
+ popup.appendChild(item);
+ list.appendChild(popup);
+ return list;
+}
+function createEditableMenulist(v) {
+ let list = createMenulist(v);
+ list.setAttribute("editable", "true");
+ return list;
+}
+function loaded() {
+ let template = document.getElementById("template");
+ ["regular", "small"].forEach(function(size) {
+ let wrapper = document.querySelectorAll("#wrapper > ." + size)[0];
+ allPairs([
+ createButton, createMenulist, createTextField, createEditableMenulist,
+ ]).forEach(function(elemList) {
+ let newBox = template.cloneNode(true);
+ newBox.className = "spacer";
+ let foregroundRow = newBox.firstChild;
+ elemList.forEach(function(creator) {
+ foregroundRow.appendChild(creator("Label"));
+ });
+ wrapper.appendChild(newBox);
+ });
+ });
+ document.documentElement.className = "";
+}
+
+]]></script>
+ <vbox id="template">
+ <hbox class="foreground"/>
+ </vbox>
+ <hbox id="wrapper">
+ <vbox class="regular" width="500"/>
+ <vbox class="small" flex="1"/>
+ </hbox>
+
+ <spacer flex="1"/>
+
+</window>
diff --git a/toolkit/themes/osx/reftests/radiosize-ref.xul b/toolkit/themes/osx/reftests/radiosize-ref.xul
new file mode 100644
index 0000000000..632f39e41f
--- /dev/null
+++ b/toolkit/themes/osx/reftests/radiosize-ref.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="data:text/css,
+vbox { height: 50px; }
+box {
+ -moz-appearance: radio;
+ margin-left: 2px;
+ margin-top: 1px;
+}
+" type="text/css"?>
+
+<window title="Reference for mini, small and regular radio button sizes"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox>
+ <hbox><box width="11" height="11"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox><box width="13" height="13"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox><box width="16" height="16"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox selected="true"><box width="11" height="11"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox selected="true"><box width="13" height="13"/></hbox>
+ </vbox>
+ <vbox>
+ <hbox selected="true"><box width="16" height="16"/></hbox>
+ </vbox>
+</window>
diff --git a/toolkit/themes/osx/reftests/radiosize.xul b/toolkit/themes/osx/reftests/radiosize.xul
new file mode 100644
index 0000000000..44f735db07
--- /dev/null
+++ b/toolkit/themes/osx/reftests/radiosize.xul
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="data:text/css,
+vbox { height: 50px; }
+radio {
+ color: transparent;
+ margin: 0;
+}
+" type="text/css"?>
+
+<window title="Radio buttons with mini, small and regular control font"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox style="font-size: 9px">
+ <hbox><radio label="Mini"/></hbox>
+ </vbox>
+ <vbox style="font: message-box">
+ <hbox><radio label="Small"/></hbox>
+ </vbox>
+ <vbox style="font: -moz-dialog">
+ <hbox><radio label="Regular"/></hbox>
+ </vbox>
+ <vbox style="font-size: 9px">
+ <hbox><radio label="Mini" selected="true"/></hbox>
+ </vbox>
+ <vbox style="font: message-box">
+ <hbox><radio label="Small" selected="true"/></hbox>
+ </vbox>
+ <vbox style="font: -moz-dialog">
+ <hbox><radio label="Regular" selected="true"/></hbox>
+ </vbox>
+</window>
diff --git a/toolkit/themes/osx/reftests/reftest-stylo.list b/toolkit/themes/osx/reftests/reftest-stylo.list
new file mode 100644
index 0000000000..82df4273dc
--- /dev/null
+++ b/toolkit/themes/osx/reftests/reftest-stylo.list
@@ -0,0 +1,6 @@
+# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
+skip-if(!cocoaWidget) == 482681.xul 482681.xul
+skip-if(!cocoaWidget) == radiosize.xul radiosize.xul
+skip-if(!cocoaWidget) == checkboxsize.xul checkboxsize.xul
+skip-if(!cocoaWidget) == baseline.xul baseline.xul
+skip-if(!cocoaWidget) == nostretch.xul nostretch.xul
diff --git a/toolkit/themes/osx/reftests/reftest.list b/toolkit/themes/osx/reftests/reftest.list
new file mode 100644
index 0000000000..27034dd862
--- /dev/null
+++ b/toolkit/themes/osx/reftests/reftest.list
@@ -0,0 +1,5 @@
+skip-if(!cocoaWidget) == 482681.xul 482681-ref.xul
+skip-if(!cocoaWidget) == radiosize.xul radiosize-ref.xul
+skip-if(!cocoaWidget) == checkboxsize.xul checkboxsize-ref.xul
+skip-if(!cocoaWidget) == baseline.xul about:blank
+skip-if(!cocoaWidget) == nostretch.xul nostretch-ref.xul
diff --git a/toolkit/themes/shared/aboutReader.css b/toolkit/themes/shared/aboutReader.css
index c6cd959277..1bfbd0309d 100644
--- a/toolkit/themes/shared/aboutReader.css
+++ b/toolkit/themes/shared/aboutReader.css
@@ -388,7 +388,19 @@ body:not(.loaded) .toolbar:-moz-locale-dir(rtl) {
}
/* Font sizes are different per-platform, so we need custom CSS to line them up. */
-%ifdef XP_WIN
+%ifdef XP_MACOSX
+.font-type-buttons > .sans-serif-button > .name {
+ margin-top: 10px;
+}
+
+.font-type-buttons > .sans-serif-button > .description {
+ margin-top: -4px;
+}
+
+.font-type-buttons > .serif-button > .name {
+ font-size: 63px;
+}
+%elifdef XP_WIN
.font-type-buttons > .sans-serif-button > .name {
margin-top: 2px;
}
diff --git a/toolkit/themes/shared/jar.inc.mn b/toolkit/themes/shared/jar.inc.mn
index 08f3250dcc..3755688279 100644
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -62,7 +62,7 @@ toolkit.jar:
skin/classic/global/media/TopLevelVideoDocument.css (../../shared/media/TopLevelVideoDocument.css)
skin/classic/global/media/imagedoc-lightnoise.png (../../shared/media/imagedoc-lightnoise.png)
skin/classic/global/media/imagedoc-darknoise.png (../../shared/media/imagedoc-darknoise.png)
- skin/classic/global/media/videocontrols.css (../../shared/media/videocontrols.css)
+* skin/classic/global/media/videocontrols.css (../../shared/media/videocontrols.css)
skin/classic/global/media/pauseButton.png (../../shared/media/pauseButton.png)
skin/classic/global/media/pauseButton@2x.png (../../shared/media/pauseButton@2x.png)
skin/classic/global/media/playButton.png (../../shared/media/playButton.png)
diff --git a/toolkit/themes/shared/media/videocontrols.css b/toolkit/themes/shared/media/videocontrols.css
index 690762062b..a40d77fe3a 100644
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -165,9 +165,15 @@
font-size: 11px;
}
+%ifdef XP_MACOSX
+.durationLabel {
+ padding-top: 2px; /* center vertically with scrubber bar */
+}
+%else
.durationLabel {
padding-top: 0; /* center vertically with scrubber bar */
}
+%endif
.positionLabel {
display: none;
@@ -253,9 +259,15 @@
padding-top: 7px;
}
+%ifdef XP_MACOSX
+.timeLabel {
+ padding-top: 7px; /* center vertically with scrubber bar */
+}
+%else
.timeLabel {
padding-top: 5px; /* center vertically with scrubber bar */
}
+%endif
.statusOverlay {
-moz-box-align: center;
diff --git a/toolkit/xre/MacApplicationDelegate.h b/toolkit/xre/MacApplicationDelegate.h
new file mode 100644
index 0000000000..74f9a93ed2
--- /dev/null
+++ b/toolkit/xre/MacApplicationDelegate.h
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+// This file defines the interface between Cocoa-specific Obj-C++ and generic C++,
+// so it itself cannot have any Obj-C bits in it.
+
+#ifndef MacApplicationDelegate_h_
+#define MacApplicationDelegate_h_
+
+void EnsureUseCocoaDockAPI(void);
+void SetupMacApplicationDelegate(void);
+void ProcessPendingGetURLAppleEvents(void);
+
+#endif
diff --git a/toolkit/xre/MacApplicationDelegate.mm b/toolkit/xre/MacApplicationDelegate.mm
new file mode 100644
index 0000000000..2b295aa7d9
--- /dev/null
+++ b/toolkit/xre/MacApplicationDelegate.mm
@@ -0,0 +1,396 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+// NSApplication delegate for Mac OS X Cocoa API.
+
+// As of 10.4 Tiger, the system can send six kinds of Apple Events to an application;
+// a well-behaved XUL app should have some kind of handling for all of them.
+//
+// See http://developer.apple.com/documentation/Cocoa/Conceptual/ScriptableCocoaApplications/SApps_handle_AEs/chapter_11_section_3.html for details.
+
+#import <Cocoa/Cocoa.h>
+#import <Carbon/Carbon.h>
+
+#include "nsCOMPtr.h"
+#include "nsINativeAppSupport.h"
+#include "nsAppRunner.h"
+#include "nsAppShell.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIServiceManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIAppStartup.h"
+#include "nsIObserverService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsObjCExceptions.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsICommandLineRunner.h"
+#include "nsIMacDockSupport.h"
+#include "nsIStandaloneNativeMenu.h"
+#include "nsILocalFileMac.h"
+#include "nsString.h"
+#include "nsCommandLineServiceMac.h"
+
+class AutoAutoreleasePool {
+public:
+ AutoAutoreleasePool()
+ {
+ mLocalPool = [[NSAutoreleasePool alloc] init];
+ }
+ ~AutoAutoreleasePool()
+ {
+ [mLocalPool release];
+ }
+private:
+ NSAutoreleasePool *mLocalPool;
+};
+
+@interface MacApplicationDelegate : NSObject<NSApplicationDelegate>
+{
+}
+
+@end
+
+static bool sProcessedGetURLEvent = false;
+
+// Methods that can be called from non-Objective-C code.
+
+// This is needed, on relaunch, to force the OS to use the "Cocoa Dock API"
+// instead of the "Carbon Dock API". For more info see bmo bug 377166.
+void
+EnsureUseCocoaDockAPI()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [GeckoNSApplication sharedApplication];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+SetupMacApplicationDelegate()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // this is called during startup, outside an event loop, and therefore
+ // needs an autorelease pool to avoid cocoa object leakage (bug 559075)
+ AutoAutoreleasePool pool;
+
+ // Ensure that ProcessPendingGetURLAppleEvents() doesn't regress bug 377166.
+ [GeckoNSApplication sharedApplication];
+
+ // This call makes it so that application:openFile: doesn't get bogus calls
+ // from Cocoa doing its own parsing of the argument string. And yes, we need
+ // to use a string with a boolean value in it. That's just how it works.
+ [[NSUserDefaults standardUserDefaults] setObject:@"NO"
+ forKey:@"NSTreatUnknownArgumentsAsOpen"];
+
+ // Create the delegate. This should be around for the lifetime of the app.
+ id<NSApplicationDelegate> delegate = [[MacApplicationDelegate alloc] init];
+ [[GeckoNSApplication sharedApplication] setDelegate:delegate];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Indirectly make the OS process any pending GetURL Apple events. This is
+// done via _DPSNextEvent() (an undocumented AppKit function called from
+// [NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]). Apple
+// events are only processed if 'dequeue' is 'YES' -- so we need to call
+// [NSApplication sendEvent:] on any event that gets returned. 'event' will
+// never itself be an Apple event, and it may be 'nil' even when Apple events
+// are processed.
+void
+ProcessPendingGetURLAppleEvents()
+{
+ AutoAutoreleasePool pool;
+ bool keepSpinning = true;
+ while (keepSpinning) {
+ sProcessedGetURLEvent = false;
+ NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:nil
+ inMode:NSDefaultRunLoopMode
+ dequeue:YES];
+ if (event)
+ [NSApp sendEvent:event];
+ keepSpinning = sProcessedGetURLEvent;
+ }
+}
+
+@implementation MacApplicationDelegate
+
+- (id)init
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ((self = [super init])) {
+ NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager];
+
+ [aeMgr setEventHandler:self
+ andSelector:@selector(handleAppleEvent:withReplyEvent:)
+ forEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
+
+ [aeMgr setEventHandler:self
+ andSelector:@selector(handleAppleEvent:withReplyEvent:)
+ forEventClass:'WWW!'
+ andEventID:'OURL'];
+
+ [aeMgr setEventHandler:self
+ andSelector:@selector(handleAppleEvent:withReplyEvent:)
+ forEventClass:kCoreEventClass
+ andEventID:kAEOpenDocuments];
+
+ if (![NSApp windowsMenu]) {
+ // If the application has a windows menu, it will keep it up to date and
+ // prepend the window list to the Dock menu automatically.
+ NSMenu* windowsMenu = [[NSMenu alloc] initWithTitle:@"Window"];
+ [NSApp setWindowsMenu:windowsMenu];
+ [windowsMenu release];
+ }
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nil);
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager];
+ [aeMgr removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL];
+ [aeMgr removeEventHandlerForEventClass:'WWW!' andEventID:'OURL'];
+ [aeMgr removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEOpenDocuments];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// The method that NSApplication calls upon a request to reopen, such as when
+// the Dock icon is clicked and no windows are open. A "visible" window may be
+// miniaturized, so we can't skip nsCocoaNativeReOpen() if 'flag' is 'true'.
+- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApp hasVisibleWindows:(BOOL)flag
+{
+ nsCOMPtr<nsINativeAppSupport> nas = do_CreateInstance(NS_NATIVEAPPSUPPORT_CONTRACTID);
+ NS_ENSURE_TRUE(nas, NO);
+
+ // Go to the common Carbon/Cocoa reopen method.
+ nsresult rv = nas->ReOpen();
+ NS_ENSURE_SUCCESS(rv, NO);
+
+ // NO says we don't want NSApplication to do anything else for us.
+ return NO;
+}
+
+// The method that NSApplication calls when documents are requested to be opened.
+// It will be called once for each selected document.
+- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSURL *url = [NSURL fileURLWithPath:filename];
+ if (!url)
+ return NO;
+
+ NSString *urlString = [url absoluteString];
+ if (!urlString)
+ return NO;
+
+ // Add the URL to any command line we're currently setting up.
+ if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String]))
+ return YES;
+
+ nsCOMPtr<nsILocalFileMac> inFile;
+ nsresult rv = NS_NewLocalFileWithCFURL((CFURLRef)url, true, getter_AddRefs(inFile));
+ if (NS_FAILED(rv))
+ return NO;
+
+ nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
+ if (!cmdLine) {
+ NS_ERROR("Couldn't create command line!");
+ return NO;
+ }
+
+ nsCString filePath;
+ rv = inFile->GetNativePath(filePath);
+ if (NS_FAILED(rv))
+ return NO;
+
+ nsCOMPtr<nsIFile> workingDir;
+ rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir));
+ if (NS_FAILED(rv))
+ return NO;
+
+ const char *argv[3] = {nullptr, "-file", filePath.get()};
+ rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT);
+ if (NS_FAILED(rv))
+ return NO;
+
+ if (NS_SUCCEEDED(cmdLine->Run()))
+ return YES;
+
+ return NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// The method that NSApplication calls when documents are requested to be printed
+// from the Finder (under the "File" menu).
+// It will be called once for each selected document.
+- (BOOL)application:(NSApplication*)theApplication printFile:(NSString*)filename
+{
+ return NO;
+}
+
+// Create the menu that shows up in the Dock.
+- (NSMenu*)applicationDockMenu:(NSApplication*)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // Create the NSMenu that will contain the dock menu items.
+ NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+ [menu setAutoenablesItems:NO];
+
+ // Add application-specific dock menu items. On error, do not insert the
+ // dock menu items.
+ nsresult rv;
+ nsCOMPtr<nsIMacDockSupport> dockSupport = do_GetService("@mozilla.org/widget/macdocksupport;1", &rv);
+ if (NS_FAILED(rv) || !dockSupport)
+ return menu;
+
+ nsCOMPtr<nsIStandaloneNativeMenu> dockMenu;
+ rv = dockSupport->GetDockMenu(getter_AddRefs(dockMenu));
+ if (NS_FAILED(rv) || !dockMenu)
+ return menu;
+
+ // Determine if the dock menu items should be displayed. This also gives
+ // the menu the opportunity to update itself before display.
+ bool shouldShowItems;
+ rv = dockMenu->MenuWillOpen(&shouldShowItems);
+ if (NS_FAILED(rv) || !shouldShowItems)
+ return menu;
+
+ // Obtain a copy of the native menu.
+ NSMenu * nativeDockMenu;
+ rv = dockMenu->GetNativeMenu(reinterpret_cast<void **>(&nativeDockMenu));
+ if (NS_FAILED(rv) || !nativeDockMenu)
+ return menu;
+
+ // Loop through the application-specific dock menu and insert its
+ // contents into the dock menu that we are building for Cocoa.
+ int numDockMenuItems = [nativeDockMenu numberOfItems];
+ if (numDockMenuItems > 0) {
+ if ([menu numberOfItems] > 0)
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ for (int i = 0; i < numDockMenuItems; i++) {
+ NSMenuItem * itemCopy = [[nativeDockMenu itemAtIndex:i] copy];
+ [menu addItem:itemCopy];
+ [itemCopy release];
+ }
+ }
+
+ return menu;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// If we don't handle applicationShouldTerminate:, a call to [NSApp terminate:]
+// (from the browser or from the OS) can result in an unclean shutdown.
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
+{
+ nsCOMPtr<nsIObserverService> obsServ =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (!obsServ)
+ return NSTerminateNow;
+
+ nsCOMPtr<nsISupportsPRBool> cancelQuit =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
+ if (!cancelQuit)
+ return NSTerminateNow;
+
+ cancelQuit->SetData(false);
+ obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr);
+
+ bool abortQuit;
+ cancelQuit->GetData(&abortQuit);
+ if (abortQuit)
+ return NSTerminateCancel;
+
+ nsCOMPtr<nsIAppStartup> appService =
+ do_GetService("@mozilla.org/toolkit/app-startup;1");
+ if (appService)
+ appService->Quit(nsIAppStartup::eForceQuit);
+
+ return NSTerminateNow;
+}
+
+- (void)handleAppleEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
+{
+ if (!event)
+ return;
+
+ AutoAutoreleasePool pool;
+
+ bool isGetURLEvent =
+ ([event eventClass] == kInternetEventClass && [event eventID] == kAEGetURL);
+ if (isGetURLEvent)
+ sProcessedGetURLEvent = true;
+
+ if (isGetURLEvent ||
+ ([event eventClass] == 'WWW!' && [event eventID] == 'OURL')) {
+ NSString* urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
+
+ // don't open chrome URLs
+ NSString* schemeString = [[NSURL URLWithString:urlString] scheme];
+ if (!schemeString ||
+ [schemeString compare:@"chrome"
+ options:NSCaseInsensitiveSearch
+ range:NSMakeRange(0, [schemeString length])] == NSOrderedSame) {
+ return;
+ }
+
+ // Add the URL to any command line we're currently setting up.
+ if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String]))
+ return;
+
+ nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
+ if (!cmdLine) {
+ NS_ERROR("Couldn't create command line!");
+ return;
+ }
+ nsCOMPtr<nsIFile> workingDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir));
+ if (NS_FAILED(rv))
+ return;
+ const char *argv[3] = {nullptr, "-url", [urlString UTF8String]};
+ rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT);
+ if (NS_FAILED(rv))
+ return;
+ rv = cmdLine->Run();
+ }
+ else if ([event eventClass] == kCoreEventClass && [event eventID] == kAEOpenDocuments) {
+ NSAppleEventDescriptor* fileListDescriptor = [event paramDescriptorForKeyword:keyDirectObject];
+ if (!fileListDescriptor)
+ return;
+
+ // Descriptor list indexing is one-based...
+ NSInteger numberOfFiles = [fileListDescriptor numberOfItems];
+ for (NSInteger i = 1; i <= numberOfFiles; i++) {
+ NSString* urlString = [[fileListDescriptor descriptorAtIndex:i] stringValue];
+ if (!urlString)
+ continue;
+
+ // We need a path, not a URL
+ NSURL* url = [NSURL URLWithString:urlString];
+ if (!url)
+ continue;
+
+ [self application:NSApp openFile:[url path]];
+ }
+ }
+}
+
+@end
diff --git a/toolkit/xre/MacAutoreleasePool.h b/toolkit/xre/MacAutoreleasePool.h
new file mode 100644
index 0000000000..7668957f19
--- /dev/null
+++ b/toolkit/xre/MacAutoreleasePool.h
@@ -0,0 +1,31 @@
+/* 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/. */
+
+#ifndef MacAutoreleasePool_h_
+#define MacAutoreleasePool_h_
+
+// This needs to be #include-able from non-ObjC code in nsAppRunner.cpp
+#ifdef __OBJC__
+@class NSAutoreleasePool;
+#else
+class NSAutoreleasePool;
+#endif
+
+namespace mozilla {
+
+class MacAutoreleasePool {
+public:
+ MacAutoreleasePool();
+ ~MacAutoreleasePool();
+
+private:
+ NSAutoreleasePool *mPool;
+
+ MacAutoreleasePool(const MacAutoreleasePool&);
+ void operator=(const MacAutoreleasePool&);
+};
+
+} // namespace mozilla
+
+#endif // MacAutoreleasePool_h_
diff --git a/toolkit/xre/MacAutoreleasePool.mm b/toolkit/xre/MacAutoreleasePool.mm
new file mode 100644
index 0000000000..ae8ad51ff1
--- /dev/null
+++ b/toolkit/xre/MacAutoreleasePool.mm
@@ -0,0 +1,20 @@
+/* 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 "MacAutoreleasePool.h"
+#include "nsDebug.h"
+
+#import <Foundation/Foundation.h>
+
+using mozilla::MacAutoreleasePool;
+
+MacAutoreleasePool::MacAutoreleasePool()
+{
+ mPool = [[NSAutoreleasePool alloc] init];
+ NS_ASSERTION(mPool != nullptr, "failed to create pool, objects will leak");
+}
+
+MacAutoreleasePool::~MacAutoreleasePool() {
+ [mPool release];
+}
diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h
new file mode 100644
index 0000000000..08035c53b6
--- /dev/null
+++ b/toolkit/xre/MacLaunchHelper.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef MacLaunchHelper_h_
+#define MacLaunchHelper_h_
+
+#include <stdint.h>
+
+#include <unistd.h>
+
+extern "C" {
+ /**
+ * Passing an aPid parameter to LaunchChildMac will wait for the launched
+ * process to terminate. When the process terminates, aPid will be set to the
+ * pid of the terminated process to confirm that it executed successfully.
+ */
+ void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid = 0);
+ bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid = 0);
+}
+
+#endif
diff --git a/toolkit/xre/MacLaunchHelper.mm b/toolkit/xre/MacLaunchHelper.mm
new file mode 100644
index 0000000000..0dadb8de88
--- /dev/null
+++ b/toolkit/xre/MacLaunchHelper.mm
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 2; 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 "MacLaunchHelper.h"
+
+#include "MacAutoreleasePool.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIAppStartup.h"
+#include "nsMemory.h"
+
+#include <Cocoa/Cocoa.h>
+#include <crt_externs.h>
+#include <ServiceManagement/ServiceManagement.h>
+#include <Security/Authorization.h>
+#include <spawn.h>
+#include <stdio.h>
+
+using namespace mozilla;
+
+void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid)
+{
+ MacAutoreleasePool pool;
+
+ @try {
+ NSString* launchPath = [NSString stringWithUTF8String:aArgv[0]];
+ NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:aArgc - 1];
+ for (int i = 1; i < aArgc; i++) {
+ [arguments addObject:[NSString stringWithUTF8String:aArgv[i]]];
+ }
+ NSTask* child = [NSTask launchedTaskWithLaunchPath:launchPath
+ arguments:arguments];
+ if (aPid) {
+ *aPid = [child processIdentifier];
+ // We used to use waitpid to wait for the process to terminate. This is
+ // incompatible with NSTask and we wait for the process to exit here
+ // instead.
+ [child waitUntilExit];
+ }
+ } @catch (NSException* e) {
+ NSLog(@"%@: %@", e.name, e.reason);
+ }
+}
+
+BOOL InstallPrivilegedHelper()
+{
+ AuthorizationRef authRef = NULL;
+ OSStatus status = AuthorizationCreate(NULL,
+ kAuthorizationEmptyEnvironment,
+ kAuthorizationFlagDefaults |
+ kAuthorizationFlagInteractionAllowed,
+ &authRef);
+ if (status != errAuthorizationSuccess) {
+ // AuthorizationCreate really shouldn't fail.
+ NSLog(@"AuthorizationCreate failed! NSOSStatusErrorDomain / %d",
+ (int)status);
+ return NO;
+ }
+
+ BOOL result = NO;
+ AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 };
+ AuthorizationRights authRights = { 1, &authItem };
+ AuthorizationFlags flags = kAuthorizationFlagDefaults |
+ kAuthorizationFlagInteractionAllowed |
+ kAuthorizationFlagPreAuthorize |
+ kAuthorizationFlagExtendRights;
+
+ // Obtain the right to install our privileged helper tool.
+ status = AuthorizationCopyRights(authRef,
+ &authRights,
+ kAuthorizationEmptyEnvironment,
+ flags,
+ NULL);
+ if (status != errAuthorizationSuccess) {
+ NSLog(@"AuthorizationCopyRights failed! NSOSStatusErrorDomain / %d",
+ (int)status);
+ } else {
+ CFErrorRef cfError;
+ // This does all the work of verifying the helper tool against the
+ // application and vice-versa. Once verification has passed, the embedded
+ // launchd.plist is extracted and placed in /Library/LaunchDaemons and then
+ // loaded. The executable is placed in /Library/PrivilegedHelperTools.
+ result = (BOOL)SMJobBless(kSMDomainSystemLaunchd,
+ (CFStringRef)@"org.mozilla.updater",
+ authRef,
+ &cfError);
+ if (!result) {
+ NSLog(@"Unable to install helper!");
+ CFRelease(cfError);
+ }
+ }
+
+ return result;
+}
+
+void AbortElevatedUpdate()
+{
+ mozilla::MacAutoreleasePool pool;
+
+ id updateServer = nil;
+ int currTry = 0;
+ const int numRetries = 10; // Number of IPC connection retries before
+ // giving up.
+ while (currTry < numRetries) {
+ @try {
+ updateServer = (id)[NSConnection
+ rootProxyForConnectionWithRegisteredName:
+ @"org.mozilla.updater.server"
+ host:nil
+ usingNameServer:[NSSocketPortNameServer sharedInstance]];
+ if (updateServer &&
+ [updateServer respondsToSelector:@selector(abort)]) {
+ [updateServer performSelector:@selector(abort)];
+ return;
+ }
+ NSLog(@"Server doesn't exist or doesn't provide correct selectors.");
+ sleep(1); // Wait 1 second.
+ currTry++;
+ } @catch (NSException* e) {
+ NSLog(@"Encountered exception, retrying: %@: %@", e.name, e.reason);
+ sleep(1); // Wait 1 second.
+ currTry++;
+ }
+ }
+ NSLog(@"Unable to clean up updater.");
+}
+
+bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid)
+{
+ LaunchChildMac(aArgc, aArgv, aPid);
+ bool didSucceed = InstallPrivilegedHelper();
+ if (!didSucceed) {
+ AbortElevatedUpdate();
+ }
+ return didSucceed;
+}
diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build
index b717e971c3..4072fe134f 100644
--- a/toolkit/xre/moz.build
+++ b/toolkit/xre/moz.build
@@ -19,6 +19,15 @@ if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
UNIFIED_SOURCES += ['nsNativeAppSupportWin.cpp']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'MacApplicationDelegate.mm',
+ 'MacAutoreleasePool.mm',
+ 'MacLaunchHelper.mm',
+ 'nsCommandLineServiceMac.cpp',
+ 'nsNativeAppSupportCocoa.mm',
+ 'updaterfileutils_osx.mm',
+ ]
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
UNIFIED_SOURCES += [
'nsNativeAppSupportDefault.cpp',
@@ -112,6 +121,12 @@ LOCAL_INCLUDES += [
'/xpcom/build',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ LOCAL_INCLUDES += [
+ '/widget',
+ '/widget/cocoa',
+ ]
+
if CONFIG['MOZ_ENABLE_XREMOTE']:
LOCAL_INCLUDES += [
'/widget/xremoteclient',
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index b15335ade3..274631aac8 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -31,6 +31,16 @@
#include "EventTracer.h"
#endif
+#ifdef XP_MACOSX
+#include "nsVersionComparator.h"
+#include "MacLaunchHelper.h"
+#include "MacApplicationDelegate.h"
+#include "MacAutoreleasePool.h"
+// these are needed for sysctl
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#endif
+
#include "prmem.h"
#include "prnetdb.h"
#include "prprf.h"
@@ -144,6 +154,11 @@
#include "WinUtils.h"
#endif
+#ifdef XP_MACOSX
+#include "nsILocalFileMac.h"
+#include "nsCommandLineServiceMac.h"
+#endif
+
// for X remote support
#ifdef MOZ_ENABLE_XREMOTE
#include "XRemoteClient.h"
@@ -159,6 +174,10 @@
#include <malloc.h>
#endif
+#if defined (XP_MACOSX)
+#include <Carbon/Carbon.h>
+#endif
+
#ifdef DEBUG
#include "mozilla/Logging.h"
#endif
@@ -1072,6 +1091,12 @@ ScopedXPCOMStartup::~ScopedXPCOMStartup()
NS_IF_RELEASE(gNativeAppSupport);
if (mServiceManager) {
+#ifdef XP_MACOSX
+ // On OS X, we need a pool to catch cocoa objects that are autoreleased
+ // during teardown.
+ mozilla::MacAutoreleasePool pool;
+#endif
+
nsCOMPtr<nsIAppStartup> appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID));
if (appStartup)
appStartup->DestroyHiddenWindow();
@@ -1457,6 +1482,10 @@ static nsresult LaunchChild(nsINativeAppSupport* aNative,
SaveToEnv("MOZ_LAUNCHED_CHILD=1");
+#if defined(XP_MACOSX)
+ CommandLineServiceMac::SetupMacCommandLine(gRestartArgc, gRestartArgv, true);
+ LaunchChildMac(gRestartArgc, gRestartArgv);
+#else
nsCOMPtr<nsIFile> lf;
nsresult rv = XRE_GetBinaryPath(gArgv[0], getter_AddRefs(lf));
if (NS_FAILED(rv))
@@ -1491,6 +1520,7 @@ static nsresult LaunchChild(nsINativeAppSupport* aNative,
return NS_ERROR_FAILURE;
#endif // XP_UNIX
#endif // WP_WIN
+#endif // WP_MACOSX
return NS_ERROR_LAUNCHED_CHILD_PROCESS;
}
@@ -1559,9 +1589,15 @@ ProfileLockedDialog(nsIFile* aProfileDir, nsIFile* aProfileLocalDir,
const char16_t* params[] = {appName.get(), appName.get()};
nsXPIDLString killMessage;
+#ifndef XP_MACOSX
sb->FormatStringFromName(aUnlocker ? u"restartMessageUnlocker"
: u"restartMessageNoUnlocker",
params, 2, getter_Copies(killMessage));
+#else
+ sb->FormatStringFromName(aUnlocker ? u"restartMessageUnlockerMac"
+ : u"restartMessageNoUnlockerMac",
+ params, 2, getter_Copies(killMessage));
+#endif
nsXPIDLString killTitle;
sb->FormatStringFromName(u"restartTitle",
@@ -1705,6 +1741,10 @@ ShowProfileManager(nsIToolkitProfileService* aProfileSvc,
rv = xpcom.SetWindowCreator(aNative);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+#ifdef XP_MACOSX
+ CommandLineServiceMac::SetupMacCommandLine(gRestartArgc, gRestartArgv, true);
+#endif
+
#ifdef XP_WIN
// we don't have to wait here because profile manager window will pump
// and DDE message will be handled
@@ -2810,6 +2850,13 @@ XREMain::XRE_mainInit(bool* aExitFlag)
if (NS_FAILED(rv))
return 2;
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIFile> parent;
+ greDir->GetParent(getter_AddRefs(parent));
+ greDir = parent.forget();
+ greDir->AppendNative(NS_LITERAL_CSTRING("Resources"));
+#endif
+
greDir.forget(&mAppData->xreDirectory);
}
@@ -2843,6 +2890,40 @@ XREMain::XRE_mainInit(bool* aExitFlag)
if (NS_FAILED(rv))
return 1;
+#ifdef XP_MACOSX
+ // Set up ability to respond to system (Apple) events. This must occur before
+ // ProcessUpdates to ensure that links clicked in external applications aren't
+ // lost when updates are pending.
+ SetupMacApplicationDelegate();
+
+ if (EnvHasValue("MOZ_LAUNCHED_CHILD")) {
+ // This is needed, on relaunch, to force the OS to use the "Cocoa Dock
+ // API". Otherwise the call to ReceiveNextEvent() below will make it
+ // use the "Carbon Dock API". For more info see bmo bug 377166.
+ EnsureUseCocoaDockAPI();
+
+ // When the app relaunches, the original process exits. This causes
+ // the dock tile to stop bouncing, lose the "running" triangle, and
+ // if the tile does not permanently reside in the Dock, even disappear.
+ // This can be confusing to the user, who is expecting the app to launch.
+ // Calling ReceiveNextEvent without requesting any event is enough to
+ // cause a dock tile for the child process to appear.
+ const EventTypeSpec kFakeEventList[] = { { INT_MAX, INT_MAX } };
+ EventRef event;
+ ::ReceiveNextEvent(GetEventTypeCount(kFakeEventList), kFakeEventList,
+ kEventDurationNoWait, false, &event);
+ }
+
+ if (CheckArg("foreground")) {
+ // The original process communicates that it was in the foreground by
+ // adding this argument. This new process, which is taking over for
+ // the old one, should make itself the active application.
+ ProcessSerialNumber psn;
+ if (::GetCurrentProcess(&psn) == noErr)
+ ::SetFrontProcess(&psn);
+ }
+#endif
+
SaveToEnv("MOZ_LAUNCHED_CHILD=");
gRestartArgc = gArgc;
@@ -2893,6 +2974,12 @@ XREMain::XRE_mainInit(bool* aExitFlag)
}
#endif
+#ifdef XP_MACOSX
+ if ((GetCurrentEventKeyModifiers() & optionKey) &&
+ !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY"))
+ gSafeMode = true;
+#endif
+
#ifdef XP_WIN
{
// Add CPU microcode version to the crash report as "CPUMicrocodeVersion".
@@ -3666,6 +3753,19 @@ XREMain::XRE_mainRun()
g_unsetenv ("DESKTOP_STARTUP_ID");
#endif
+#ifdef XP_MACOSX
+ // we re-initialize the command-line service and do appleevents munging
+ // after we are sure that we're not restarting
+ cmdLine = do_CreateInstance("@mozilla.org/toolkit/command-line;1");
+ NS_ENSURE_TRUE(cmdLine, NS_ERROR_FAILURE);
+
+ CommandLineServiceMac::SetupMacCommandLine(gArgc, gArgv, false);
+
+ rv = cmdLine->Init(gArgc, gArgv,
+ workingDir, nsICommandLine::STATE_INITIAL_LAUNCH);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+#endif
+
nsCOMPtr<nsIObserverService> obsService =
mozilla::services::GetObserverService();
if (obsService)
diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp
index 841ea2a2d0..109782b5f8 100644
--- a/toolkit/xre/nsEmbedFunctions.cpp
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -39,6 +39,10 @@
#include "nsXREDirProvider.h"
#include "mozilla/Omnijar.h"
+#if defined(XP_MACOSX)
+#include "nsVersionComparator.h"
+#include "chrome/common/mach_ipc_mac.h"
+#endif
#include "nsX11ErrorHandler.h"
#include "nsGDKErrorHandler.h"
#include "base/at_exit.h"
@@ -280,6 +284,81 @@ XRE_InitChildProcess(int aArgc,
PROFILER_LABEL("Startup", "XRE_InitChildProcess",
js::ProfileEntry::Category::OTHER);
+ // Complete 'task_t' exchange for Mac OS X. This structure has the same size
+ // regardless of architecture so we don't have any cross-arch issues here.
+#ifdef XP_MACOSX
+ if (aArgc < 1)
+ return NS_ERROR_FAILURE;
+ const char* const mach_port_name = aArgv[--aArgc];
+
+ const int kTimeoutMs = 1000;
+
+ MachSendMessage child_message(0);
+ if (!child_message.AddDescriptor(MachMsgPortDescriptor(mach_task_self()))) {
+ NS_WARNING("child AddDescriptor(mach_task_self()) failed.");
+ return NS_ERROR_FAILURE;
+ }
+
+ ReceivePort child_recv_port;
+ mach_port_t raw_child_recv_port = child_recv_port.GetPort();
+ if (!child_message.AddDescriptor(MachMsgPortDescriptor(raw_child_recv_port))) {
+ NS_WARNING("Adding descriptor to message failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ ReceivePort* ports_out_receiver = new ReceivePort();
+ if (!child_message.AddDescriptor(MachMsgPortDescriptor(ports_out_receiver->GetPort()))) {
+ NS_WARNING("Adding descriptor to message failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ ReceivePort* ports_in_receiver = new ReceivePort();
+ if (!child_message.AddDescriptor(MachMsgPortDescriptor(ports_in_receiver->GetPort()))) {
+ NS_WARNING("Adding descriptor to message failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ MachPortSender child_sender(mach_port_name);
+ kern_return_t err = child_sender.SendMessage(child_message, kTimeoutMs);
+ if (err != KERN_SUCCESS) {
+ NS_WARNING("child SendMessage() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ MachReceiveMessage parent_message;
+ err = child_recv_port.WaitForMessage(&parent_message, kTimeoutMs);
+ if (err != KERN_SUCCESS) {
+ NS_WARNING("child WaitForMessage() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (parent_message.GetTranslatedPort(0) == MACH_PORT_NULL) {
+ NS_WARNING("child GetTranslatedPort(0) failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ err = task_set_bootstrap_port(mach_task_self(),
+ parent_message.GetTranslatedPort(0));
+
+ if (parent_message.GetTranslatedPort(1) == MACH_PORT_NULL) {
+ NS_WARNING("child GetTranslatedPort(1) failed");
+ return NS_ERROR_FAILURE;
+ }
+ MachPortSender* ports_out_sender = new MachPortSender(parent_message.GetTranslatedPort(1));
+
+ if (parent_message.GetTranslatedPort(2) == MACH_PORT_NULL) {
+ NS_WARNING("child GetTranslatedPort(2) failed");
+ return NS_ERROR_FAILURE;
+ }
+ MachPortSender* ports_in_sender = new MachPortSender(parent_message.GetTranslatedPort(2));
+
+ if (err != KERN_SUCCESS) {
+ NS_WARNING("child task_set_bootstrap_port() failed");
+ return NS_ERROR_FAILURE;
+ }
+
+#endif
+
SetupErrorHandling(aArgv[0]);
gArgv = aArgv;
@@ -325,6 +404,11 @@ XRE_InitChildProcess(int aArgc,
base::ProcessId parentPID = strtol(parentPIDString, &end, 10);
MOZ_ASSERT(!*end, "invalid parent PID");
+#ifdef XP_MACOSX
+ mozilla::ipc::SharedMemoryBasic::SetupMachMemory(parentPID, ports_in_receiver, ports_in_sender,
+ ports_out_sender, ports_out_receiver, true);
+#endif
+
#if defined(XP_WIN)
// On Win7+, register the application user model id passed in by
// parent. This insures windows created by the container properly
@@ -456,6 +540,11 @@ XRE_InitChildProcess(int aArgc,
// scope and being deleted
process->CleanUp();
mozilla::Omnijar::CleanUp();
+
+#if defined(XP_MACOSX)
+ // Everybody should be done using shared memory by now.
+ mozilla::ipc::SharedMemoryBasic::Shutdown();
+#endif
}
}
@@ -571,6 +660,47 @@ XRE_RunAppShell()
{
nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE);
+#if defined(XP_MACOSX)
+ {
+ // In content processes that want XPCOM (and hence want
+ // AppShell), we usually run our hybrid event loop through
+ // MessagePump::Run(), by way of nsBaseAppShell::Run(). The
+ // Cocoa nsAppShell impl, however, implements its own Run()
+ // that's unaware of MessagePump. That's all rather suboptimal,
+ // but oddly enough not a problem... usually.
+ //
+ // The problem with this setup comes during startup.
+ // XPCOM-in-subprocesses depends on IPC, e.g. to init the pref
+ // service, so we have to init IPC first. But, IPC also
+ // indirectly kinda-depends on XPCOM, because MessagePump
+ // schedules work from off-main threads (e.g. IO thread) by
+ // using NS_DispatchToMainThread(). If the IO thread receives a
+ // Message from the parent before nsThreadManager is
+ // initialized, then DispatchToMainThread() will fail, although
+ // MessagePump will remember the task. This race condition
+ // isn't a problem when appShell->Run() ends up in
+ // MessagePump::Run(), because MessagePump will immediate see it
+ // has work to do. It *is* a problem when we end up in [NSApp
+ // run], because it's not aware that MessagePump has work that
+ // needs to be processed; that was supposed to be signaled by
+ // nsIRunnable(s).
+ //
+ // So instead of hacking Cocoa nsAppShell or rewriting the
+ // event-loop system, we compromise here by processing any tasks
+ // that might have been enqueued on MessagePump, *before*
+ // MessagePump::ScheduleWork was able to successfully
+ // DispatchToMainThread().
+ MessageLoop* loop = MessageLoop::current();
+ bool couldNest = loop->NestableTasksAllowed();
+
+ loop->SetNestableTasksAllowed(true);
+ RefPtr<Runnable> task = new MessageLoop::QuitTask();
+ loop->PostTask(task.forget());
+ loop->Run();
+
+ loop->SetNestableTasksAllowed(couldNest);
+ }
+#endif // XP_MACOSX
return appShell->Run();
}
@@ -589,6 +719,16 @@ XRE_ShutdownChildProcess()
// (4) ProcessChild joins the IO thread
// (5) exit()
MessageLoop::current()->Quit();
+#if defined(XP_MACOSX)
+ nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID));
+ if (appShell) {
+ // On Mac, we might be only above nsAppShell::Run(), not
+ // MessagePump::Run(). See XRE_RunAppShell(). To account for
+ // that case, we fire off an Exit() here. If we were indeed
+ // above MessagePump::Run(), this Exit() is just superfluous.
+ appShell->Exit();
+ }
+#endif // XP_MACOSX
}
namespace {
diff --git a/toolkit/xre/nsSigHandlers.cpp b/toolkit/xre/nsSigHandlers.cpp
index 68d909848c..11648e45ac 100644
--- a/toolkit/xre/nsSigHandlers.cpp
+++ b/toolkit/xre/nsSigHandlers.cpp
@@ -154,6 +154,22 @@ static void fpehandler(int signum, siginfo_t *si, void *context)
NS_DebugBreak(NS_DEBUG_ABORT, "Divide by zero", nullptr, __FILE__, __LINE__);
}
+#ifdef XP_MACOSX
+ ucontext_t *uc = (ucontext_t *)context;
+
+#if defined(__i386__) || defined(__amd64__)
+ _STRUCT_FP_CONTROL *ctrl = &uc->uc_mcontext->__fs.__fpu_fcw;
+ ctrl->__invalid = ctrl->__denorm = ctrl->__zdiv = ctrl->__ovrfl = ctrl->__undfl = ctrl->__precis = 1;
+
+ _STRUCT_FP_STATUS *status = &uc->uc_mcontext->__fs.__fpu_fsw;
+ status->__invalid = status->__denorm = status->__zdiv = status->__ovrfl = status->__undfl =
+ status->__precis = status->__stkflt = status->__errsumm = 0;
+
+ uint32_t *mxcsr = &uc->uc_mcontext->__fs.__fpu_mxcsr;
+ *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */
+ *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */
+#endif
+#endif
#if defined(LINUX)
ucontext_t *uc = (ucontext_t *)context;
diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp
index 812818788c..4994458852 100644
--- a/toolkit/xre/nsUpdateDriver.cpp
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
@@ -26,6 +27,13 @@
#include "nsPrintfCString.h"
#include "mozilla/DebugOnly.h"
+#ifdef XP_MACOSX
+#include "nsILocalFileMac.h"
+#include "nsCommandLineServiceMac.h"
+#include "MacLaunchHelper.h"
+#include "updaterfileutils_osx.h"
+#endif
+
#if defined(XP_WIN)
# include <direct.h>
# include <process.h>
@@ -53,11 +61,16 @@ GetUpdateLog()
#ifdef XP_WIN
#define UPDATER_BIN "updater.exe"
+#elif XP_MACOSX
+#define UPDATER_BIN "org.mozilla.updater"
#else
#define UPDATER_BIN "updater"
#endif
#define UPDATER_INI "updater.ini"
-#if defined(XP_UNIX)
+#ifdef XP_MACOSX
+#define UPDATER_APP "updater.app"
+#endif
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
#define UPDATER_PNG "updater.png"
#endif
@@ -96,7 +109,18 @@ static nsresult
GetInstallDirPath(nsIFile *appDir, nsACString& installDirPath)
{
nsresult rv;
-#if XP_WIN
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIFile> parentDir1, parentDir2;
+ rv = appDir->GetParent(getter_AddRefs(parentDir1));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = parentDir1->GetParent(getter_AddRefs(parentDir2));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = parentDir2->GetNativePath(installDirPath);
+#elif XP_WIN
nsAutoString installDirPathW;
rv = appDir->GetPath(installDirPathW);
if (NS_FAILED(rv)) {
@@ -112,6 +136,35 @@ GetInstallDirPath(nsIFile *appDir, nsACString& installDirPath)
return NS_OK;
}
+#if defined(XP_MACOSX)
+// This is a copy of OS X's XRE_GetBinaryPath from nsAppRunner.cpp with the
+// gBinaryPath check removed so that the updater can reload the stub executable
+// instead of xulrunner-bin. See bug 349737.
+static nsresult
+GetXULRunnerStubPath(const char* argv0, nsIFile* *aResult)
+{
+ // Works even if we're not bundled.
+ CFBundleRef appBundle = ::CFBundleGetMainBundle();
+ if (!appBundle)
+ return NS_ERROR_FAILURE;
+
+ CFURLRef bundleURL = ::CFBundleCopyExecutableURL(appBundle);
+ if (!bundleURL)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsILocalFileMac> lfm;
+ nsresult rv = NS_NewLocalFileWithCFURL(bundleURL, true, getter_AddRefs(lfm));
+
+ ::CFRelease(bundleURL);
+
+ if (NS_FAILED(rv))
+ return rv;
+
+ lfm.forget(aResult);
+ return NS_OK;
+}
+#endif /* XP_MACOSX */
+
static bool
GetFile(nsIFile *dir, const nsCSubstring &name, nsCOMPtr<nsIFile> &result)
{
@@ -271,10 +324,16 @@ CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
nsCOMPtr<nsIFile> &updater)
{
// Copy the updater application from the GRE and the updater ini from the app
+#if defined(XP_MACOSX)
+ if (!CopyFileIntoUpdateDir(appDir, NS_LITERAL_CSTRING(UPDATER_APP), updateDir))
+ return false;
+ CopyFileIntoUpdateDir(greDir, NS_LITERAL_CSTRING(UPDATER_INI), updateDir);
+#else
if (!CopyFileIntoUpdateDir(greDir, NS_LITERAL_CSTRING(UPDATER_BIN), updateDir))
return false;
CopyFileIntoUpdateDir(appDir, NS_LITERAL_CSTRING(UPDATER_INI), updateDir);
-#if defined(XP_UNIX)
+#endif
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
nsCOMPtr<nsIFile> iconDir;
appDir->Clone(getter_AddRefs(iconDir));
iconDir->AppendNative(NS_LITERAL_CSTRING("icons"));
@@ -285,6 +344,16 @@ CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
nsresult rv = updateDir->Clone(getter_AddRefs(updater));
if (NS_FAILED(rv))
return false;
+#if defined(XP_MACOSX)
+ rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_APP));
+ nsresult tmp = updater->AppendNative(NS_LITERAL_CSTRING("Contents"));
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = updater->AppendNative(NS_LITERAL_CSTRING("MacOS"));
+ if (NS_FAILED(tmp) || NS_FAILED(rv))
+ return false;
+#endif
rv = updater->AppendNative(NS_LITERAL_CSTRING(UPDATER_BIN));
return NS_SUCCEEDED(rv);
}
@@ -295,7 +364,8 @@ CopyUpdaterIntoUpdateDir(nsIFile *greDir, nsIFile *appDir, nsIFile *updateDir,
*
* @param pathToAppend A new library path to prepend to LD_LIBRARY_PATH
*/
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN)
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \
+ !defined(XP_MACOSX)
#include "prprf.h"
#define PATH_SEPARATOR ":"
#define LD_LIBRARY_PATH_ENVVAR_NAME "LD_LIBRARY_PATH"
@@ -372,7 +442,13 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
// to restart the running application.
nsCOMPtr<nsIFile> appFile;
+#if defined(XP_MACOSX)
+ // On OS X we need to pass the location of the xulrunner-stub executable
+ // rather than xulrunner-bin. See bug 349737.
+ GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
+#else
XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
+#endif
if (!appFile)
return;
@@ -424,7 +500,11 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
// Get the directory where the update will be staged.
nsAutoCString applyToDir;
nsCOMPtr<nsIFile> updatedDir;
+#ifdef XP_MACOSX
+ if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
+#else
if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
+#endif
return;
}
#ifdef XP_WIN
@@ -468,7 +548,7 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
// Construct the PID argument for this process. We start the updater using
// execv on all Unix platforms except Mac, so on those platforms we pass 0
// instead of a good PID to signal the updater not to try and wait for us.
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) & !defined(XP_MACOSX)
nsAutoCString pid("0");
#else
nsAutoCString pid;
@@ -508,13 +588,14 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
if (gSafeMode) {
PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
}
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN)
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \
+ !defined(XP_MACOSX)
AppendToLibPath(installDirPath.get());
#endif
LOG(("spawning updater process for replacing [%s]\n", updaterPath.get()));
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) & !defined(XP_MACOSX)
exit(execv(updaterPath.get(), argv));
#elif defined(XP_WIN)
// Switch the application using updater.exe
@@ -522,6 +603,10 @@ SwitchToUpdatedApp(nsIFile *greDir, nsIFile *updateDir,
return;
}
_exit(0);
+#elif defined(XP_MACOSX)
+ CommandLineServiceMac::SetupMacCommandLine(argc, argv, true);
+ LaunchChildMac(argc, argv);
+ exit(0);
#else
PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr);
exit(0);
@@ -567,7 +652,13 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
// to restart the running application.
nsCOMPtr<nsIFile> appFile;
+#if defined(XP_MACOSX)
+ // On OS X we need to pass the location of the xulrunner-stub executable
+ // rather than xulrunner-bin. See bug 349737.
+ GetXULRunnerStubPath(appArgv[0], getter_AddRefs(appFile));
+#else
XRE_GetBinaryPath(appArgv[0], getter_AddRefs(appFile));
+#endif
if (!appFile)
return;
@@ -623,7 +714,11 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
if (restart && !isOSUpdate) {
applyToDir.Assign(installDirPath);
} else {
+#ifdef XP_MACOSX
+ if (!GetFile(updateDir, NS_LITERAL_CSTRING("Updated.app"), updatedDir)) {
+#else
if (!GetFile(appDir, NS_LITERAL_CSTRING("updated"), updatedDir)) {
+#endif
return;
}
#ifdef XP_WIN
@@ -678,7 +773,7 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
// Signal the updater application that it should stage the update.
pid.AssignASCII("-1");
} else {
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) & !defined(XP_MACOSX)
pid.AssignASCII("0");
#else
pid.AppendInt((int32_t) getpid());
@@ -714,7 +809,8 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
if (gSafeMode) {
PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
}
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN)
+#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && \
+ !defined(XP_MACOSX)
AppendToLibPath(installDirPath.get());
#endif
@@ -724,10 +820,10 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
LOG(("spawning updater process [%s]\n", updaterPath.get()));
-#if defined(XP_UNIX)
- // We use execv to spawn the updater process on all UNIX-like systems.
- // Windows has execv, but it is a faked implementation that doesn't really
- // replace the current process.
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
+ // We use execv to spawn the updater process on all UNIX systems except Mac OSX
+ // since it is known to cause problems on the Mac. Windows has execv, but it
+ // is a faked implementation that doesn't really replace the current process.
// Instead it spawns a new process, so we gain nothing from using execv on
// Windows.
if (restart) {
@@ -749,6 +845,24 @@ ApplyUpdate(nsIFile *greDir, nsIFile *updateDir, nsIFile *statusFile,
// We are going to process an update so we should exit now
_exit(0);
}
+#elif defined(XP_MACOSX)
+ CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart);
+ // We need to detect whether elevation is required for this update. This can
+ // occur when an admin user installs the application, but another admin
+ // user attempts to update (see bug 394984).
+ if (restart && !IsRecursivelyWritable(installDirPath.get())) {
+ if (!LaunchElevatedUpdate(argc, argv, outpid)) {
+ LOG(("Failed to launch elevated update!"));
+ exit(1);
+ }
+ exit(0);
+ } else {
+ if (restart) {
+ LaunchChildMac(argc, argv);
+ exit(0);
+ }
+ LaunchChildMac(argc, argv, outpid);
+ }
#else
*outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr);
if (restart) {
@@ -769,6 +883,9 @@ ProcessHasTerminated(ProcessType pt)
}
CloseHandle(pt);
return true;
+#elif defined(XP_MACOSX)
+ // We're waiting for the process to terminate in LaunchChildMac.
+ return true;
#elif defined(XP_UNIX)
int exitStatus;
pid_t exited = waitpid(pt, &exitStatus, WNOHANG);
diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp
index 7997b53b16..e38cc4f35b 100644
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -48,11 +48,19 @@
#include <windows.h>
#include <shlobj.h>
#endif
+#ifdef XP_MACOSX
+#include "nsILocalFileMac.h"
+// for chflags()
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
#ifdef XP_UNIX
#include <ctype.h>
#endif
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+#define APP_REGISTRY_NAME "Application Registry"
+#elif defined(XP_WIN)
#define APP_REGISTRY_NAME "registry.dat"
#else
#define APP_REGISTRY_NAME "appreg"
@@ -100,6 +108,9 @@ nsXREDirProvider::Initialize(nsIFile *aXULAppDir,
mXULAppDir = aXULAppDir;
mGREDir = aGREDir;
mGREDir->Clone(getter_AddRefs(mGREBinDir));
+#ifdef XP_MACOSX
+ mGREBinDir->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
+#endif
if (!mProfileDir) {
nsCOMPtr<nsIDirectoryServiceProvider> app(do_QueryInterface(mAppProvider));
@@ -129,6 +140,30 @@ nsXREDirProvider::SetProfile(nsIFile* aDir, nsIFile* aLocalDir)
if (NS_FAILED(rv))
return rv;
+#ifdef XP_MACOSX
+ bool same;
+ if (NS_SUCCEEDED(aDir->Equals(aLocalDir, &same)) && !same) {
+ // Ensure that the cache directory is not indexed by Spotlight
+ // (bug 718910). At least on OS X, the cache directory (under
+ // ~/Library/Caches/) is always the "local" user profile
+ // directory. This is confusing, since *both* user profile
+ // directories are "local" (they both exist under the user's
+ // home directory). But this usage dates back at least as far
+ // as the patch for bug 291033, where "local" seems to mean
+ // "suitable for temporary storage". Don't hide the cache
+ // directory if by some chance it and the "non-local" profile
+ // directory are the same -- there are bad side effects from
+ // hiding a profile directory under /Library/Application Support/
+ // (see bug 801883).
+ nsAutoCString cacheDir;
+ if (NS_SUCCEEDED(aLocalDir->GetNativePath(cacheDir))) {
+ if (chflags(cacheDir.get(), UF_HIDDEN)) {
+ NS_WARNING("Failed to set Cache directory to HIDDEN.");
+ }
+ }
+ }
+#endif
+
mProfileDir = aDir;
mProfileLocalDir = aLocalDir;
return NS_OK;
@@ -163,7 +198,7 @@ nsXREDirProvider::GetUserProfilesRootDir(nsIFile** aResult,
aProfileName, aAppName, aVendorName);
if (NS_SUCCEEDED(rv)) {
-#if !defined(XP_UNIX)
+#if !defined(XP_UNIX) || defined(XP_MACOSX)
rv = file->AppendNative(NS_LITERAL_CSTRING("Profiles"));
#endif
// We must create the profile directory here if it does not exist.
@@ -188,7 +223,7 @@ nsXREDirProvider::GetUserProfilesLocalDir(nsIFile** aResult,
aProfileName, aAppName, aVendorName);
if (NS_SUCCEEDED(rv)) {
-#if !defined(XP_UNIX)
+#if !defined(XP_UNIX) || defined(XP_MACOSX)
rv = file->AppendNative(NS_LITERAL_CSTRING("Profiles"));
#endif
// We must create the profile directory here if it does not exist.
@@ -201,7 +236,7 @@ nsXREDirProvider::GetUserProfilesLocalDir(nsIFile** aResult,
return NS_OK;
}
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
/**
* Get the directory that is the parent of the system-wide directories
* for extensions and native-messaing manifests.
@@ -215,6 +250,12 @@ GetSystemParentDirectory(nsIFile** aFile)
{
nsresult rv;
nsCOMPtr<nsIFile> localDir;
+#if defined(XP_MACOSX)
+ rv = GetOSXFolderType(kOnSystemDisk, kApplicationSupportFolderType, getter_AddRefs(localDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla"));
+ }
+#else
NS_NAMED_LITERAL_CSTRING(dirname,
#ifdef HAVE_USR_LIB64_DIR
"/usr/lib64/mozilla"
@@ -225,6 +266,7 @@ GetSystemParentDirectory(nsIFile** aFile)
#endif
);
rv = NS_NewNativeLocalFile(dirname, false, getter_AddRefs(localDir));
+#endif
if (NS_SUCCEEDED(rv)) {
localDir.forget(aFile);
@@ -308,14 +350,18 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
!strcmp(aProperty, XRE_USER_APP_DATA_DIR)) {
rv = GetUserAppDataDirectory(getter_AddRefs(file));
}
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
else if (!strcmp(aProperty, XRE_SYS_NATIVE_MESSAGING_MANIFESTS)) {
nsCOMPtr<nsIFile> localDir;
rv = ::GetSystemParentDirectory(getter_AddRefs(localDir));
if (NS_SUCCEEDED(rv)) {
NS_NAMED_LITERAL_CSTRING(dirname,
+#if defined(XP_MACOSX)
+ "NativeMessagingHosts"
+#else
"native-messaging-hosts"
+#endif
);
rv = localDir->AppendNative(dirname);
if (NS_SUCCEEDED(rv)) {
@@ -327,10 +373,17 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
nsCOMPtr<nsIFile> localDir;
rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false);
if (NS_SUCCEEDED(rv)) {
+#if defined(XP_MACOSX)
+ rv = localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla"));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localDir->AppendNative(NS_LITERAL_CSTRING("NativeMessagingHosts"));
+ }
+#else
rv = localDir->AppendNative(NS_LITERAL_CSTRING(".mozilla"));
if (NS_SUCCEEDED(rv)) {
rv = localDir->AppendNative(NS_LITERAL_CSTRING("native-messaging-hosts"));
}
+#endif
}
if (NS_SUCCEEDED(rv)) {
localDir.swap(file);
@@ -372,7 +425,7 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
return mAppProvider->GetFile(NS_APP_PROFILE_DIR_STARTUP, aPersistent,
aFile);
}
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
else if (!strcmp(aProperty, XRE_SYS_LOCAL_EXTENSION_PARENT_DIR)) {
#ifdef ENABLE_SYSTEM_EXTENSION_DIRS
return GetSystemExtensionsDirectory(aFile);
@@ -381,7 +434,7 @@ nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent,
#endif
}
#endif
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
else if (!strcmp(aProperty, XRE_SYS_SHARE_EXTENSION_PARENT_DIR)) {
#ifdef ENABLE_SYSTEM_EXTENSION_DIRS
#if defined(__OpenBSD__) || defined(__FreeBSD__)
@@ -1050,7 +1103,42 @@ nsXREDirProvider::GetUpdateRootDir(nsIFile* *aResult)
rv = appFile->GetParent(getter_AddRefs(updRoot));
NS_ENSURE_SUCCESS(rv, rv);
-#ifdef XP_WIN
+#ifdef XP_MACOSX
+ nsCOMPtr<nsIFile> appRootDirFile;
+ nsCOMPtr<nsIFile> localDir;
+ nsAutoString appDirPath;
+ if (NS_FAILED(appFile->GetParent(getter_AddRefs(appRootDirFile))) ||
+ NS_FAILED(appRootDirFile->GetPath(appDirPath)) ||
+ NS_FAILED(GetUserDataDirectoryHome(getter_AddRefs(localDir), true))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t dotIndex = appDirPath.RFind(".app");
+ if (dotIndex == kNotFound) {
+ dotIndex = appDirPath.Length();
+ }
+ appDirPath = Substring(appDirPath, 1, dotIndex - 1);
+
+ bool hasVendor = gAppData->vendor && strlen(gAppData->vendor) != 0;
+ if (hasVendor || gAppData->name) {
+ if (NS_FAILED(localDir->AppendNative(nsDependentCString(hasVendor ?
+ gAppData->vendor :
+ gAppData->name)))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else if (NS_FAILED(localDir->AppendNative(NS_LITERAL_CSTRING("Mozilla")))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(localDir->Append(NS_LITERAL_STRING("updates"))) ||
+ NS_FAILED(localDir->AppendRelativePath(appDirPath))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ localDir.forget(aResult);
+ return NS_OK;
+
+#elif XP_WIN
nsAutoString pathHash;
bool pathHashResult = false;
bool hasVendor = gAppData->vendor && strlen(gAppData->vendor) != 0;
@@ -1192,7 +1280,32 @@ nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal)
nsresult rv;
nsCOMPtr<nsIFile> localDir;
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+ FSRef fsRef;
+ OSType folderType;
+ if (aLocal) {
+ folderType = kCachedDataFolderType;
+ } else {
+#ifdef MOZ_THUNDERBIRD
+ folderType = kDomainLibraryFolderType;
+#else
+ folderType = kApplicationSupportFolderType;
+#endif
+ }
+ OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef);
+ NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
+
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILocalFileMac> dirFileMac = do_QueryInterface(localDir);
+ NS_ENSURE_TRUE(dirFileMac, NS_ERROR_UNEXPECTED);
+
+ rv = dirFileMac->InitWithFSRef(&fsRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ localDir = do_QueryInterface(dirFileMac, &rv);
+#elif defined(XP_WIN)
nsString path;
if (aLocal) {
rv = GetShellFolderPath(CSIDL_LOCAL_APPDATA, path);
@@ -1255,7 +1368,7 @@ nsXREDirProvider::GetSysUserExtensionsDirectory(nsIFile** aFile)
return NS_OK;
}
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
nsresult
nsXREDirProvider::GetSystemExtensionsDirectory(nsIFile** aFile)
{
@@ -1265,7 +1378,11 @@ nsXREDirProvider::GetSystemExtensionsDirectory(nsIFile** aFile)
rv = GetSystemParentDirectory(getter_AddRefs(localDir));
if (NS_SUCCEEDED(rv)) {
NS_NAMED_LITERAL_CSTRING(sExtensions,
+#if defined(XP_MACOSX)
+ "Extensions"
+#else
"extensions"
+#endif
);
rv = localDir->AppendNative(sExtensions);
@@ -1332,7 +1449,7 @@ nsXREDirProvider::AppendSysUserExtensionPath(nsIFile* aFile)
nsresult rv;
-#if defined(XP_WIN)
+#if defined (XP_MACOSX) || defined(XP_WIN)
static const char* const sXR = "Mozilla";
rv = aFile->AppendNative(nsDependentCString(sXR));
@@ -1391,7 +1508,19 @@ nsXREDirProvider::AppendProfilePath(nsIFile* aFile,
nsresult rv;
-#if defined(XP_WIN)
+#if defined (XP_MACOSX)
+ if (!profile.IsEmpty()) {
+ rv = AppendProfileString(aFile, profile.get());
+ }
+ else {
+ // Note that MacOS ignores the vendor when creating the profile hierarchy -
+ // all application preferences directories live alongside one another in
+ // ~/Library/Application Support/
+ rv = aFile->AppendNative(appName);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#elif defined(XP_WIN)
if (!profile.IsEmpty()) {
rv = AppendProfileString(aFile, profile.get());
}
diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h
index 014317e641..655f664e6d 100644
--- a/toolkit/xre/nsXREDirProvider.h
+++ b/toolkit/xre/nsXREDirProvider.h
@@ -102,7 +102,7 @@ protected:
nsresult GetFilesInternal(const char* aProperty, nsISimpleEnumerator** aResult);
static nsresult GetUserDataDirectoryHome(nsIFile* *aFile, bool aLocal);
static nsresult GetSysUserExtensionsDirectory(nsIFile* *aFile);
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
static nsresult GetSystemExtensionsDirectory(nsIFile** aFile);
#endif
static nsresult EnsureDirectoryExists(nsIFile* aDirectory);
diff --git a/tools/fuzzing/libfuzzer/harness/LibFuzzerTestHarness.h b/tools/fuzzing/libfuzzer/harness/LibFuzzerTestHarness.h
index 70321a5748..6ac2344e31 100644
--- a/tools/fuzzing/libfuzzer/harness/LibFuzzerTestHarness.h
+++ b/tools/fuzzing/libfuzzer/harness/LibFuzzerTestHarness.h
@@ -193,6 +193,14 @@ static class ScopedXPCOM : public nsIDirectoryServiceProvider2
}
greD->Clone(getter_AddRefs(mGREBinD));
+#ifdef XP_MACOSX
+ nsAutoCString leafName;
+ mGREBinD->GetNativeLeafName(leafName);
+ if (leafName.Equals("Resources")) {
+ mGREBinD->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
+ }
+#endif
+
nsCOMPtr<nsIFile> copy = mGREBinD;
return copy.forget();
}
diff --git a/tools/profiler/tasktracer/GeckoTaskTracer.cpp b/tools/profiler/tasktracer/GeckoTaskTracer.cpp
index aefcb274ec..36d1bffc38 100644
--- a/tools/profiler/tasktracer/GeckoTaskTracer.cpp
+++ b/tools/profiler/tasktracer/GeckoTaskTracer.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
@@ -25,6 +26,10 @@
#include <unistd.h>
#include <sys/syscall.h>
#define gettid() static_cast<pid_t>(syscall(SYS_gettid))
+#elif defined(XP_MACOSX)
+#include <unistd.h>
+#include <sys/syscall.h>
+#define gettid() static_cast<pid_t>(syscall(SYS_thread_selfid))
#elif defined(LINUX)
#include <sys/types.h>
pid_t gettid();
diff --git a/uriloader/exthandler/mac/nsDecodeAppleFile.cpp b/uriloader/exthandler/mac/nsDecodeAppleFile.cpp
new file mode 100644
index 0000000000..1a428a62db
--- /dev/null
+++ b/uriloader/exthandler/mac/nsDecodeAppleFile.cpp
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsDecodeAppleFile.h"
+#include "prmem.h"
+#include "nsCRT.h"
+
+
+NS_IMPL_ADDREF(nsDecodeAppleFile)
+NS_IMPL_RELEASE(nsDecodeAppleFile)
+
+NS_INTERFACE_MAP_BEGIN(nsDecodeAppleFile)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsDecodeAppleFile::nsDecodeAppleFile()
+{
+ m_state = parseHeaders;
+ m_dataBufferLength = 0;
+ m_dataBuffer = (unsigned char*) PR_MALLOC(MAX_BUFFERSIZE);
+ m_entries = nullptr;
+ m_rfRefNum = -1;
+ m_totalDataForkWritten = 0;
+ m_totalResourceForkWritten = 0;
+ m_headerOk = false;
+
+ m_comment[0] = 0;
+ memset(&m_dates, 0, sizeof(m_dates));
+ memset(&m_finderInfo, 0, sizeof(m_dates));
+ memset(&m_finderExtraInfo, 0, sizeof(m_dates));
+}
+
+nsDecodeAppleFile::~nsDecodeAppleFile()
+{
+
+ PR_FREEIF(m_dataBuffer);
+ if (m_entries)
+ delete [] m_entries;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Initialize(nsIOutputStream *output, nsIFile *file)
+{
+ m_output = output;
+
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(file);
+ macFile->GetTargetFSSpec(&m_fsFileSpec);
+
+ m_offset = 0;
+ m_dataForkOffset = 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Close(void)
+{
+ nsresult rv;
+ rv = m_output->Close();
+
+ int32_t i;
+
+ if (m_rfRefNum != -1)
+ FSClose(m_rfRefNum);
+
+ /* Check if the file is complete and if it's the case, write file attributes */
+ if (m_headerOk)
+ {
+ bool dataOk = true; /* It's ok if the file doesn't have a datafork, therefore set it to true by default. */
+ if (m_headers.magic == APPLESINGLE_MAGIC)
+ {
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (ENT_DFORK == m_entries[i].id)
+ {
+ dataOk = (bool)(m_totalDataForkWritten == m_entries[i].length);
+ break;
+ }
+ }
+
+ bool resourceOk = FALSE;
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (ENT_RFORK == m_entries[i].id)
+ {
+ resourceOk = (bool)(m_totalResourceForkWritten == m_entries[i].length);
+ break;
+ }
+
+ if (dataOk && resourceOk)
+ {
+ HFileInfo *fpb;
+ CInfoPBRec cipbr;
+
+ fpb = (HFileInfo *) &cipbr;
+ fpb->ioVRefNum = m_fsFileSpec.vRefNum;
+ fpb->ioDirID = m_fsFileSpec.parID;
+ fpb->ioNamePtr = m_fsFileSpec.name;
+ fpb->ioFDirIndex = 0;
+ PBGetCatInfoSync(&cipbr);
+
+ /* set finder info */
+ memcpy(&fpb->ioFlFndrInfo, &m_finderInfo, sizeof (FInfo));
+ memcpy(&fpb->ioFlXFndrInfo, &m_finderExtraInfo, sizeof (FXInfo));
+ fpb->ioFlFndrInfo.fdFlags &= 0xfc00; /* clear flags maintained by finder */
+
+ /* set file dates */
+ fpb->ioFlCrDat = m_dates.create - CONVERT_TIME;
+ fpb->ioFlMdDat = m_dates.modify - CONVERT_TIME;
+ fpb->ioFlBkDat = m_dates.backup - CONVERT_TIME;
+
+ /* update file info */
+ fpb->ioDirID = fpb->ioFlParID;
+ PBSetCatInfoSync(&cipbr);
+
+ /* set comment */
+ IOParam vinfo;
+ GetVolParmsInfoBuffer vp;
+ DTPBRec dtp;
+
+ memset((void *) &vinfo, 0, sizeof (vinfo));
+ vinfo.ioVRefNum = fpb->ioVRefNum;
+ vinfo.ioBuffer = (Ptr) &vp;
+ vinfo.ioReqCount = sizeof (vp);
+ if (PBHGetVolParmsSync((HParmBlkPtr) &vinfo) == noErr && ((vp.vMAttrib >> bHasDesktopMgr) & 1))
+ {
+ memset((void *) &dtp, 0, sizeof (dtp));
+ dtp.ioVRefNum = fpb->ioVRefNum;
+ if (PBDTGetPath(&dtp) == noErr)
+ {
+ dtp.ioDTBuffer = (Ptr) &m_comment[1];
+ dtp.ioNamePtr = fpb->ioNamePtr;
+ dtp.ioDirID = fpb->ioDirID;
+ dtp.ioDTReqCount = m_comment[0];
+ if (PBDTSetCommentSync(&dtp) == noErr)
+ PBDTFlushSync(&dtp);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Flush(void)
+{
+ return m_output->Flush();
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval)
+{
+ return m_output->WriteFrom(inStr, count, _retval);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval)
+{
+ return m_output->WriteSegments(reader, closure, count, _retval);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::IsNonBlocking(bool *aNonBlocking)
+{
+ return m_output->IsNonBlocking(aNonBlocking);
+}
+
+NS_IMETHODIMP nsDecodeAppleFile::Write(const char *buffer, uint32_t bufferSize, uint32_t* writeCount)
+{
+ /* WARNING: to simplify my life, I presume that I should get all appledouble headers in the first block,
+ else I would have to implement a buffer */
+
+ const char * buffPtr = buffer;
+ uint32_t dataCount;
+ int32_t i;
+ nsresult rv = NS_OK;
+
+ *writeCount = 0;
+
+ while (bufferSize > 0 && NS_SUCCEEDED(rv))
+ {
+ switch (m_state)
+ {
+ case parseHeaders :
+ dataCount = sizeof(ap_header) - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == sizeof(ap_header))
+ {
+ memcpy(&m_headers, m_dataBuffer, sizeof(ap_header));
+
+ /* Check header to be sure we are dealing with the right kind of data, else just write it to the data fork. */
+ if ((m_headers.magic == APPLEDOUBLE_MAGIC || m_headers.magic == APPLESINGLE_MAGIC) &&
+ m_headers.version == VERSION && m_headers.entriesCount)
+ {
+ /* Just to be sure, the filler must contains only 0 */
+ for (i = 0; i < 4 && m_headers.fill[i] == 0L; i ++)
+ ;
+ if (i == 4)
+ m_state = parseEntries;
+ }
+ m_dataBufferLength = 0;
+
+ if (m_state == parseHeaders)
+ {
+ dataCount = 0;
+ m_state = parseWriteThrough;
+ }
+ }
+ break;
+
+ case parseEntries :
+ if (!m_entries)
+ {
+ m_entries = new ap_entry[m_headers.entriesCount];
+ if (!m_entries)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ uint32_t entriesSize = sizeof(ap_entry) * m_headers.entriesCount;
+ dataCount = entriesSize - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == entriesSize)
+ {
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ {
+ memcpy(&m_entries[i], &m_dataBuffer[i * sizeof(ap_entry)], sizeof(ap_entry));
+ if (m_headers.magic == APPLEDOUBLE_MAGIC)
+ {
+ uint32_t offset = m_entries[i].offset + m_entries[i].length;
+ if (offset > m_dataForkOffset)
+ m_dataForkOffset = offset;
+ }
+ }
+ m_headerOk = true;
+ m_state = parseLookupPart;
+ }
+ break;
+
+ case parseLookupPart :
+ /* which part are we parsing? */
+ m_currentPartID = -1;
+ for (i = 0; i < m_headers.entriesCount; i ++)
+ if (m_offset == m_entries[i].offset && m_entries[i].length)
+ {
+ m_currentPartID = m_entries[i].id;
+ m_currentPartLength = m_entries[i].length;
+ m_currentPartCount = 0;
+
+ switch (m_currentPartID)
+ {
+ case ENT_DFORK : m_state = parseDataFork; break;
+ case ENT_RFORK : m_state = parseResourceFork; break;
+
+ case ENT_COMMENT :
+ case ENT_DATES :
+ case ENT_FINFO :
+ m_dataBufferLength = 0;
+ m_state = parsePart;
+ break;
+
+ default : m_state = parseSkipPart; break;
+ }
+ break;
+ }
+
+ if (m_currentPartID == -1)
+ {
+ /* maybe is the datafork of an appledouble file? */
+ if (m_offset == m_dataForkOffset)
+ {
+ m_currentPartID = ENT_DFORK;
+ m_currentPartLength = -1;
+ m_currentPartCount = 0;
+ m_state = parseDataFork;
+ }
+ else
+ dataCount = 1;
+ }
+ break;
+
+ case parsePart :
+ dataCount = m_currentPartLength - m_dataBufferLength;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ memcpy(&m_dataBuffer[m_dataBufferLength], buffPtr, dataCount);
+ m_dataBufferLength += dataCount;
+
+ if (m_dataBufferLength == m_currentPartLength)
+ {
+ switch (m_currentPartID)
+ {
+ case ENT_COMMENT :
+ m_comment[0] = m_currentPartLength > 255 ? 255 : m_currentPartLength;
+ memcpy(&m_comment[1], buffPtr, m_comment[0]);
+ break;
+ case ENT_DATES :
+ if (m_currentPartLength == sizeof(m_dates))
+ memcpy(&m_dates, buffPtr, m_currentPartLength);
+ break;
+ case ENT_FINFO :
+ if (m_currentPartLength == (sizeof(m_finderInfo) + sizeof(m_finderExtraInfo)))
+ {
+ memcpy(&m_finderInfo, buffPtr, sizeof(m_finderInfo));
+ memcpy(&m_finderExtraInfo, buffPtr + sizeof(m_finderInfo), sizeof(m_finderExtraInfo));
+ }
+ break;
+ }
+ m_state = parseLookupPart;
+ }
+ break;
+
+ case parseSkipPart :
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+ break;
+
+ case parseDataFork :
+ if (m_headers.magic == APPLEDOUBLE_MAGIC)
+ dataCount = bufferSize;
+ else
+ {
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+ }
+
+ if (m_output)
+ {
+ uint32_t writeCount;
+ rv = m_output->Write((const char *)buffPtr, dataCount, &writeCount);
+ if (dataCount != writeCount)
+ rv = NS_ERROR_FAILURE;
+ m_totalDataForkWritten += dataCount;
+ }
+
+ break;
+
+ case parseResourceFork :
+ dataCount = m_currentPartLength - m_currentPartCount;
+ if (dataCount > bufferSize)
+ dataCount = bufferSize;
+ else
+ m_state = parseLookupPart;
+
+ if (m_rfRefNum == -1)
+ {
+ if (noErr != FSpOpenRF(&m_fsFileSpec, fsWrPerm, &m_rfRefNum))
+ return NS_ERROR_FAILURE;
+ }
+
+ long count = dataCount;
+ if (noErr != FSWrite(m_rfRefNum, &count, buffPtr) || count != dataCount)
+ return NS_ERROR_FAILURE;
+ m_totalResourceForkWritten += dataCount;
+ break;
+
+ case parseWriteThrough :
+ dataCount = bufferSize;
+ if (m_output)
+ {
+ uint32_t writeCount;
+ rv = m_output->Write((const char *)buffPtr, dataCount, &writeCount);
+ if (dataCount != writeCount)
+ rv = NS_ERROR_FAILURE;
+ }
+ break;
+ }
+
+ if (dataCount)
+ {
+ *writeCount += dataCount;
+ bufferSize -= dataCount;
+ buffPtr += dataCount;
+ m_currentPartCount += dataCount;
+ m_offset += dataCount;
+ dataCount = 0;
+ }
+ }
+
+ return rv;
+}
diff --git a/uriloader/exthandler/mac/nsDecodeAppleFile.h b/uriloader/exthandler/mac/nsDecodeAppleFile.h
new file mode 100644
index 0000000000..cea2d701ec
--- /dev/null
+++ b/uriloader/exthandler/mac/nsDecodeAppleFile.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsDecodeAppleFile_h__
+#define nsDecodeAppleFile_h__
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsIOutputStream.h"
+
+/*
+** applefile definitions used
+*/
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=mac68k
+#endif
+
+#define APPLESINGLE_MAGIC 0x00051600L
+#define APPLEDOUBLE_MAGIC 0x00051607L
+#define VERSION 0x00020000
+
+#define NUM_ENTRIES 6
+
+#define ENT_DFORK 1L
+#define ENT_RFORK 2L
+#define ENT_NAME 3L
+#define ENT_COMMENT 4L
+#define ENT_DATES 8L
+#define ENT_FINFO 9L
+
+#define CONVERT_TIME 1265437696L
+
+/*
+** data type used in the header decoder.
+*/
+typedef struct ap_header
+{
+ int32_t magic;
+ int32_t version;
+ int32_t fill[4];
+ int16_t entriesCount;
+
+} ap_header;
+
+typedef struct ap_entry
+{
+ int32_t id;
+ int32_t offset;
+ int32_t length;
+
+} ap_entry;
+
+typedef struct ap_dates
+{
+ int32_t create, modify, backup, access;
+
+} ap_dates;
+
+#if PRAGMA_STRUCT_ALIGN
+ #pragma options align=reset
+#endif
+
+/*
+**Error codes
+*/
+enum {
+ errADNotEnoughData = -12099,
+ errADNotSupported,
+ errADBadVersion
+};
+
+
+class nsDecodeAppleFile : public nsIOutputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsDecodeAppleFile();
+ virtual ~nsDecodeAppleFile();
+
+ MOZ_MUST_USE nsresult Initialize(nsIOutputStream *output, nsIFile *file);
+
+private:
+ #define MAX_BUFFERSIZE 1024
+ enum ParserState {parseHeaders, parseEntries, parseLookupPart, parsePart, parseSkipPart,
+ parseDataFork, parseResourceFork, parseWriteThrough};
+
+ nsCOMPtr<nsIOutputStream> m_output;
+ FSSpec m_fsFileSpec;
+ SInt16 m_rfRefNum;
+
+ unsigned char * m_dataBuffer;
+ int32_t m_dataBufferLength;
+ ParserState m_state;
+ ap_header m_headers;
+ ap_entry * m_entries;
+ int32_t m_offset;
+ int32_t m_dataForkOffset;
+ int32_t m_totalDataForkWritten;
+ int32_t m_totalResourceForkWritten;
+ bool m_headerOk;
+
+ int32_t m_currentPartID;
+ int32_t m_currentPartLength;
+ int32_t m_currentPartCount;
+
+ Str255 m_comment;
+ ap_dates m_dates;
+ FInfo m_finderInfo;
+ FXInfo m_finderExtraInfo;
+};
+
+#endif
diff --git a/uriloader/exthandler/mac/nsLocalHandlerAppMac.h b/uriloader/exthandler/mac/nsLocalHandlerAppMac.h
new file mode 100644
index 0000000000..402ec52953
--- /dev/null
+++ b/uriloader/exthandler/mac/nsLocalHandlerAppMac.h
@@ -0,0 +1,26 @@
+/* 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/. */
+
+#ifndef NSLOCALHANDLERAPPMAC_H_
+#define NSLOCALHANDLERAPPMAC_H_
+
+#include "nsLocalHandlerApp.h"
+
+class nsLocalHandlerAppMac : public nsLocalHandlerApp {
+
+ public:
+ nsLocalHandlerAppMac() { }
+
+ nsLocalHandlerAppMac(const char16_t *aName, nsIFile *aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable) {}
+
+ nsLocalHandlerAppMac(const nsAString & aName, nsIFile *aExecutable)
+ : nsLocalHandlerApp(aName, aExecutable) {}
+ virtual ~nsLocalHandlerAppMac() { }
+
+ NS_IMETHOD LaunchWithURI(nsIURI* aURI, nsIInterfaceRequestor* aWindowContext);
+ NS_IMETHOD GetName(nsAString& aName);
+};
+
+#endif /*NSLOCALHANDLERAPPMAC_H_*/
diff --git a/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm b/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm
new file mode 100644
index 0000000000..fd633ab726
--- /dev/null
+++ b/uriloader/exthandler/mac/nsLocalHandlerAppMac.mm
@@ -0,0 +1,84 @@
+/* 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/. */
+
+#import <CoreFoundation/CoreFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+#include "nsObjCExceptions.h"
+#include "nsLocalHandlerAppMac.h"
+#include "nsILocalFileMac.h"
+#include "nsIURI.h"
+
+// We override this to make sure app bundles display their pretty name (without .app suffix)
+NS_IMETHODIMP nsLocalHandlerAppMac::GetName(nsAString& aName)
+{
+ if (mExecutable) {
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(mExecutable);
+ if (macFile) {
+ bool isPackage;
+ (void)macFile->IsPackage(&isPackage);
+ if (isPackage)
+ return macFile->GetBundleDisplayName(aName);
+ }
+ }
+
+ return nsLocalHandlerApp::GetName(aName);
+}
+
+/**
+ * mostly copy/pasted from nsMacShellService.cpp (which is in browser/,
+ * so we can't depend on it here). This code probably really wants to live
+ * somewhere more central (see bug 389922).
+ */
+NS_IMETHODIMP
+nsLocalHandlerAppMac::LaunchWithURI(nsIURI *aURI,
+ nsIInterfaceRequestor *aWindowContext)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(mExecutable, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CFURLRef appURL;
+ rv = lfm->GetCFURL(&appURL);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsAutoCString uriSpec;
+ aURI->GetAsciiSpec(uriSpec);
+
+ const UInt8* uriString = reinterpret_cast<const UInt8*>(uriSpec.get());
+ CFURLRef uri = ::CFURLCreateWithBytes(NULL, uriString, uriSpec.Length(),
+ kCFStringEncodingUTF8, NULL);
+ if (!uri) {
+ ::CFRelease(appURL);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ CFArrayRef uris = ::CFArrayCreate(NULL, reinterpret_cast<const void**>(&uri),
+ 1, NULL);
+ if (!uris) {
+ ::CFRelease(uri);
+ ::CFRelease(appURL);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ LSLaunchURLSpec launchSpec;
+ launchSpec.appURL = appURL;
+ launchSpec.itemURLs = uris;
+ launchSpec.passThruParams = NULL;
+ launchSpec.launchFlags = kLSLaunchDefaults;
+ launchSpec.asyncRefCon = NULL;
+
+ OSErr err = ::LSOpenFromURLSpec(&launchSpec, NULL);
+
+ ::CFRelease(uris);
+ ::CFRelease(uri);
+ ::CFRelease(appURL);
+
+ return err != noErr ? NS_ERROR_FAILURE : NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/uriloader/exthandler/mac/nsMIMEInfoMac.h b/uriloader/exthandler/mac/nsMIMEInfoMac.h
new file mode 100644
index 0000000000..298357f757
--- /dev/null
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.h
@@ -0,0 +1,34 @@
+/* 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/. */
+
+#ifndef nsMIMEInfoMac_h_
+#define nsMIMEInfoMac_h_
+
+#include "nsMIMEInfoImpl.h"
+
+class nsMIMEInfoMac : public nsMIMEInfoImpl {
+ public:
+ explicit nsMIMEInfoMac(const char* aMIMEType = "") : nsMIMEInfoImpl(aMIMEType) {}
+ explicit nsMIMEInfoMac(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) {}
+ nsMIMEInfoMac(const nsACString& aType, HandlerClass aClass) :
+ nsMIMEInfoImpl(aType, aClass) {}
+
+ NS_IMETHOD LaunchWithFile(nsIFile* aFile);
+ protected:
+ virtual MOZ_MUST_USE nsresult LoadUriInternal(nsIURI *aURI);
+#ifdef DEBUG
+ virtual MOZ_MUST_USE nsresult LaunchDefaultWithFile(nsIFile* aFile) {
+ NS_NOTREACHED("do not call this method, use LaunchWithFile");
+ return NS_ERROR_UNEXPECTED;
+ }
+#endif
+ static MOZ_MUST_USE nsresult OpenApplicationWithURI(nsIFile *aApplication,
+ const nsCString& aURI);
+
+ NS_IMETHOD GetDefaultDescription(nsAString& aDefaultDescription);
+
+};
+
+
+#endif
diff --git a/uriloader/exthandler/mac/nsMIMEInfoMac.mm b/uriloader/exthandler/mac/nsMIMEInfoMac.mm
new file mode 100644
index 0000000000..64d3a82dea
--- /dev/null
+++ b/uriloader/exthandler/mac/nsMIMEInfoMac.mm
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 3; 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/. */
+
+#import <ApplicationServices/ApplicationServices.h>
+
+#include "nsObjCExceptions.h"
+#include "nsMIMEInfoMac.h"
+#include "nsILocalFileMac.h"
+#include "nsIFileURL.h"
+
+// We override this to make sure app bundles display their pretty name (without .app suffix)
+NS_IMETHODIMP nsMIMEInfoMac::GetDefaultDescription(nsAString& aDefaultDescription)
+{
+ if (mDefaultApplication) {
+ nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(mDefaultApplication);
+ if (macFile) {
+ bool isPackage;
+ (void)macFile->IsPackage(&isPackage);
+ if (isPackage)
+ return macFile->GetBundleDisplayName(aDefaultDescription);
+ }
+ }
+
+ return nsMIMEInfoImpl::GetDefaultDescription(aDefaultDescription);
+}
+
+NS_IMETHODIMP
+nsMIMEInfoMac::LaunchWithFile(nsIFile *aFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<nsIFile> application;
+ nsresult rv;
+
+ NS_ASSERTION(mClass == eMIMEInfo, "only MIME infos are currently allowed"
+ "to pass content by value");
+
+ if (mPreferredAction == useHelperApp) {
+
+ // we don't yet support passing content by value (rather than reference)
+ // to web apps. at some point, we will probably want to.
+ nsCOMPtr<nsILocalHandlerApp> localHandlerApp =
+ do_QueryInterface(mPreferredApplication, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = localHandlerApp->GetExecutable(getter_AddRefs(application));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ } else if (mPreferredAction == useSystemDefault) {
+ application = mDefaultApplication;
+ }
+ else
+ return NS_ERROR_INVALID_ARG;
+
+ // if we've already got an app, just QI so we have the launchWithDoc method
+ nsCOMPtr<nsILocalFileMac> app;
+ if (application) {
+ app = do_QueryInterface(application, &rv);
+ if (NS_FAILED(rv)) return rv;
+ } else {
+ // otherwise ask LaunchServices for an app directly
+ nsCOMPtr<nsILocalFileMac> tempFile = do_QueryInterface(aFile, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ FSRef tempFileRef;
+ tempFile->GetFSRef(&tempFileRef);
+
+ FSRef appFSRef;
+ if (::LSGetApplicationForItem(&tempFileRef, kLSRolesAll, &appFSRef, nullptr) == noErr)
+ {
+ app = (do_CreateInstance("@mozilla.org/file/local;1"));
+ if (!app) return NS_ERROR_FAILURE;
+ app->InitWithFSRef(&appFSRef);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return app->LaunchWithDoc(aFile, false);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsMIMEInfoMac::LoadUriInternal(nsIURI *aURI)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsAutoCString uri;
+ aURI->GetSpec(uri);
+ if (!uri.IsEmpty()) {
+ CFURLRef myURLRef = ::CFURLCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8*)uri.get(),
+ strlen(uri.get()),
+ kCFStringEncodingUTF8,
+ NULL);
+ if (myURLRef) {
+ OSStatus status = ::LSOpenCFURLRef(myURLRef, NULL);
+ if (status == noErr)
+ rv = NS_OK;
+ ::CFRelease(myURLRef);
+ }
+ }
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.h b/uriloader/exthandler/mac/nsOSHelperAppService.h
new file mode 100644
index 0000000000..7371e1f42d
--- /dev/null
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 3; 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/. */
+
+#ifndef nsOSHelperAppService_h__
+#define nsOSHelperAppService_h__
+
+// The OS helper app service is a subclass of nsExternalHelperAppService and is implemented on each
+// platform. It contains platform specific code for finding helper applications for a given mime type
+// in addition to launching those applications. This is the Mac version.
+
+#include "nsExternalHelperAppService.h"
+#include "nsCExternalHandlerService.h"
+#include "nsMIMEInfoImpl.h"
+#include "nsCOMPtr.h"
+
+class nsOSHelperAppService : public nsExternalHelperAppService
+{
+public:
+ nsOSHelperAppService();
+ virtual ~nsOSHelperAppService();
+
+ // override nsIExternalProtocolService methods
+ NS_IMETHOD GetApplicationDescription(const nsACString& aScheme, nsAString& _retval);
+
+ // method overrides --> used to hook the mime service into internet config....
+ NS_IMETHOD GetFromTypeAndExtension(const nsACString& aType, const nsACString& aFileExt, nsIMIMEInfo ** aMIMEInfo);
+ already_AddRefed<nsIMIMEInfo> GetMIMEInfoFromOS(const nsACString& aMIMEType, const nsACString& aFileExt, bool * aFound);
+ NS_IMETHOD GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval);
+
+ // GetFileTokenForPath must be implemented by each platform.
+ // platformAppPath --> a platform specific path to an application that we got out of the
+ // rdf data source. This can be a mac file spec, a unix path or a windows path depending on the platform
+ // aFile --> an nsIFile representation of that platform application path.
+ virtual MOZ_MUST_USE nsresult GetFileTokenForPath(const char16_t * platformAppPath, nsIFile ** aFile);
+
+ MOZ_MUST_USE nsresult OSProtocolHandlerExists(const char * aScheme,
+ bool * aHandlerExists);
+
+private:
+ uint32_t mPermissions;
+};
+
+#endif // nsOSHelperAppService_h__
diff --git a/uriloader/exthandler/mac/nsOSHelperAppService.mm b/uriloader/exthandler/mac/nsOSHelperAppService.mm
new file mode 100644
index 0000000000..00f12f5448
--- /dev/null
+++ b/uriloader/exthandler/mac/nsOSHelperAppService.mm
@@ -0,0 +1,569 @@
+/* -*- Mode: C++; tab-width: 3; 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 <sys/types.h>
+#include <sys/stat.h>
+#include "nsOSHelperAppService.h"
+#include "nsObjCExceptions.h"
+#include "nsISupports.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsXPIDLString.h"
+#include "nsIURL.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsMimeTypes.h"
+#include "nsIStringBundle.h"
+#include "nsIPromptService.h"
+#include "nsMemory.h"
+#include "nsCRT.h"
+#include "nsMIMEInfoMac.h"
+#include "nsEmbedCID.h"
+
+#import <CoreFoundation/CoreFoundation.h>
+#import <ApplicationServices/ApplicationServices.h>
+
+// chrome URL's
+#define HELPERAPPLAUNCHER_BUNDLE_URL "chrome://global/locale/helperAppLauncher.properties"
+#define BRAND_BUNDLE_URL "chrome://branding/locale/brand.properties"
+
+using mozilla::LogLevel;
+
+/* This is an undocumented interface (in the Foundation framework) that has
+ * been stable since at least 10.2.8 and is still present on SnowLeopard.
+ * Furthermore WebKit has three public methods (in WebKitSystemInterface.h)
+ * that are thin wrappers around this interface's last three methods. So
+ * it's unlikely to change anytime soon. Now that we're no longer using
+ * Internet Config Services, this is the only way to look up a MIME type
+ * from an extension, or vice versa.
+ */
+@class NSURLFileTypeMappingsInternal;
+
+@interface NSURLFileTypeMappings : NSObject
+{
+ NSURLFileTypeMappingsInternal *_internal;
+}
+
++ (NSURLFileTypeMappings*)sharedMappings;
+- (NSString*)MIMETypeForExtension:(NSString*)aString;
+- (NSString*)preferredExtensionForMIMEType:(NSString*)aString;
+- (NSArray*)extensionsForMIMEType:(NSString*)aString;
+@end
+
+nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
+{
+ mode_t mask = umask(0777);
+ umask(mask);
+ mPermissions = 0666 & ~mask;
+}
+
+nsOSHelperAppService::~nsOSHelperAppService()
+{}
+
+nsresult nsOSHelperAppService::OSProtocolHandlerExists(const char * aProtocolScheme, bool * aHandlerExists)
+{
+ // CFStringCreateWithBytes() can fail even if we're not out of memory --
+ // for example if the 'bytes' parameter is something very wierd (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ CFStringRef schemeString = ::CFStringCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8*)aProtocolScheme,
+ strlen(aProtocolScheme),
+ kCFStringEncodingUTF8,
+ false);
+ if (schemeString) {
+ // LSCopyDefaultHandlerForURLScheme() can fail to find the default handler
+ // for aProtocolScheme when it's never been explicitly set (using
+ // LSSetDefaultHandlerForURLScheme()). For example, Safari is the default
+ // handler for the "http" scheme on a newly installed copy of OS X. But
+ // this (presumably) wasn't done using LSSetDefaultHandlerForURLScheme(),
+ // so LSCopyDefaultHandlerForURLScheme() will fail to find Safari. To get
+ // around this we use LSCopyAllHandlersForURLScheme() instead -- which seems
+ // never to fail.
+ // http://lists.apple.com/archives/Carbon-dev/2007/May/msg00349.html
+ // http://www.realsoftware.com/listarchives/realbasic-nug/2008-02/msg00119.html
+ CFArrayRef handlerArray = ::LSCopyAllHandlersForURLScheme(schemeString);
+ *aHandlerExists = !!handlerArray;
+ if (handlerArray)
+ ::CFRelease(handlerArray);
+ ::CFRelease(schemeString);
+ } else {
+ *aHandlerExists = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(const nsACString& aScheme, nsAString& _retval)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ CFStringRef schemeCFString =
+ ::CFStringCreateWithBytes(kCFAllocatorDefault,
+ (const UInt8 *)PromiseFlatCString(aScheme).get(),
+ aScheme.Length(),
+ kCFStringEncodingUTF8,
+ false);
+
+ if (schemeCFString) {
+ CFStringRef lookupCFString = ::CFStringCreateWithFormat(NULL, NULL, CFSTR("%@:"), schemeCFString);
+
+ if (lookupCFString) {
+ CFURLRef lookupCFURL = ::CFURLCreateWithString(NULL, lookupCFString, NULL);
+
+ if (lookupCFURL) {
+ CFURLRef appCFURL = NULL;
+ OSStatus theErr = ::LSGetApplicationForURL(lookupCFURL, kLSRolesAll, NULL, &appCFURL);
+
+ if (theErr == noErr) {
+ CFBundleRef handlerBundle = ::CFBundleCreate(NULL, appCFURL);
+
+ if (handlerBundle) {
+ // Get the human-readable name of the default handler bundle
+ CFStringRef bundleName =
+ (CFStringRef)::CFBundleGetValueForInfoDictionaryKey(handlerBundle,
+ kCFBundleNameKey);
+
+ if (bundleName) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex bundleNameLength = ::CFStringGetLength(bundleName);
+ buffer.SetLength(bundleNameLength);
+ ::CFStringGetCharacters(bundleName, CFRangeMake(0, bundleNameLength),
+ buffer.Elements());
+ _retval.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), bundleNameLength);
+ rv = NS_OK;
+ }
+
+ ::CFRelease(handlerBundle);
+ }
+
+ ::CFRelease(appCFURL);
+ }
+
+ ::CFRelease(lookupCFURL);
+ }
+
+ ::CFRelease(lookupCFString);
+ }
+
+ ::CFRelease(schemeCFString);
+ }
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsOSHelperAppService::GetFileTokenForPath(const char16_t * aPlatformAppPath, nsIFile ** aFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv;
+ nsCOMPtr<nsILocalFileMac> localFile (do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ CFURLRef pathAsCFURL;
+ CFStringRef pathAsCFString = ::CFStringCreateWithCharacters(NULL,
+ reinterpret_cast<const UniChar*>(aPlatformAppPath),
+ NS_strlen(aPlatformAppPath));
+ if (!pathAsCFString)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (::CFStringGetCharacterAtIndex(pathAsCFString, 0) == '/') {
+ // we have a Posix path
+ pathAsCFURL = ::CFURLCreateWithFileSystemPath(nullptr, pathAsCFString,
+ kCFURLPOSIXPathStyle, false);
+ if (!pathAsCFURL) {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ // if it doesn't start with a / it's not an absolute Posix path
+ // let's check if it's a HFS path left over from old preferences
+
+ // If it starts with a ':' char, it's not an absolute HFS path
+ // so bail for that, and also if it's empty
+ if (::CFStringGetLength(pathAsCFString) == 0 ||
+ ::CFStringGetCharacterAtIndex(pathAsCFString, 0) == ':')
+ {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ pathAsCFURL = ::CFURLCreateWithFileSystemPath(nullptr, pathAsCFString,
+ kCFURLHFSPathStyle, false);
+ if (!pathAsCFURL) {
+ ::CFRelease(pathAsCFString);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ rv = localFile->InitWithCFURL(pathAsCFURL);
+ ::CFRelease(pathAsCFString);
+ ::CFRelease(pathAsCFURL);
+ if (NS_FAILED(rv))
+ return rv;
+ *aFile = localFile;
+ NS_IF_ADDREF(*aFile);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsOSHelperAppService::GetFromTypeAndExtension(const nsACString& aType, const nsACString& aFileExt, nsIMIMEInfo ** aMIMEInfo)
+{
+ return nsExternalHelperAppService::GetFromTypeAndExtension(aType, aFileExt, aMIMEInfo);
+}
+
+// Returns the MIME types an application bundle explicitly claims to handle.
+// Returns NULL if aAppRef doesn't explicitly claim to handle any MIME types.
+// If the return value is non-NULL, the caller is responsible for freeing it.
+// This isn't necessarily the same as the MIME types the application bundle
+// is registered to handle in the Launch Services database. (For example
+// the Preview application is normally registered to handle the application/pdf
+// MIME type, even though it doesn't explicitly claim to handle *any* MIME
+// types in its Info.plist. This is probably because Preview does explicitly
+// claim to handle the com.adobe.pdf UTI, and Launch Services somehow
+// translates this into a claim to support the application/pdf MIME type.
+// Launch Services doesn't provide any APIs (documented or undocumented) to
+// query which MIME types a given application is registered to handle. So any
+// app that wants this information (e.g. the Default Apps pref pane) needs to
+// iterate through the entire Launch Services database -- a process which can
+// take several seconds.)
+static CFArrayRef GetMIMETypesHandledByApp(FSRef *aAppRef)
+{
+ CFURLRef appURL = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aAppRef);
+ if (!appURL) {
+ return NULL;
+ }
+ CFDictionaryRef infoDict = ::CFBundleCopyInfoDictionaryForURL(appURL);
+ ::CFRelease(appURL);
+ if (!infoDict) {
+ return NULL;
+ }
+ CFTypeRef cfObject = ::CFDictionaryGetValue(infoDict, CFSTR("CFBundleDocumentTypes"));
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFArrayGetTypeID())) {
+ ::CFRelease(infoDict);
+ return NULL;
+ }
+
+ CFArrayRef docTypes = static_cast<CFArrayRef>(cfObject);
+ CFIndex docTypesCount = ::CFArrayGetCount(docTypes);
+ if (docTypesCount == 0) {
+ ::CFRelease(infoDict);
+ return NULL;
+ }
+
+ CFMutableArrayRef mimeTypes =
+ ::CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ for (CFIndex i = 0; i < docTypesCount; ++i) {
+ cfObject = ::CFArrayGetValueAtIndex(docTypes, i);
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFDictionaryGetTypeID())) {
+ continue;
+ }
+ CFDictionaryRef typeDict = static_cast<CFDictionaryRef>(cfObject);
+
+ // When this key is present (on OS X 10.5 and later), its contents
+ // take precedence over CFBundleTypeMIMETypes (and CFBundleTypeExtensions
+ // and CFBundleTypeOSTypes).
+ cfObject = ::CFDictionaryGetValue(typeDict, CFSTR("LSItemContentTypes"));
+ if (cfObject && (::CFGetTypeID(cfObject) == ::CFArrayGetTypeID())) {
+ continue;
+ }
+
+ cfObject = ::CFDictionaryGetValue(typeDict, CFSTR("CFBundleTypeMIMETypes"));
+ if (!cfObject || (::CFGetTypeID(cfObject) != ::CFArrayGetTypeID())) {
+ continue;
+ }
+ CFArrayRef mimeTypeHolder = static_cast<CFArrayRef>(cfObject);
+ CFArrayAppendArray(mimeTypes, mimeTypeHolder,
+ ::CFRangeMake(0, ::CFArrayGetCount(mimeTypeHolder)));
+ }
+
+ ::CFRelease(infoDict);
+ if (!::CFArrayGetCount(mimeTypes)) {
+ ::CFRelease(mimeTypes);
+ mimeTypes = NULL;
+ }
+ return mimeTypes;
+}
+
+// aMIMEType and aFileExt might not match, If they don't we set *aFound to
+// false and return a minimal nsIMIMEInfo structure.
+already_AddRefed<nsIMIMEInfo>
+nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
+ const nsACString& aFileExt,
+ bool * aFound)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ *aFound = false;
+
+ const nsCString& flatType = PromiseFlatCString(aMIMEType);
+ const nsCString& flatExt = PromiseFlatCString(aFileExt);
+
+ MOZ_LOG(mLog, LogLevel::Debug, ("Mac: HelperAppService lookup for type '%s' ext '%s'\n",
+ flatType.get(), flatExt.get()));
+
+ // Create a Mac-specific MIME info so we can use Mac-specific members.
+ RefPtr<nsMIMEInfoMac> mimeInfoMac = new nsMIMEInfoMac(aMIMEType);
+
+ NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
+
+ OSStatus err;
+ bool haveAppForType = false;
+ bool haveAppForExt = false;
+ bool typeAppIsDefault = false;
+ bool extAppIsDefault = false;
+ FSRef typeAppFSRef;
+ FSRef extAppFSRef;
+
+ CFStringRef cfMIMEType = NULL;
+
+ if (!aMIMEType.IsEmpty()) {
+ CFURLRef appURL = NULL;
+ // CFStringCreateWithCString() can fail even if we're not out of memory --
+ // for example if the 'cStr' parameter is something very weird (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ cfMIMEType = ::CFStringCreateWithCString(NULL, flatType.get(),
+ kCFStringEncodingUTF8);
+ if (cfMIMEType) {
+ err = ::LSCopyApplicationForMIMEType(cfMIMEType, kLSRolesAll, &appURL);
+ if ((err == noErr) && appURL && ::CFURLGetFSRef(appURL, &typeAppFSRef)) {
+ haveAppForType = true;
+ MOZ_LOG(mLog, LogLevel::Debug, ("LSCopyApplicationForMIMEType found a default application\n"));
+ }
+ if (appURL) {
+ ::CFRelease(appURL);
+ }
+ }
+ }
+ if (!aFileExt.IsEmpty()) {
+ // CFStringCreateWithCString() can fail even if we're not out of memory --
+ // for example if the 'cStr' parameter is something very wierd (like "ÿÿ~"
+ // aka "\xFF\xFF~"), or possibly if it can't be interpreted as using what's
+ // specified in the 'encoding' parameter. See bug 548719.
+ CFStringRef cfExt = ::CFStringCreateWithCString(NULL, flatExt.get(), kCFStringEncodingUTF8);
+ if (cfExt) {
+ err = ::LSGetApplicationForInfo(kLSUnknownType, kLSUnknownCreator, cfExt,
+ kLSRolesAll, &extAppFSRef, nullptr);
+ if (err == noErr) {
+ haveAppForExt = true;
+ MOZ_LOG(mLog, LogLevel::Debug, ("LSGetApplicationForInfo found a default application\n"));
+ }
+ ::CFRelease(cfExt);
+ }
+ }
+
+ if (haveAppForType && haveAppForExt) {
+ // Do aMIMEType and aFileExt match?
+ if (::FSCompareFSRefs((const FSRef *) &typeAppFSRef, (const FSRef *) &extAppFSRef) == noErr) {
+ typeAppIsDefault = true;
+ *aFound = true;
+ }
+ } else if (haveAppForType) {
+ // If aFileExt isn't empty, it doesn't match aMIMEType.
+ if (aFileExt.IsEmpty()) {
+ typeAppIsDefault = true;
+ *aFound = true;
+ }
+ } else if (haveAppForExt) {
+ // If aMIMEType isn't empty, it doesn't match aFileExt, which should mean
+ // that we haven't found a matching app. But make an exception for an app
+ // that also explicitly claims to handle aMIMEType, or which doesn't claim
+ // to handle any MIME types. This helps work around the following Apple
+ // design flaw:
+ //
+ // Launch Services is somewhat unreliable about registering Apple apps to
+ // handle MIME types. Probably this is because Apple has officially
+ // deprecated support for MIME types (in favor of UTIs). As a result,
+ // most of Apple's own apps don't explicitly claim to handle any MIME
+ // types (instead they claim to handle one or more UTIs). So Launch
+ // Services must contain logic to translate support for a given UTI into
+ // support for one or more MIME types, and it doesn't always do this
+ // correctly. For example DiskImageMounter isn't (by default) registered
+ // to handle the application/x-apple-diskimage MIME type. See bug 675356.
+ //
+ // Apple has also deprecated support for file extensions, and Apple apps
+ // also don't register to handle them. But for some reason Launch Services
+ // is (apparently) better about translating support for a given UTI into
+ // support for one or more file extensions. It's not at all clear why.
+ if (aMIMEType.IsEmpty()) {
+ extAppIsDefault = true;
+ *aFound = true;
+ } else {
+ CFArrayRef extAppMIMETypes = GetMIMETypesHandledByApp(&extAppFSRef);
+ if (extAppMIMETypes) {
+ if (cfMIMEType) {
+ if (::CFArrayContainsValue(extAppMIMETypes,
+ ::CFRangeMake(0, ::CFArrayGetCount(extAppMIMETypes)),
+ cfMIMEType)) {
+ extAppIsDefault = true;
+ *aFound = true;
+ }
+ }
+ ::CFRelease(extAppMIMETypes);
+ } else {
+ extAppIsDefault = true;
+ *aFound = true;
+ }
+ }
+ }
+
+ if (cfMIMEType) {
+ ::CFRelease(cfMIMEType);
+ }
+
+ if (aMIMEType.IsEmpty()) {
+ if (haveAppForExt) {
+ // If aMIMEType is empty and we've found a default app for aFileExt, try
+ // to get the MIME type from aFileExt. (It might also be worth doing
+ // this when aMIMEType isn't empty but haveAppForType is false -- but
+ // the doc for this method says that if we have a MIME type (in
+ // aMIMEType), we need to give it preference.)
+ NSURLFileTypeMappings *map = [NSURLFileTypeMappings sharedMappings];
+ NSString *extStr = [NSString stringWithCString:flatExt.get() encoding:NSASCIIStringEncoding];
+ NSString *typeStr = map ? [map MIMETypeForExtension:extStr] : NULL;
+ if (typeStr) {
+ nsAutoCString mimeType;
+ mimeType.Assign((char *)[typeStr cStringUsingEncoding:NSASCIIStringEncoding]);
+ mimeInfoMac->SetMIMEType(mimeType);
+ haveAppForType = true;
+ } else {
+ // Sometimes the OS won't give us a MIME type for an extension that's
+ // registered with Launch Services and has a default app: For example
+ // Real Player registers itself for the "ogg" extension and for the
+ // audio/x-ogg and application/x-ogg MIME types, but
+ // MIMETypeForExtension returns nil for the "ogg" extension even on
+ // systems where Real Player is installed. This is probably an Apple
+ // bug. But bad things happen if we return an nsIMIMEInfo structure
+ // with an empty MIME type and set *aFound to true. So in this
+ // case we need to set it to false here.
+ haveAppForExt = false;
+ extAppIsDefault = false;
+ *aFound = false;
+ }
+ } else {
+ // Otherwise set the MIME type to a reasonable fallback.
+ mimeInfoMac->SetMIMEType(NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM));
+ }
+ }
+
+ if (typeAppIsDefault || extAppIsDefault) {
+ if (haveAppForExt)
+ mimeInfoMac->AppendExtension(aFileExt);
+
+ nsCOMPtr<nsILocalFileMac> app(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ if (!app) {
+ [localPool release];
+ return nullptr;
+ }
+
+ CFStringRef cfAppName = NULL;
+ if (typeAppIsDefault) {
+ app->InitWithFSRef(&typeAppFSRef);
+ ::LSCopyItemAttribute((const FSRef *) &typeAppFSRef, kLSRolesAll,
+ kLSItemDisplayName, (CFTypeRef *) &cfAppName);
+ } else {
+ app->InitWithFSRef(&extAppFSRef);
+ ::LSCopyItemAttribute((const FSRef *) &extAppFSRef, kLSRolesAll,
+ kLSItemDisplayName, (CFTypeRef *) &cfAppName);
+ }
+ if (cfAppName) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex appNameLength = ::CFStringGetLength(cfAppName);
+ buffer.SetLength(appNameLength);
+ ::CFStringGetCharacters(cfAppName, CFRangeMake(0, appNameLength),
+ buffer.Elements());
+ nsAutoString appName;
+ appName.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), appNameLength);
+ mimeInfoMac->SetDefaultDescription(appName);
+ ::CFRelease(cfAppName);
+ }
+
+ mimeInfoMac->SetDefaultApplication(app);
+ mimeInfoMac->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
+ } else {
+ mimeInfoMac->SetPreferredAction(nsIMIMEInfo::saveToDisk);
+ }
+
+ nsAutoCString mimeType;
+ mimeInfoMac->GetMIMEType(mimeType);
+ if (*aFound && !mimeType.IsEmpty()) {
+ // If we have a MIME type, make sure its preferred extension is included
+ // in our list.
+ NSURLFileTypeMappings *map = [NSURLFileTypeMappings sharedMappings];
+ NSString *typeStr = [NSString stringWithCString:mimeType.get() encoding:NSASCIIStringEncoding];
+ NSString *extStr = map ? [map preferredExtensionForMIMEType:typeStr] : NULL;
+ if (extStr) {
+ nsAutoCString preferredExt;
+ preferredExt.Assign((char *)[extStr cStringUsingEncoding:NSASCIIStringEncoding]);
+ mimeInfoMac->AppendExtension(preferredExt);
+ }
+
+ CFStringRef cfType = ::CFStringCreateWithCString(NULL, mimeType.get(), kCFStringEncodingUTF8);
+ if (cfType) {
+ CFStringRef cfTypeDesc = NULL;
+ if (::LSCopyKindStringForMIMEType(cfType, &cfTypeDesc) == noErr) {
+ AutoTArray<UniChar, 255> buffer;
+ CFIndex typeDescLength = ::CFStringGetLength(cfTypeDesc);
+ buffer.SetLength(typeDescLength);
+ ::CFStringGetCharacters(cfTypeDesc, CFRangeMake(0, typeDescLength),
+ buffer.Elements());
+ nsAutoString typeDesc;
+ typeDesc.Assign(reinterpret_cast<char16_t*>(buffer.Elements()), typeDescLength);
+ mimeInfoMac->SetDescription(typeDesc);
+ }
+ if (cfTypeDesc) {
+ ::CFRelease(cfTypeDesc);
+ }
+ ::CFRelease(cfType);
+ }
+ }
+
+ MOZ_LOG(mLog, LogLevel::Debug, ("OS gave us: type '%s' found '%i'\n", mimeType.get(), *aFound));
+
+ [localPool release];
+ return mimeInfoMac.forget();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+NS_IMETHODIMP
+nsOSHelperAppService::GetProtocolHandlerInfoFromOS(const nsACString &aScheme,
+ bool *found,
+ nsIHandlerInfo **_retval)
+{
+ NS_ASSERTION(!aScheme.IsEmpty(), "No scheme was specified!");
+
+ nsresult rv = OSProtocolHandlerExists(nsPromiseFlatCString(aScheme).get(),
+ found);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsMIMEInfoMac *handlerInfo =
+ new nsMIMEInfoMac(aScheme, nsMIMEInfoBase::eProtocolInfo);
+ NS_ENSURE_TRUE(handlerInfo, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = handlerInfo);
+
+ if (!*found) {
+ // Code that calls this requires an object regardless if the OS has
+ // something for us, so we return the empty object.
+ return NS_OK;
+ }
+
+ nsAutoString desc;
+ rv = GetApplicationDescription(aScheme, desc);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "GetApplicationDescription failed");
+ handlerInfo->SetDefaultDescription(desc);
+
+ return NS_OK;
+}
+
diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build
index f0bafd945b..127e4da2bd 100644
--- a/uriloader/exthandler/moz.build
+++ b/uriloader/exthandler/moz.build
@@ -21,6 +21,8 @@ XPIDL_MODULE = 'exthandler'
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
osdir = 'win'
LOCAL_INCLUDES += ['win']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ osdir = 'mac'
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
osdir = 'uikit'
else:
@@ -51,7 +53,13 @@ UNIFIED_SOURCES += [
'nsMIMEInfoImpl.cpp',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'mac/nsLocalHandlerAppMac.mm',
+ 'mac/nsMIMEInfoMac.mm',
+ 'mac/nsOSHelperAppService.mm',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'uikit':
UNIFIED_SOURCES += [
'uikit/nsLocalHandlerAppUIKit.mm',
'uikit/nsMIMEInfoUIKit.mm',
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
index dc7d200041..7d5592f948 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -63,6 +63,10 @@
#include "nsIObserverService.h" // so we can be a profile change observer
#include "nsIPropertyBag2.h" // for the 64-bit content length
+#ifdef XP_MACOSX
+#include "nsILocalFileMac.h"
+#endif
+
#include "nsIPluginHost.h" // XXX needed for ext->type mapping (bug 233289)
#include "nsPluginHost.h"
#include "nsEscape.h"
@@ -271,7 +275,50 @@ static nsresult GetDownloadDirectory(nsIFile **_directory,
bool aSkipChecks = false)
{
nsCOMPtr<nsIFile> dir;
- // On all supported platforms, we default to the system's temporary directory.
+#ifdef XP_MACOSX
+ // On OS X, we first try to get the users download location, if it's set.
+ switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
+ case NS_FOLDER_VALUE_DESKTOP:
+ (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
+ break;
+ case NS_FOLDER_VALUE_CUSTOM:
+ {
+ Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(dir));
+ if (!dir) break;
+
+ // If we're not checking for availability we're done.
+ if (aSkipChecks) {
+ dir.forget(_directory);
+ return NS_OK;
+ }
+
+ // We have the directory, and now we need to make sure it exists
+ bool dirExists = false;
+ (void) dir->Exists(&dirExists);
+ if (dirExists) break;
+
+ nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_FAILED(rv)) {
+ dir = nullptr;
+ break;
+ }
+ }
+ break;
+ case NS_FOLDER_VALUE_DOWNLOADS:
+ // This is just the OS default location, so fall out
+ break;
+ }
+
+ if (!dir) {
+ // If not, we default to the OS X default download location.
+ nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
+ getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#else
+ // On all other platforms, we default to the systems temporary directory.
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(dir));
NS_ENSURE_SUCCESS(rv, rv);
@@ -345,6 +392,7 @@ static nsresult GetDownloadDirectory(nsIFile **_directory,
}
#endif
+#endif
NS_ASSERTION(dir, "Somehow we didn't get a download directory!");
dir.forget(_directory);
@@ -416,7 +464,11 @@ struct nsExtraMimeTypeEntry {
const char* mDescription;
};
+#ifdef XP_MACOSX
+#define MAC_TYPE(x) x
+#else
#define MAC_TYPE(x) 0
+#endif
/**
* This table lists all of the 'extra' content types that we can deduce from particular
@@ -427,7 +479,11 @@ struct nsExtraMimeTypeEntry {
*/
static const nsExtraMimeTypeEntry extraMimeEntries[] =
{
+#if defined(XP_MACOSX) // don't define .bin on the mac...use internet config to look that up...
+ { APPLICATION_OCTET_STREAM, "exe,com", "Binary File" },
+#else
{ APPLICATION_OCTET_STREAM, "exe,com,bin", "Binary File" },
+#endif
{ APPLICATION_GZIP2, "gz", "gzip" },
{ "application/x-arj", "arj", "ARJ file" },
{ "application/rtf", "rtf", "Rich Text Format File" },
diff --git a/uriloader/exthandler/nsLocalHandlerApp.h b/uriloader/exthandler/nsLocalHandlerApp.h
index 2e6ba6d8c3..30b410ce32 100644
--- a/uriloader/exthandler/nsLocalHandlerApp.h
+++ b/uriloader/exthandler/nsLocalHandlerApp.h
@@ -46,8 +46,15 @@ protected:
nsresult LaunchWithIProcess(const nsCString &aArg);
};
-// Any platforms that need a platform-specific class instead of just
+// any platforms that need a platform-specific class instead of just
// using nsLocalHandlerApp need to add an include and a typedef here.
+#ifdef XP_MACOSX
+# ifndef NSLOCALHANDLERAPPMAC_H_
+# include "mac/nsLocalHandlerAppMac.h"
+typedef nsLocalHandlerAppMac PlatformLocalHandlerApp_t;
+# endif
+#else
typedef nsLocalHandlerApp PlatformLocalHandlerApp_t;
+#endif
#endif // __nsLocalHandlerAppImpl_h__
diff --git a/view/nsView.cpp b/view/nsView.cpp
index 79fd7cf075..8f509cadeb 100644
--- a/view/nsView.cpp
+++ b/view/nsView.cpp
@@ -241,8 +241,8 @@ LayoutDeviceIntRect nsView::CalcWidgetBounds(nsWindowType aType)
LayoutDeviceIntRect newBounds =
LayoutDeviceIntRect::FromUnknownRect(viewBounds.ToNearestPixels(p2a));
-#if (MOZ_WIDGET_GTK == 3)
- // GTK 3 rounds widget coordinates to the nearest global "display
+#if defined(XP_MACOSX) || (MOZ_WIDGET_GTK == 3)
+ // cocoa and GTK round widget coordinates to the nearest global "display
// pixel" integer value. So we avoid fractional display pixel values by
// rounding to the nearest value that won't yield a fractional display pixel.
nsIWidget* widget = parentWidget ? parentWidget : mWindow.get();
diff --git a/view/nsViewManager.cpp b/view/nsViewManager.cpp
index b6a6e25ef2..e41f005b25 100644
--- a/view/nsViewManager.cpp
+++ b/view/nsViewManager.cpp
@@ -598,7 +598,10 @@ nsViewManager::InvalidateWidgetArea(nsView *aWidgetView,
"Only plugin or popup widgets can be children!");
// We do not need to invalidate in plugin widgets, but we should
- // exclude them from the invalidation region.
+ // exclude them from the invalidation region IF we're not on
+ // Mac. On Mac we need to draw under plugin widgets, because
+ // plugin widgets are basically invisible
+#ifndef XP_MACOSX
// GetBounds should compensate for chrome on a toplevel widget
LayoutDeviceIntRect bounds = childWidget->GetBounds();
@@ -610,6 +613,7 @@ nsViewManager::InvalidateWidgetArea(nsView *aWidgetView,
children.Or(children, rr - aWidgetView->ViewToWidgetOffset());
children.SimplifyInward(20);
}
+#endif
}
}
}
diff --git a/widget/CompositorWidget.h b/widget/CompositorWidget.h
index 829255b62d..eb657ba7f3 100644
--- a/widget/CompositorWidget.h
+++ b/widget/CompositorWidget.h
@@ -54,7 +54,11 @@ class CompositorWidgetChild;
class WidgetRenderingContext
{
-/** Mac Stub **/
+public:
+#ifdef XP_MACOSX
+ WidgetRenderingContext() : mLayerManager(nullptr) {}
+ layers::LayerManagerComposite* mLayerManager;
+#endif
};
/**
diff --git a/widget/GfxInfoBase.cpp b/widget/GfxInfoBase.cpp
index ae0878c757..ed47355367 100644
--- a/widget/GfxInfoBase.cpp
+++ b/widget/GfxInfoBase.cpp
@@ -1,3 +1,4 @@
+/* vim: se cin sw=2 ts=2 et : */
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
@@ -645,6 +646,13 @@ MatchingOperatingSystems(OperatingSystem aBlockedOS, OperatingSystem aSystemOS)
}
#endif
+#if defined (XP_MACOSX)
+ if (aBlockedOS == OperatingSystem::OSX) {
+ // We do want even "unknown" aSystemOS to fall under "all OS X"
+ return true;
+ }
+#endif
+
return aSystemOS == aBlockedOS;
}
diff --git a/widget/NativeKeyToDOMCodeName.h b/widget/NativeKeyToDOMCodeName.h
index 05bcd3babb..74f2ed1f47 100644
--- a/widget/NativeKeyToDOMCodeName.h
+++ b/widget/NativeKeyToDOMCodeName.h
@@ -27,6 +27,12 @@
#define CODE_MAP_WIN(aCPPCodeName, aNativeKey) \
NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, \
CODE_NAME_INDEX_##aCPPCodeName)
+#elif defined(XP_MACOSX)
+#undef CODE_MAP_MAC
+// aNativeKey is key code starting with kVK_.
+#define CODE_MAP_MAC(aCPPCodeName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, \
+ CODE_NAME_INDEX_##aCPPCodeName)
#elif defined(MOZ_WIDGET_GTK)
#undef CODE_MAP_X11
// aNativeKey is hardware_keycode of GDKEvent or nativeScanCode of QKeyEvent.
diff --git a/widget/NativeKeyToDOMKeyName.h b/widget/NativeKeyToDOMKeyName.h
index 45400e9564..25dafff6f3 100644
--- a/widget/NativeKeyToDOMKeyName.h
+++ b/widget/NativeKeyToDOMKeyName.h
@@ -61,6 +61,10 @@
#else
#error Any NS_*_TO_DOM_KEY_NAME_INDEX() is not defined.
#endif // #if defined(NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX) ...
+#elif defined(XP_MACOSX)
+#undef KEY_MAP_COCOA
+#define KEY_MAP_COCOA(aCPPKeyName, aNativeKey) \
+ NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, KEY_NAME_INDEX_##aCPPKeyName)
#elif defined(MOZ_WIDGET_GTK)
#undef KEY_MAP_GTK
#define KEY_MAP_GTK(aCPPKeyName, aNativeKey) \
diff --git a/widget/TextEvents.h b/widget/TextEvents.h
index 736effeeab..6c29341144 100644
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -111,6 +111,10 @@ protected:
, mLocation(nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD)
, mAccessKeyForwardedToChild(false)
, mUniqueId(0)
+#ifdef XP_MACOSX
+ , mNativeModifierFlags(0)
+ , mNativeKeyCode(0)
+#endif // #ifdef XP_MACOSX
, mKeyNameIndex(mozilla::KEY_NAME_INDEX_Unidentified)
, mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
, mInputMethodAppState(eNotHandled)
@@ -136,6 +140,10 @@ public:
, mLocation(nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD)
, mAccessKeyForwardedToChild(false)
, mUniqueId(0)
+#ifdef XP_MACOSX
+ , mNativeModifierFlags(0)
+ , mNativeKeyCode(0)
+#endif // #ifdef XP_MACOSX
, mKeyNameIndex(mozilla::KEY_NAME_INDEX_Unidentified)
, mCodeNameIndex(CODE_NAME_INDEX_UNKNOWN)
, mInputMethodAppState(eNotHandled)
@@ -196,6 +204,15 @@ public:
// CODE_NAME_INDEX_USE_STRING.
nsString mCodeValue;
+#ifdef XP_MACOSX
+ // Values given by a native NSEvent, for use with Cocoa NPAPI plugins.
+ nsString mNativeCharacters;
+ nsString mNativeCharactersIgnoringModifiers;
+ // If this is non-empty, create a text event for plugins instead of a
+ // keyboard event.
+ nsString mPluginTextEventString;
+#endif // #ifdef XP_MACOSX
+
// OS-specific native event can optionally be preserved
void* mNativeKeyEvent;
// A DOM keyCode value or 0. If a keypress event whose mCharCode is 0, this
@@ -223,6 +240,12 @@ public:
// over long periods.
uint32_t mUniqueId;
+#ifdef XP_MACOSX
+ // Values given by a native NSEvent, for use with Cocoa NPAPI plugins.
+ uint32_t mNativeModifierFlags;
+ uint16_t mNativeKeyCode;
+#endif // #ifdef XP_MACOSX
+
// DOM KeyboardEvent.key
KeyNameIndex mKeyNameIndex;
// DOM KeyboardEvent.code
@@ -374,6 +397,14 @@ public:
// is destroyed.
mNativeKeyEvent = nullptr;
mUniqueId = aEvent.mUniqueId;
+#ifdef XP_MACOSX
+ mNativeKeyCode = aEvent.mNativeKeyCode;
+ mNativeModifierFlags = aEvent.mNativeModifierFlags;
+ mNativeCharacters.Assign(aEvent.mNativeCharacters);
+ mNativeCharactersIgnoringModifiers.
+ Assign(aEvent.mNativeCharactersIgnoringModifiers);
+ mPluginTextEventString.Assign(aEvent.mPluginTextEventString);
+#endif
mInputMethodAppState = aEvent.mInputMethodAppState;
mIsSynthesizedByTIP = aEvent.mIsSynthesizedByTIP;
}
diff --git a/widget/WidgetEventImpl.cpp b/widget/WidgetEventImpl.cpp
index 1302c3e948..59c80672b1 100644
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -479,7 +479,11 @@ WidgetInputEvent::AccelModifier()
sAccelModifier = MODIFIER_CONTROL;
break;
default:
+#ifdef XP_MACOSX
+ sAccelModifier = MODIFIER_META;
+#else
sAccelModifier = MODIFIER_CONTROL;
+#endif
break;
}
}
diff --git a/widget/cocoa/ComplexTextInputPanel.h b/widget/cocoa/ComplexTextInputPanel.h
new file mode 100644
index 0000000000..648e6d9114
--- /dev/null
+++ b/widget/cocoa/ComplexTextInputPanel.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Modified by Josh Aas of Mozilla Corporation.
+ */
+
+#ifndef ComplexTextInputPanel_h_
+#define ComplexTextInputPanel_h_
+
+#include "nsString.h"
+#include "npapi.h"
+
+class ComplexTextInputPanel
+{
+public:
+ static ComplexTextInputPanel* GetSharedComplexTextInputPanel();
+ virtual void PlacePanel(int32_t x, int32_t y) = 0; // Bottom left coordinate of plugin in screen coords
+ virtual void InterpretKeyEvent(void* aEvent, nsAString& aOutText) = 0;
+ virtual bool IsInComposition() = 0;
+ virtual void* GetInputContext() = 0;
+ virtual void CancelComposition() = 0;
+
+protected:
+ virtual ~ComplexTextInputPanel() {};
+};
+
+#endif // ComplexTextInputPanel_h_
diff --git a/widget/cocoa/ComplexTextInputPanel.mm b/widget/cocoa/ComplexTextInputPanel.mm
new file mode 100644
index 0000000000..a4b58955e2
--- /dev/null
+++ b/widget/cocoa/ComplexTextInputPanel.mm
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Modified by Josh Aas of Mozilla Corporation.
+ */
+
+#import "ComplexTextInputPanel.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <algorithm>
+#include "mozilla/Preferences.h"
+#include "nsChildView.h"
+
+using namespace mozilla;
+
+extern "C" OSStatus TSMProcessRawKeyEvent(EventRef anEvent);
+
+#define kInputWindowHeight 20
+
+@interface ComplexTextInputPanelImpl : NSPanel {
+ NSTextView *mInputTextView;
+}
+
++ (ComplexTextInputPanelImpl*)sharedComplexTextInputPanelImpl;
+
+- (NSTextInputContext*)inputContext;
+- (void)interpretKeyEvent:(NSEvent*)event string:(NSString**)string;
+- (void)cancelComposition;
+- (BOOL)inComposition;
+
+// This places the text input panel fully onscreen and below the lower left
+// corner of the focused plugin.
+- (void)adjustTo:(NSPoint)point;
+
+@end
+
+@implementation ComplexTextInputPanelImpl
+
++ (ComplexTextInputPanelImpl*)sharedComplexTextInputPanelImpl
+{
+ static ComplexTextInputPanelImpl *sComplexTextInputPanelImpl;
+ if (!sComplexTextInputPanelImpl)
+ sComplexTextInputPanelImpl = [[ComplexTextInputPanelImpl alloc] init];
+ return sComplexTextInputPanelImpl;
+}
+
+- (id)init
+{
+ // In the original Apple code the style mask is given by a function which is not open source.
+ // What could possibly be worth hiding in that function, I do not know.
+ // Courtesy of gdb: stylemask: 011000011111, 0x61f
+ self = [super initWithContentRect:NSZeroRect styleMask:0x61f backing:NSBackingStoreBuffered defer:YES];
+ if (!self)
+ return nil;
+
+ // Set the frame size.
+ NSRect visibleFrame = [[NSScreen mainScreen] visibleFrame];
+ NSRect frame = NSMakeRect(visibleFrame.origin.x, visibleFrame.origin.y, visibleFrame.size.width, kInputWindowHeight);
+
+ [self setFrame:frame display:NO];
+
+ mInputTextView = [[NSTextView alloc] initWithFrame:[self.contentView frame]];
+ mInputTextView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable | NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin | NSViewMinYMargin;
+
+ NSScrollView* scrollView = [[NSScrollView alloc] initWithFrame:[self.contentView frame]];
+ scrollView.documentView = mInputTextView;
+ self.contentView = scrollView;
+ [scrollView release];
+
+ [self setFloatingPanel:YES];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(keyboardInputSourceChanged:)
+ name:NSTextInputContextKeyboardSelectionDidChangeNotification
+ object:nil];
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [mInputTextView release];
+
+ [super dealloc];
+}
+
+- (void)keyboardInputSourceChanged:(NSNotification *)notification
+{
+ static int8_t sDoCancel = -1;
+ if (!sDoCancel || ![self inComposition]) {
+ return;
+ }
+ if (sDoCancel < 0) {
+ bool cancelComposition = false;
+ static const char* kPrefName =
+ "ui.plugin.cancel_composition_at_input_source_changed";
+ nsresult rv = Preferences::GetBool(kPrefName, &cancelComposition);
+ NS_ENSURE_SUCCESS(rv, );
+ sDoCancel = cancelComposition ? 1 : 0;
+ }
+ if (sDoCancel) {
+ [self cancelComposition];
+ }
+}
+
+- (void)interpretKeyEvent:(NSEvent*)event string:(NSString**)string
+{
+ *string = nil;
+
+ if (![[mInputTextView inputContext] handleEvent:event]) {
+ return;
+ }
+
+ if ([mInputTextView hasMarkedText]) {
+ // Don't show the input method window for dead keys
+ if ([[event characters] length] > 0) {
+ [self orderFront:nil];
+ }
+ return;
+ } else {
+ [self orderOut:nil];
+
+ NSString *text = [[mInputTextView textStorage] string];
+ if ([text length] > 0) {
+ *string = [[text copy] autorelease];
+ }
+ }
+
+ [mInputTextView setString:@""];
+}
+
+- (NSTextInputContext*)inputContext
+{
+ return [mInputTextView inputContext];
+}
+
+- (void)cancelComposition
+{
+ [mInputTextView setString:@""];
+ [self orderOut:nil];
+}
+
+- (BOOL)inComposition
+{
+ return [mInputTextView hasMarkedText];
+}
+
+- (void)adjustTo:(NSPoint)point
+{
+ NSRect selfRect = [self frame];
+ NSRect rect = NSMakeRect(point.x,
+ point.y - selfRect.size.height,
+ 500,
+ selfRect.size.height);
+
+ // Adjust to screen.
+ NSRect screenRect = [[NSScreen mainScreen] visibleFrame];
+ if (rect.origin.x < screenRect.origin.x) {
+ rect.origin.x = screenRect.origin.x;
+ }
+ if (rect.origin.y < screenRect.origin.y) {
+ rect.origin.y = screenRect.origin.y;
+ }
+ CGFloat xMostOfScreen = screenRect.origin.x + screenRect.size.width;
+ CGFloat yMostOfScreen = screenRect.origin.y + screenRect.size.height;
+ CGFloat xMost = rect.origin.x + rect.size.width;
+ CGFloat yMost = rect.origin.y + rect.size.height;
+ if (xMostOfScreen < xMost) {
+ rect.origin.x -= xMost - xMostOfScreen;
+ }
+ if (yMostOfScreen < yMost) {
+ rect.origin.y -= yMost - yMostOfScreen;
+ }
+
+ [self setFrame:rect display:[self isVisible]];
+}
+
+@end
+
+class ComplexTextInputPanelPrivate : public ComplexTextInputPanel
+{
+public:
+ ComplexTextInputPanelPrivate();
+
+ virtual void InterpretKeyEvent(void* aEvent, nsAString& aOutText);
+ virtual bool IsInComposition();
+ virtual void PlacePanel(int32_t x, int32_t y);
+ virtual void* GetInputContext() { return [mPanel inputContext]; }
+ virtual void CancelComposition() { [mPanel cancelComposition]; }
+
+private:
+ ~ComplexTextInputPanelPrivate();
+ ComplexTextInputPanelImpl* mPanel;
+};
+
+ComplexTextInputPanelPrivate::ComplexTextInputPanelPrivate()
+{
+ mPanel = [[ComplexTextInputPanelImpl alloc] init];
+}
+
+ComplexTextInputPanelPrivate::~ComplexTextInputPanelPrivate()
+{
+ [mPanel release];
+}
+
+ComplexTextInputPanel*
+ComplexTextInputPanel::GetSharedComplexTextInputPanel()
+{
+ static ComplexTextInputPanelPrivate *sComplexTextInputPanelPrivate;
+ if (!sComplexTextInputPanelPrivate) {
+ sComplexTextInputPanelPrivate = new ComplexTextInputPanelPrivate();
+ }
+ return sComplexTextInputPanelPrivate;
+}
+
+void
+ComplexTextInputPanelPrivate::InterpretKeyEvent(void* aEvent, nsAString& aOutText)
+{
+ NSString* textString = nil;
+ [mPanel interpretKeyEvent:(NSEvent*)aEvent string:&textString];
+
+ if (textString) {
+ nsCocoaUtils::GetStringForNSString(textString, aOutText);
+ }
+}
+
+bool
+ComplexTextInputPanelPrivate::IsInComposition()
+{
+ return !![mPanel inComposition];
+}
+
+void
+ComplexTextInputPanelPrivate::PlacePanel(int32_t x, int32_t y)
+{
+ [mPanel adjustTo:NSMakePoint(x, y)];
+}
diff --git a/widget/cocoa/CustomCocoaEvents.h b/widget/cocoa/CustomCocoaEvents.h
new file mode 100644
index 0000000000..0043f0d695
--- /dev/null
+++ b/widget/cocoa/CustomCocoaEvents.h
@@ -0,0 +1,18 @@
+/* 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/. */
+
+/*
+ * This file defines constants to be used in the "subtype" field of
+ * NSApplicationDefined type NSEvents.
+ */
+
+#ifndef WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+#define WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_
+
+// Empty event, just used for prodding the event loop into responding.
+const short kEventSubtypeNone = 0;
+// Tracer event, used for timing the event loop responsiveness.
+const short kEventSubtypeTrace = 1;
+
+#endif /* WIDGET_COCOA_CUSTOMCOCOAEVENTS_H_ */
diff --git a/widget/cocoa/GfxInfo.h b/widget/cocoa/GfxInfo.h
new file mode 100644
index 0000000000..05bdad158b
--- /dev/null
+++ b/widget/cocoa/GfxInfo.h
@@ -0,0 +1,95 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : public GfxInfoBase
+{
+public:
+
+ GfxInfo();
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool *aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams) override;
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active) override;
+
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual nsresult Init() override;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ virtual uint32_t OperatingSystemVersion() override { return mOSXVersion; }
+
+ nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override;
+
+protected:
+
+ virtual ~GfxInfo() {}
+
+ virtual nsresult GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString &aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+private:
+
+ void GetDeviceInfo();
+ void GetSelectedCityInfo();
+ void AddCrashReportAnnotations();
+
+ nsString mAdapterRAMString;
+ nsString mDeviceID;
+ nsString mDriverVersion;
+ nsString mDriverDate;
+ nsString mDeviceKey;
+
+ nsString mAdapterVendorID;
+ nsString mAdapterDeviceID;
+
+ uint32_t mOSXVersion;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/cocoa/GfxInfo.mm b/widget/cocoa/GfxInfo.mm
new file mode 100644
index 0000000000..97bb4c8324
--- /dev/null
+++ b/widget/cocoa/GfxInfo.mm
@@ -0,0 +1,426 @@
+/* -*- Mode: C++; tab-width: 2; 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 <OpenGL/OpenGL.h>
+#include <OpenGL/CGLRenderers.h>
+
+#include "mozilla/ArrayUtils.h"
+
+#include "GfxInfo.h"
+#include "nsUnicharUtils.h"
+#include "nsCocoaFeatures.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+#import <Foundation/Foundation.h>
+#import <IOKit/IOKitLib.h>
+#import <Cocoa/Cocoa.h>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+GfxInfo::GfxInfo()
+{
+}
+
+static OperatingSystem
+OSXVersionToOperatingSystem(uint32_t aOSXVersion) {
+ switch (nsCocoaFeatures::ExtractMajorVersion(aOSXVersion)) {
+ case 10:
+ switch (nsCocoaFeatures::ExtractMinorVersion(aOSXVersion)) {
+ case 6:
+ return OperatingSystem::OSX10_6;
+ case 7:
+ return OperatingSystem::OSX10_7;
+ case 8:
+ return OperatingSystem::OSX10_8;
+ case 9:
+ return OperatingSystem::OSX10_9;
+ case 10:
+ return OperatingSystem::OSX10_10;
+ case 11:
+ return OperatingSystem::OSX10_11;
+ case 12:
+ return OperatingSystem::OSX10_12;
+ case 13:
+ return OperatingSystem::OSX10_13;
+ case 14:
+ return OperatingSystem::OSX10_14;
+ case 15:
+ return OperatingSystem::OSX10_15;
+ case 16:
+ // Depending on the SDK version, we either get 10.16 or 11.0.
+ // Normalize this to 11.0.
+ return OperatingSystem::OSX11_0;
+ default:
+ break;
+ }
+ break;
+ case 11:
+ switch (nsCocoaFeatures::ExtractMinorVersion(aOSXVersion)) {
+ case 0:
+ return OperatingSystem::OSX11_0;
+ default:
+ break;
+ }
+ break;
+ }
+
+ return OperatingSystem::Unknown;
+}
+// The following three functions are derived from Chromium code
+static CFTypeRef SearchPortForProperty(io_registry_entry_t dspPort,
+ CFStringRef propertyName)
+{
+ return IORegistryEntrySearchCFProperty(dspPort,
+ kIOServicePlane,
+ propertyName,
+ kCFAllocatorDefault,
+ kIORegistryIterateRecursively |
+ kIORegistryIterateParents);
+}
+
+static uint32_t IntValueOfCFData(CFDataRef d)
+{
+ uint32_t value = 0;
+
+ if (d) {
+ const uint32_t *vp = reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(d));
+ if (vp != NULL)
+ value = *vp;
+ }
+
+ return value;
+}
+
+void
+GfxInfo::GetDeviceInfo()
+{
+ io_registry_entry_t dsp_port = CGDisplayIOServicePort(kCGDirectMainDisplay);
+ CFTypeRef vendor_id_ref = SearchPortForProperty(dsp_port, CFSTR("vendor-id"));
+ if (vendor_id_ref) {
+ mAdapterVendorID.AppendPrintf("0x%04x", IntValueOfCFData((CFDataRef)vendor_id_ref));
+ CFRelease(vendor_id_ref);
+ }
+ CFTypeRef device_id_ref = SearchPortForProperty(dsp_port, CFSTR("device-id"));
+ if (device_id_ref) {
+ mAdapterDeviceID.AppendPrintf("0x%04x", IntValueOfCFData((CFDataRef)device_id_ref));
+ CFRelease(device_id_ref);
+ }
+}
+
+nsresult
+GfxInfo::Init()
+{
+ nsresult rv = GfxInfoBase::Init();
+
+ // Calling CGLQueryRendererInfo causes us to switch to the discrete GPU
+ // even when we don't want to. We'll avoid doing so for now and just
+ // use the device ids.
+
+ GetDeviceInfo();
+
+ AddCrashReportAnnotations();
+
+ mOSXVersion = nsCocoaFeatures::macOSVersion();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetD2DEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString DWriteVersion; */
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString & aDwriteVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString cleartypeParameters; */
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString & aCleartypeParams)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDescription; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription)
+{
+ aAdapterDescription.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDescription2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterRAM; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ aAdapterRAM = mAdapterRAMString;
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterRAM2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDriver; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver)
+{
+ aAdapterDriver.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriver2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString & aAdapterDriver)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDriverVersion; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverVersion2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDriverDate; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate)
+{
+ aAdapterDriverDate.AssignLiteral("");
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDriverDate2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterVendorID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID)
+{
+ aAdapterVendorID = mAdapterVendorID;
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterVendorID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterDeviceID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID = mAdapterDeviceID;
+ return NS_OK;
+}
+
+/* readonly attribute DOMString adapterDeviceID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterSubsysID; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute DOMString adapterSubsysID2; */
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+/* readonly attribute boolean isGPU2Active; */
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active)
+{
+ return NS_ERROR_FAILURE;
+}
+
+void
+GfxInfo::AddCrashReportAnnotations()
+{
+ /*** STUB ***/
+}
+
+// We don't support checking driver versions on Mac.
+#define IMPLEMENT_MAC_DRIVER_BLOCKLIST(os, vendor, device, features, blockOn, ruleId) \
+ APPEND_TO_DRIVER_BLOCKLIST(os, vendor, device, features, blockOn, \
+ DRIVER_COMPARISON_IGNORED, V(0,0,0,0), ruleId, "")
+
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ if (!mDriverInfo->Length()) {
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_WEBGL_MSAA, nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION, "FEATURE_FAILURE_MAC_ATI_NO_MSAA");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorATI), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(RadeonX1000),
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_MAC_RADEONX1000_NO_TEXTURE2D");
+ IMPLEMENT_MAC_DRIVER_BLOCKLIST(OperatingSystem::OSX,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorNVIDIA), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Geforce7300GT),
+ nsIGfxInfo::FEATURE_WEBGL_OPENGL, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, "FEATURE_FAILURE_MAC_7300_NO_WEBGL");
+ }
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t* aStatus,
+ nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ OperatingSystem os = OSXVersionToOperatingSystem(mOSXVersion);
+ if (aOS)
+ *aOS = os;
+
+ if (mShutdownOccurred) {
+ return NS_OK;
+ }
+
+ // Don't evaluate special cases when we're evaluating the downloaded blocklist.
+ if (!aDriverInfo.Length()) {
+ if (aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
+ // Blacklist all ATI cards on OSX, except for
+ // 0x6760 and 0x9488
+ if (mAdapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorATI), nsCaseInsensitiveStringComparator()) &&
+ (mAdapterDeviceID.LowerCaseEqualsLiteral("0x6760") ||
+ mAdapterDeviceID.LowerCaseEqualsLiteral("0x9488"))) {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+ } else if (aFeature == nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION) {
+ // See bug 1249659
+ switch(os) {
+ case OperatingSystem::OSX10_5:
+ case OperatingSystem::OSX10_6:
+ case OperatingSystem::OSX10_7:
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_OS_VERSION;
+ aFailureId = "FEATURE_FAILURE_CANVAS_OSX_VERSION";
+ break;
+ default:
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ break;
+ }
+ return NS_OK;
+ }
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+}
+
+nsresult
+GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray)
+{
+ // Getting the refresh rate is a little hard on OS X. We could use
+ // CVDisplayLinkGetNominalOutputVideoRefreshPeriod, but that's a little
+ // involved. Ideally we could query it from vsync. For now, we leave it out.
+ int32_t deviceCount = 0;
+ for (NSScreen* screen in [NSScreen screens]) {
+ NSRect rect = [screen frame];
+
+ JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx));
+
+ JS::Rooted<JS::Value> screenWidth(aCx, JS::Int32Value((int)rect.size.width));
+ JS_SetProperty(aCx, obj, "screenWidth", screenWidth);
+
+ JS::Rooted<JS::Value> screenHeight(aCx, JS::Int32Value((int)rect.size.height));
+ JS_SetProperty(aCx, obj, "screenHeight", screenHeight);
+
+ JS::Rooted<JS::Value> scale(aCx, JS::NumberValue(nsCocoaUtils::GetBackingScaleFactor(screen)));
+ JS_SetProperty(aCx, obj, "scale", scale);
+
+ JS::Rooted<JS::Value> element(aCx, JS::ObjectValue(*obj));
+ JS_SetElement(aCx, aOutArray, deviceCount++, element);
+ }
+ return NS_OK;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+/* void spoofVendorID (in DOMString aVendorID); */
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString & aVendorID)
+{
+ mAdapterVendorID = aVendorID;
+ return NS_OK;
+}
+
+/* void spoofDeviceID (in unsigned long aDeviceID); */
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
+{
+ mAdapterDeviceID = aDeviceID;
+ return NS_OK;
+}
+
+/* void spoofDriverVersion (in DOMString aDriverVersion); */
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
+{
+ mDriverVersion = aDriverVersion;
+ return NS_OK;
+}
+
+/* void spoofOSVersion (in unsigned long aVersion); */
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion)
+{
+ mOSXVersion = aVersion;
+ return NS_OK;
+}
+
+#endif
diff --git a/widget/cocoa/NativeKeyBindings.h b/widget/cocoa/NativeKeyBindings.h
new file mode 100644
index 0000000000..d1ba2c3701
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozilla_widget_NativeKeyBindings_h_
+#define mozilla_widget_NativeKeyBindings_h_
+
+#import <Cocoa/Cocoa.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsDataHashtable.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+typedef nsDataHashtable<nsPtrHashKey<struct objc_selector>, CommandInt>
+ SelectorCommandHashtable;
+
+class NativeKeyBindings final
+{
+ typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType;
+ typedef nsIWidget::DoCommandCallback DoCommandCallback;
+
+public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ bool Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData);
+
+private:
+ NativeKeyBindings();
+
+ SelectorCommandHashtable mSelectorToCommand;
+
+ static NativeKeyBindings* sInstanceForSingleLineEditor;
+ static NativeKeyBindings* sInstanceForMultiLineEditor;
+}; // NativeKeyBindings
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_NativeKeyBindings_h_
diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.mm
new file mode 100644
index 0000000000..2f4ecadff0
--- /dev/null
+++ b/widget/cocoa/NativeKeyBindings.mm
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 2; 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 "NativeKeyBindings.h"
+
+#include "nsTArray.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/TextEvents.h"
+
+namespace mozilla {
+namespace widget {
+
+PRLogModuleInfo* gNativeKeyBindingsLog = nullptr;
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings*
+NativeKeyBindings::GetInstance(NativeKeyBindingsType aType)
+{
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ if (!sInstanceForSingleLineEditor) {
+ sInstanceForSingleLineEditor = new NativeKeyBindings();
+ sInstanceForSingleLineEditor->Init(aType);
+ }
+ return sInstanceForSingleLineEditor;
+ case nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ if (!sInstanceForMultiLineEditor) {
+ sInstanceForMultiLineEditor = new NativeKeyBindings();
+ sInstanceForMultiLineEditor->Init(aType);
+ }
+ return sInstanceForMultiLineEditor;
+ default:
+ MOZ_CRASH("Not implemented");
+ return nullptr;
+ }
+}
+
+// static
+void
+NativeKeyBindings::Shutdown()
+{
+ delete sInstanceForSingleLineEditor;
+ sInstanceForSingleLineEditor = nullptr;
+ delete sInstanceForMultiLineEditor;
+ sInstanceForMultiLineEditor = nullptr;
+}
+
+NativeKeyBindings::NativeKeyBindings()
+{
+}
+
+#define SEL_TO_COMMAND(aSel, aCommand) \
+ mSelectorToCommand.Put( \
+ reinterpret_cast<struct objc_selector *>(@selector(aSel)), aCommand)
+
+void
+NativeKeyBindings::Init(NativeKeyBindingsType aType)
+{
+ if (!gNativeKeyBindingsLog) {
+ gNativeKeyBindingsLog = PR_NewLogModule("NativeKeyBindings");
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::Init", this));
+
+ // Many selectors have a one-to-one mapping to a Gecko command. Those mappings
+ // are registered in mSelectorToCommand.
+
+ // Selectors from NSResponder's "Responding to Action Messages" section and
+ // from NSText's "Action Methods for Editing" section
+
+ // TODO: Improves correctness of left / right meaning
+ // TODO: Add real paragraph motions
+
+ // SEL_TO_COMMAND(cancelOperation:, );
+ // SEL_TO_COMMAND(capitalizeWord:, );
+ // SEL_TO_COMMAND(centerSelectionInVisibleArea:, );
+ // SEL_TO_COMMAND(changeCaseOfLetter:, );
+ // SEL_TO_COMMAND(complete:, );
+ SEL_TO_COMMAND(copy:, CommandCopy);
+ // SEL_TO_COMMAND(copyFont:, );
+ // SEL_TO_COMMAND(copyRuler:, );
+ SEL_TO_COMMAND(cut:, CommandCut);
+ SEL_TO_COMMAND(delete:, CommandDelete);
+ SEL_TO_COMMAND(deleteBackward:, CommandDeleteCharBackward);
+ // SEL_TO_COMMAND(deleteBackwardByDecomposingPreviousCharacter:, );
+ SEL_TO_COMMAND(deleteForward:, CommandDeleteCharForward);
+
+ // TODO: deleteTo* selectors are also supposed to add text to a kill buffer
+ SEL_TO_COMMAND(deleteToBeginningOfLine:, CommandDeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToBeginningOfParagraph:, CommandDeleteToBeginningOfLine);
+ SEL_TO_COMMAND(deleteToEndOfLine:, CommandDeleteToEndOfLine);
+ SEL_TO_COMMAND(deleteToEndOfParagraph:, CommandDeleteToEndOfLine);
+ // SEL_TO_COMMAND(deleteToMark:, );
+
+ SEL_TO_COMMAND(deleteWordBackward:, CommandDeleteWordBackward);
+ SEL_TO_COMMAND(deleteWordForward:, CommandDeleteWordForward);
+ // SEL_TO_COMMAND(indent:, );
+ // SEL_TO_COMMAND(insertBacktab:, );
+ // SEL_TO_COMMAND(insertContainerBreak:, );
+ // SEL_TO_COMMAND(insertLineBreak:, );
+ // SEL_TO_COMMAND(insertNewline:, );
+ // SEL_TO_COMMAND(insertNewlineIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertParagraphSeparator:, );
+ // SEL_TO_COMMAND(insertTab:, );
+ // SEL_TO_COMMAND(insertTabIgnoringFieldEditor:, );
+ // SEL_TO_COMMAND(insertDoubleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(insertSingleQuoteIgnoringSubstitution:, );
+ // SEL_TO_COMMAND(lowercaseWord:, );
+ SEL_TO_COMMAND(moveBackward:, CommandCharPrevious);
+ SEL_TO_COMMAND(moveBackwardAndModifySelection:, CommandSelectCharPrevious);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveDown:, CommandEndLine);
+ } else {
+ SEL_TO_COMMAND(moveDown:, CommandLineNext);
+ }
+ SEL_TO_COMMAND(moveDownAndModifySelection:, CommandSelectLineNext);
+ SEL_TO_COMMAND(moveForward:, CommandCharNext);
+ SEL_TO_COMMAND(moveForwardAndModifySelection:, CommandSelectCharNext);
+ SEL_TO_COMMAND(moveLeft:, CommandCharPrevious);
+ SEL_TO_COMMAND(moveLeftAndModifySelection:, CommandSelectCharPrevious);
+ SEL_TO_COMMAND(moveParagraphBackwardAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveParagraphForwardAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveRight:, CommandCharNext);
+ SEL_TO_COMMAND(moveRightAndModifySelection:, CommandSelectCharNext);
+ SEL_TO_COMMAND(moveToBeginningOfDocument:, CommandMoveTop);
+ SEL_TO_COMMAND(moveToBeginningOfDocumentAndModifySelection:,
+ CommandSelectTop);
+ SEL_TO_COMMAND(moveToBeginningOfLine:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfLineAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraph:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToBeginningOfParagraphAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToEndOfDocument:, CommandMoveBottom);
+ SEL_TO_COMMAND(moveToEndOfDocumentAndModifySelection:, CommandSelectBottom);
+ SEL_TO_COMMAND(moveToEndOfLine:, CommandEndLine);
+ SEL_TO_COMMAND(moveToEndOfLineAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraph:, CommandEndLine);
+ SEL_TO_COMMAND(moveToEndOfParagraphAndModifySelection:, CommandSelectEndLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLine:, CommandBeginLine);
+ SEL_TO_COMMAND(moveToLeftEndOfLineAndModifySelection:,
+ CommandSelectBeginLine);
+ SEL_TO_COMMAND(moveToRightEndOfLine:, CommandEndLine);
+ SEL_TO_COMMAND(moveToRightEndOfLineAndModifySelection:, CommandSelectEndLine);
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(moveUp:, CommandBeginLine);
+ } else {
+ SEL_TO_COMMAND(moveUp:, CommandLinePrevious);
+ }
+ SEL_TO_COMMAND(moveUpAndModifySelection:, CommandSelectLinePrevious);
+ SEL_TO_COMMAND(moveWordBackward:, CommandWordPrevious);
+ SEL_TO_COMMAND(moveWordBackwardAndModifySelection:,
+ CommandSelectWordPrevious);
+ SEL_TO_COMMAND(moveWordForward:, CommandWordNext);
+ SEL_TO_COMMAND(moveWordForwardAndModifySelection:, CommandSelectWordNext);
+ SEL_TO_COMMAND(moveWordLeft:, CommandWordPrevious);
+ SEL_TO_COMMAND(moveWordLeftAndModifySelection:, CommandSelectWordPrevious);
+ SEL_TO_COMMAND(moveWordRight:, CommandWordNext);
+ SEL_TO_COMMAND(moveWordRightAndModifySelection:, CommandSelectWordNext);
+ SEL_TO_COMMAND(pageDown:, CommandMovePageDown);
+ SEL_TO_COMMAND(pageDownAndModifySelection:, CommandSelectPageDown);
+ SEL_TO_COMMAND(pageUp:, CommandMovePageUp);
+ SEL_TO_COMMAND(pageUpAndModifySelection:, CommandSelectPageUp);
+ SEL_TO_COMMAND(paste:, CommandPaste);
+ // SEL_TO_COMMAND(pasteFont:, );
+ // SEL_TO_COMMAND(pasteRuler:, );
+ SEL_TO_COMMAND(scrollLineDown:, CommandScrollLineDown);
+ SEL_TO_COMMAND(scrollLineUp:, CommandScrollLineUp);
+ SEL_TO_COMMAND(scrollPageDown:, CommandScrollPageDown);
+ SEL_TO_COMMAND(scrollPageUp:, CommandScrollPageUp);
+ SEL_TO_COMMAND(scrollToBeginningOfDocument:, CommandScrollTop);
+ SEL_TO_COMMAND(scrollToEndOfDocument:, CommandScrollBottom);
+ SEL_TO_COMMAND(selectAll:, CommandSelectAll);
+ // selectLine: is complex, see KeyDown
+ if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) {
+ SEL_TO_COMMAND(selectParagraph:, CommandSelectAll);
+ }
+ // SEL_TO_COMMAND(selectToMark:, );
+ // selectWord: is complex, see KeyDown
+ // SEL_TO_COMMAND(setMark:, );
+ // SEL_TO_COMMAND(showContextHelp:, );
+ // SEL_TO_COMMAND(supplementalTargetForAction:sender:, );
+ // SEL_TO_COMMAND(swapWithMark:, );
+ // SEL_TO_COMMAND(transpose:, );
+ // SEL_TO_COMMAND(transposeWords:, );
+ // SEL_TO_COMMAND(uppercaseWord:, );
+ // SEL_TO_COMMAND(yank:, );
+}
+
+#undef SEL_TO_COMMAND
+
+bool
+NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress", this));
+
+ // Recover the current event, which should always be the key down we are
+ // responding to.
+
+ NSEvent* cocoaEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent);
+
+ if (!cocoaEvent || [cocoaEvent type] != NSKeyDown) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, no Cocoa key down event", this));
+
+ return false;
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, interpreting", this));
+
+ AutoTArray<KeyBindingsCommand, 2> bindingCommands;
+ nsCocoaUtils::GetCommandsFromKeyEvent(cocoaEvent, bindingCommands);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, bindingCommands=%u",
+ this, bindingCommands.Length()));
+
+ AutoTArray<Command, 4> geckoCommands;
+
+ for (uint32_t i = 0; i < bindingCommands.Length(); i++) {
+ SEL selector = bindingCommands[i].selector;
+
+ if (MOZ_LOG_TEST(gNativeKeyBindingsLog, LogLevel::Info)) {
+ NSString* selectorString = NSStringFromSelector(selector);
+ nsAutoString nsSelectorString;
+ nsCocoaUtils::GetStringForNSString(selectorString, nsSelectorString);
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, selector=%s",
+ this, NS_LossyConvertUTF16toASCII(nsSelectorString).get()));
+ }
+
+ // Try to find a simple mapping in the hashtable
+ Command geckoCommand = static_cast<Command>(mSelectorToCommand.Get(
+ reinterpret_cast<struct objc_selector*>(selector)));
+
+ if (geckoCommand) {
+ geckoCommands.AppendElement(geckoCommand);
+ } else if (selector == @selector(selectLine:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ geckoCommands.AppendElement(CommandBeginLine);
+ geckoCommands.AppendElement(CommandSelectEndLine);
+ } else if (selector == @selector(selectWord:)) {
+ // This is functional, but Cocoa's version is direction-less in that
+ // selection direction is not determined until some future directed action
+ // is taken. See bug 282097, comment 79 for more details.
+ geckoCommands.AppendElement(CommandWordPrevious);
+ geckoCommands.AppendElement(CommandSelectWordNext);
+ }
+ }
+
+ if (geckoCommands.IsEmpty()) {
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, handled=false", this));
+
+ return false;
+ }
+
+ for (uint32_t i = 0; i < geckoCommands.Length(); i++) {
+ Command geckoCommand = geckoCommands[i];
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, command=%s",
+ this, WidgetKeyboardEvent::GetCommandStr(geckoCommand)));
+
+ // Execute the Gecko command
+ aCallback(geckoCommand, aCallbackData);
+ }
+
+ MOZ_LOG(gNativeKeyBindingsLog, LogLevel::Info,
+ ("%p NativeKeyBindings::KeyPress, handled=true", this));
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/OSXNotificationCenter.h b/widget/cocoa/OSXNotificationCenter.h
new file mode 100644
index 0000000000..30767b5c55
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef OSXNotificationCenter_h
+#define OSXNotificationCenter_h
+
+#import <Foundation/Foundation.h>
+#include "nsIAlertsService.h"
+#include "imgINotificationObserver.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "mozilla/RefPtr.h"
+
+@class mozNotificationCenterDelegate;
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+typedef NSInteger NSUserNotificationActivationType;
+#endif
+
+namespace mozilla {
+
+class OSXNotificationInfo;
+
+class OSXNotificationCenter : public nsIAlertsService,
+ public nsIAlertsIconData,
+ public nsIAlertNotificationImageListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIALERTSICONDATA
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+
+ OSXNotificationCenter();
+
+ nsresult Init();
+ void CloseAlertCocoaString(NSString *aAlertName);
+ void OnActivate(NSString *aAlertName, NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex);
+ void ShowPendingNotification(OSXNotificationInfo *osxni);
+
+protected:
+ virtual ~OSXNotificationCenter();
+
+private:
+ mozNotificationCenterDelegate *mDelegate;
+ nsTArray<RefPtr<OSXNotificationInfo> > mActiveAlerts;
+ nsTArray<RefPtr<OSXNotificationInfo> > mPendingAlerts;
+};
+
+} // namespace mozilla
+
+#endif // OSXNotificationCenter_h
diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm
new file mode 100644
index 0000000000..e9e36a96be
--- /dev/null
+++ b/widget/cocoa/OSXNotificationCenter.mm
@@ -0,0 +1,589 @@
+/* -*- Mode: C++; tab-width: 2; 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 "OSXNotificationCenter.h"
+#import <AppKit/AppKit.h>
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "nsICancelable.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#import "nsCocoaUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+
+using namespace mozilla;
+
+#define MAX_NOTIFICATION_NAME_LEN 5000
+
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+@protocol NSUserNotificationCenterDelegate
+@end
+static NSString * const NSUserNotificationDefaultSoundName = @"DefaultSoundName";
+enum {
+ NSUserNotificationActivationTypeNone = 0,
+ NSUserNotificationActivationTypeContentsClicked = 1,
+ NSUserNotificationActivationTypeActionButtonClicked = 2,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_9) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9)
+enum {
+ NSUserNotificationActivationTypeReplied = 3,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
+enum {
+ NSUserNotificationActivationTypeAdditionalActionClicked = 4
+};
+#endif
+
+@protocol FakeNSUserNotification <NSObject>
+@property (copy) NSString* title;
+@property (copy) NSString* subtitle;
+@property (copy) NSString* informativeText;
+@property (copy) NSString* actionButtonTitle;
+@property (copy) NSDictionary* userInfo;
+@property (copy) NSDate* deliveryDate;
+@property (copy) NSTimeZone* deliveryTimeZone;
+@property (copy) NSDateComponents* deliveryRepeatInterval;
+@property (readonly) NSDate* actualDeliveryDate;
+@property (readonly, getter=isPresented) BOOL presented;
+@property (readonly, getter=isRemote) BOOL remote;
+@property (copy) NSString* soundName;
+@property BOOL hasActionButton;
+@property (readonly) NSUserNotificationActivationType activationType;
+@property (copy) NSString *otherButtonTitle;
+@property (copy) NSImage *contentImage;
+@end
+
+@protocol FakeNSUserNotificationCenter <NSObject>
++ (id<FakeNSUserNotificationCenter>)defaultUserNotificationCenter;
+@property (assign) id <NSUserNotificationCenterDelegate> delegate;
+@property (copy) NSArray *scheduledNotifications;
+- (void)scheduleNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeScheduledNotification:(id<FakeNSUserNotification>)notification;
+@property (readonly) NSArray *deliveredNotifications;
+- (void)deliverNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeDeliveredNotification:(id<FakeNSUserNotification>)notification;
+- (void)removeAllDeliveredNotifications;
+- (void)_removeAllDisplayedNotifications;
+- (void)_removeDisplayedNotification:(id<FakeNSUserNotification>)notification;
+@end
+
+@interface mozNotificationCenterDelegate : NSObject <NSUserNotificationCenterDelegate>
+{
+ OSXNotificationCenter *mOSXNC;
+}
+ - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc;
+@end
+
+@implementation mozNotificationCenterDelegate
+
+- (id)initWithOSXNC:(OSXNotificationCenter*)osxnc
+{
+ [super init];
+ // We should *never* outlive this OSXNotificationCenter.
+ mOSXNC = osxnc;
+ return self;
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDeliverNotification:(id<FakeNSUserNotification>)notification
+{
+
+}
+
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didActivateNotification:(id<FakeNSUserNotification>)notification
+{
+ unsigned long long additionalActionIndex = ULLONG_MAX;
+ if ([notification respondsToSelector:@selector(_alternateActionIndex)]) {
+ NSNumber *alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"];
+ additionalActionIndex = [alternateActionIndex unsignedLongLongValue];
+ }
+ mOSXNC->OnActivate([[notification userInfo] valueForKey:@"name"],
+ notification.activationType,
+ additionalActionIndex);
+}
+
+- (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ shouldPresentNotification:(id<FakeNSUserNotification>)notification
+{
+ return YES;
+}
+
+// This is an undocumented method that we need for parity with Safari.
+// Apple bug #15440664.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didRemoveDeliveredNotifications:(NSArray *)notifications
+{
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+ }
+}
+
+// This is an undocumented method that we need to be notified if a user clicks the close button.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+ didDismissAlert:(id<FakeNSUserNotification>)notification
+{
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ mOSXNC->CloseAlertCocoaString(name);
+}
+
+@end
+
+namespace mozilla {
+
+enum {
+ OSXNotificationActionDisable = 0,
+ OSXNotificationActionSettings = 1,
+};
+
+class OSXNotificationInfo final : public nsISupports {
+private:
+ virtual ~OSXNotificationInfo();
+
+public:
+ NS_DECL_ISUPPORTS
+ OSXNotificationInfo(NSString *name, nsIObserver *observer,
+ const nsAString & alertCookie);
+
+ NSString *mName;
+ nsCOMPtr<nsIObserver> mObserver;
+ nsString mCookie;
+ RefPtr<nsICancelable> mIconRequest;
+ id<FakeNSUserNotification> mPendingNotifiction;
+};
+
+NS_IMPL_ISUPPORTS0(OSXNotificationInfo)
+
+OSXNotificationInfo::OSXNotificationInfo(NSString *name, nsIObserver *observer,
+ const nsAString & alertCookie)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ASSERTION(name, "Cannot create OSXNotificationInfo without a name!");
+ mName = [name retain];
+ mObserver = observer;
+ mCookie = alertCookie;
+ mPendingNotifiction = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationInfo::~OSXNotificationInfo()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mName release];
+ [mPendingNotifiction release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static id<FakeNSUserNotificationCenter> GetNotificationCenter() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ Class c = NSClassFromString(@"NSUserNotificationCenter");
+ return [c performSelector:@selector(defaultUserNotificationCenter)];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+OSXNotificationCenter::OSXNotificationCenter()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mDelegate = [[mozNotificationCenterDelegate alloc] initWithOSXNC:this];
+ GetNotificationCenter().delegate = mDelegate;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+OSXNotificationCenter::~OSXNotificationCenter()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [GetNotificationCenter() removeAllDeliveredNotifications];
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(OSXNotificationCenter, nsIAlertsService, nsIAlertsIconData,
+ nsIAlertNotificationImageListener)
+
+nsresult OSXNotificationCenter::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
+ const nsAString & aAlertText, bool aAlertTextClickable,
+ const nsAString & aAlertCookie,
+ nsIObserver * aAlertListener,
+ const nsAString & aAlertName,
+ const nsAString & aBidi,
+ const nsAString & aLang,
+ const nsAString & aData,
+ nsIPrincipal * aPrincipal,
+ bool aInPrivateBrowsing,
+ bool aRequireInteraction)
+{
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle,
+ aAlertText, aAlertTextClickable,
+ aAlertCookie, aBidi, aLang, aData,
+ aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowPersistentNotification(const nsAString& aPersistentData,
+ nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener)
+{
+ return ShowAlertWithIconData(aAlert, aAlertListener, 0, nullptr);
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::ShowAlertWithIconData(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener,
+ uint32_t aIconSize,
+ const uint8_t* aIconData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ENSURE_ARG(aAlert);
+
+ Class unClass = NSClassFromString(@"NSUserNotification");
+ id<FakeNSUserNotification> notification = [[unClass alloc] init];
+
+ nsAutoString title;
+ nsresult rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.title = nsCocoaUtils::ToNSString(title);
+
+ nsAutoString hostPort;
+ rv = aAlert->GetSource(hostPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle));
+
+ if (!hostPort.IsEmpty() && bundle) {
+ const char16_t* formatStrings[] = { hostPort.get() };
+ nsXPIDLString notificationSource;
+ bundle->FormatStringFromName(u"source.label",
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(notificationSource));
+ notification.subtitle = nsCocoaUtils::ToNSString(notificationSource);
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ notification.informativeText = nsCocoaUtils::ToNSString(text);
+
+ notification.soundName = NSUserNotificationDefaultSoundName;
+ notification.hasActionButton = NO;
+
+ // If this is not an application/extension alert, show additional actions dealing with permissions.
+ bool isActionable;
+ if (bundle && NS_SUCCEEDED(aAlert->GetActionable(&isActionable)) && isActionable) {
+ nsXPIDLString closeButtonTitle, actionButtonTitle, disableButtonTitle, settingsButtonTitle;
+ bundle->GetStringFromName(u"closeButton.title",
+ getter_Copies(closeButtonTitle));
+ bundle->GetStringFromName(u"actionButton.label",
+ getter_Copies(actionButtonTitle));
+ if (!hostPort.IsEmpty()) {
+ const char16_t* formatStrings[] = { hostPort.get() };
+ bundle->FormatStringFromName(u"webActions.disableForOrigin.label",
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(disableButtonTitle));
+ }
+ bundle->GetStringFromName(u"webActions.settings.label",
+ getter_Copies(settingsButtonTitle));
+
+ notification.otherButtonTitle = nsCocoaUtils::ToNSString(closeButtonTitle);
+
+ // OS X 10.8 only shows action buttons if the "Alerts" style is set in
+ // Notification Center preferences, and doesn't support the alternate
+ // action menu.
+ if ([notification respondsToSelector:@selector(set_showsButtons:)] &&
+ [notification respondsToSelector:@selector(set_alwaysShowAlternateActionMenu:)] &&
+ [notification respondsToSelector:@selector(set_alternateActionButtonTitles:)]) {
+
+ notification.hasActionButton = YES;
+ notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle);
+
+ [(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"];
+ [(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"];
+ [(NSObject*)notification setValue:@[
+ nsCocoaUtils::ToNSString(disableButtonTitle),
+ nsCocoaUtils::ToNSString(settingsButtonTitle)
+ ]
+ forKey:@"_alternateActionButtonTitles"];
+ }
+ }
+ nsAutoString name;
+ rv = aAlert->GetName(name);
+ // Don't let an alert name be more than MAX_NOTIFICATION_NAME_LEN characters.
+ // More than that shouldn't be necessary and userInfo (assigned to below) has
+ // a length limit of 16k on OS X 10.11. Exception thrown if limit exceeded.
+ if (name.Length() > MAX_NOTIFICATION_NAME_LEN) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ NSString *alertName = nsCocoaUtils::ToNSString(name);
+ if (!alertName) {
+ return NS_ERROR_FAILURE;
+ }
+ notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
+ forKeys:[NSArray arrayWithObjects:@"name", nil]];
+
+ nsAutoString cookie;
+ rv = aAlert->GetCookie(cookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, cookie);
+
+ // Show the favicon if supported on this version of OS X.
+ if (aIconSize > 0 &&
+ [notification respondsToSelector:@selector(set_identityImage:)] &&
+ [notification respondsToSelector:@selector(set_identityImageHasBorder:)]) {
+
+ NSData *iconData = [NSData dataWithBytes:aIconData length:aIconSize];
+ NSImage *icon = [[[NSImage alloc] initWithData:iconData] autorelease];
+
+ [(NSObject*)notification setValue:icon forKey:@"_identityImage"];
+ [(NSObject*)notification setValue:@(NO) forKey:@"_identityImageHasBorder"];
+ }
+
+ bool inPrivateBrowsing;
+ rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Show the notification without waiting for an image if there is no icon URL or
+ // notification icons are not supported on this version of OS X.
+ if (![unClass instancesRespondToSelector:@selector(setContentImage:)]) {
+ CloseAlertCocoaString(alertName);
+ mActiveAlerts.AppendElement(osxni);
+ [GetNotificationCenter() deliverNotification:notification];
+ [notification release];
+ if (aAlertListener) {
+ aAlertListener->Observe(nullptr, "alertshow", cookie.get());
+ }
+ } else {
+ mPendingAlerts.AppendElement(osxni);
+ osxni->mPendingNotifiction = notification;
+ // Wait six seconds for the image to load.
+ rv = aAlert->LoadImage(6000, this, osxni,
+ getter_AddRefs(osxni->mIconRequest));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ShowPendingNotification(osxni);
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::CloseAlert(const nsAString& aAlertName,
+ nsIPrincipal* aPrincipal)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString *alertName = nsCocoaUtils::ToNSString(aAlertName);
+ CloseAlertCocoaString(alertName);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+OSXNotificationCenter::CloseAlertCocoaString(NSString *aAlertName)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ NSArray *notifications = [GetNotificationCenter() deliveredNotifications];
+ for (id<FakeNSUserNotification> notification in notifications) {
+ NSString *name = [[notification userInfo] valueForKey:@"name"];
+ if ([name isEqualToString:aAlertName]) {
+ [GetNotificationCenter() removeDeliveredNotification:notification];
+ [GetNotificationCenter() _removeDisplayedNotification:notification];
+ break;
+ }
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo *osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertfinished", osxni->mCookie.get());
+ }
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+ mActiveAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+OSXNotificationCenter::OnActivate(NSString *aAlertName,
+ NSUserNotificationActivationType aActivationType,
+ unsigned long long aAdditionalActionIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aAlertName) {
+ return; // Can't do anything without a name
+ }
+
+ for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
+ OSXNotificationInfo *osxni = mActiveAlerts[i];
+ if ([aAlertName isEqualToString:osxni->mName]) {
+ if (osxni->mObserver) {
+ switch ((int)aActivationType) {
+ case NSUserNotificationActivationTypeAdditionalActionClicked:
+ case NSUserNotificationActivationTypeActionButtonClicked:
+ switch (aAdditionalActionIndex) {
+ case OSXNotificationActionDisable:
+ osxni->mObserver->Observe(nullptr, "alertdisablecallback", osxni->mCookie.get());
+ break;
+ case OSXNotificationActionSettings:
+ osxni->mObserver->Observe(nullptr, "alertsettingscallback", osxni->mCookie.get());
+ break;
+ default:
+ NS_WARNING("Unknown NSUserNotification additional action clicked");
+ break;
+ }
+ break;
+ default:
+ osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
+ break;
+ }
+ }
+ return;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo *osxni)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (osxni->mIconRequest) {
+ osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
+ osxni->mIconRequest = nullptr;
+ }
+
+ CloseAlertCocoaString(osxni->mName);
+
+ for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
+ if (mPendingAlerts[i] == osxni) {
+ mActiveAlerts.AppendElement(osxni);
+ mPendingAlerts.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ [GetNotificationCenter() deliverNotification:osxni->mPendingNotifiction];
+
+ if (osxni->mObserver) {
+ osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get());
+ }
+
+ [osxni->mPendingNotifiction release];
+ osxni->mPendingNotifiction = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageMissing(nsISupports* aUserData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ OSXNotificationInfo *osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (osxni->mPendingNotifiction) {
+ // If there was an error getting the image, or the request timed out, show
+ // the notification without a content image.
+ ShowPendingNotification(osxni);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+OSXNotificationCenter::OnImageReady(nsISupports* aUserData,
+ imgIRequest* aRequest)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_WARN_IF(NS_FAILED(rv) || !image)) {
+ return rv;
+ }
+
+ OSXNotificationInfo *osxni = static_cast<OSXNotificationInfo*>(aUserData);
+ if (!osxni->mPendingNotifiction) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSImage *cocoaImage = nil;
+ nsCocoaUtils::CreateNSImageFromImageContainer(image, imgIContainer::FRAME_FIRST, &cocoaImage, 1.0f);
+ (osxni->mPendingNotifiction).contentImage = cocoaImage;
+ [cocoaImage release];
+ ShowPendingNotification(osxni);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/RectTextureImage.h b/widget/cocoa/RectTextureImage.h
new file mode 100644
index 0000000000..022b216c6b
--- /dev/null
+++ b/widget/cocoa/RectTextureImage.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef RectTextureImage_h_
+#define RectTextureImage_h_
+
+#include "mozilla/RefPtr.h"
+
+class MacIOSurface;
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+} // namespace gl
+
+namespace widget {
+
+// Manages a texture which can resize dynamically, binds to the
+// LOCAL_GL_TEXTURE_RECTANGLE_ARB texture target and is automatically backed
+// by a power-of-two size GL texture. The latter two features are used for
+// compatibility with older Mac hardware which we block GL layers on.
+// RectTextureImages are used both for accelerated GL layers drawing and for
+// OMTC BasicLayers drawing.
+class RectTextureImage {
+public:
+ RectTextureImage();
+
+ virtual ~RectTextureImage();
+
+ already_AddRefed<gfx::DrawTarget>
+ BeginUpdate(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion =
+ LayoutDeviceIntRegion());
+ void EndUpdate();
+
+ void UpdateIfNeeded(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ void (^aCallback)(gfx::DrawTarget*,
+ const LayoutDeviceIntRegion&))
+ {
+ RefPtr<gfx::DrawTarget> drawTarget = BeginUpdate(aNewSize, aDirtyRegion);
+ if (drawTarget) {
+ aCallback(drawTarget, GetUpdateRegion());
+ EndUpdate();
+ }
+ }
+
+ void UpdateFromCGContext(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ CGContextRef aCGContext);
+
+ LayoutDeviceIntRegion GetUpdateRegion() {
+ MOZ_ASSERT(mInUpdate, "update region only valid during update");
+ return mUpdateRegion;
+ }
+
+ void Draw(mozilla::layers::GLManager* aManager,
+ const LayoutDeviceIntPoint& aLocation,
+ const gfx::Matrix4x4& aTransform = gfx::Matrix4x4());
+
+
+protected:
+ void DeleteTexture();
+ void BindIOSurfaceToTexture(gl::GLContext* aGL);
+
+ RefPtr<MacIOSurface> mIOSurface;
+ gl::GLContext* mGLContext;
+ LayoutDeviceIntRegion mUpdateRegion;
+ LayoutDeviceIntSize mBufferSize;
+ GLuint mTexture;
+ bool mInUpdate;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // RectTextureImage_h_
diff --git a/widget/cocoa/RectTextureImage.mm b/widget/cocoa/RectTextureImage.mm
new file mode 100644
index 0000000000..c67af97d0a
--- /dev/null
+++ b/widget/cocoa/RectTextureImage.mm
@@ -0,0 +1,171 @@
+/* -*- Mode: objc; tab-width: 2; 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 "RectTextureImage.h"
+
+#include "gfxUtils.h"
+#include "GLContextCGL.h"
+#include "mozilla/layers/GLManager.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#include "OGLShaderProgram.h"
+#include "ScopedGLHelpers.h"
+
+namespace mozilla {
+namespace widget {
+
+RectTextureImage::RectTextureImage()
+ : mGLContext(nullptr)
+ , mTexture(0)
+ , mInUpdate(false)
+{
+}
+
+RectTextureImage::~RectTextureImage()
+{
+ DeleteTexture();
+}
+
+already_AddRefed<gfx::DrawTarget>
+RectTextureImage::BeginUpdate(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion)
+{
+ MOZ_ASSERT(!mInUpdate, "Beginning update during update!");
+ mUpdateRegion = aDirtyRegion;
+ bool needRecreate = false;
+ if (aNewSize != mBufferSize) {
+ mBufferSize = aNewSize;
+ mUpdateRegion =
+ LayoutDeviceIntRect(LayoutDeviceIntPoint(0, 0), aNewSize);
+ needRecreate = true;
+ }
+
+ if (mUpdateRegion.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (!mIOSurface || needRecreate) {
+ DeleteTexture();
+ mIOSurface = MacIOSurface::CreateIOSurface(mBufferSize.width,
+ mBufferSize.height);
+
+ if (!mIOSurface) {
+ return nullptr;
+ }
+ }
+
+ mInUpdate = true;
+
+ mIOSurface->Lock(false);
+ unsigned char* ioData = (unsigned char*)mIOSurface->GetBaseAddress();
+ gfx::IntSize size(mBufferSize.width, mBufferSize.height);
+ int32_t stride = mIOSurface->GetBytesPerRow();
+ gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8;
+ RefPtr<gfx::DrawTarget> drawTarget =
+ gfx::Factory::CreateDrawTargetForData(gfx::BackendType::SKIA,
+ ioData, size,
+ stride, format);
+ return drawTarget.forget();
+}
+
+void
+RectTextureImage::EndUpdate()
+{
+ MOZ_ASSERT(mInUpdate, "Ending update while not in update");
+ mIOSurface->Unlock(false);
+ mInUpdate = false;
+}
+
+void
+RectTextureImage::UpdateFromCGContext(const LayoutDeviceIntSize& aNewSize,
+ const LayoutDeviceIntRegion& aDirtyRegion,
+ CGContextRef aCGContext)
+{
+ gfx::IntSize size = gfx::IntSize(CGBitmapContextGetWidth(aCGContext),
+ CGBitmapContextGetHeight(aCGContext));
+ RefPtr<gfx::DrawTarget> dt = BeginUpdate(aNewSize, aDirtyRegion);
+ if (dt) {
+ gfx::Rect rect(0, 0, size.width, size.height);
+ gfxUtils::ClipToRegion(dt, GetUpdateRegion().ToUnknownRegion());
+ RefPtr<gfx::SourceSurface> sourceSurface =
+ dt->CreateSourceSurfaceFromData(static_cast<uint8_t *>(CGBitmapContextGetData(aCGContext)),
+ size,
+ CGBitmapContextGetBytesPerRow(aCGContext),
+ gfx::SurfaceFormat::B8G8R8A8);
+ dt->DrawSurface(sourceSurface, rect, rect,
+ gfx::DrawSurfaceOptions(),
+ gfx::DrawOptions(1.0, gfx::CompositionOp::OP_SOURCE));
+ dt->PopClip();
+ EndUpdate();
+ }
+}
+
+void
+RectTextureImage::Draw(layers::GLManager* aManager,
+ const LayoutDeviceIntPoint& aLocation,
+ const gfx::Matrix4x4& aTransform)
+{
+ gl::GLContext* gl = aManager->gl();
+
+ BindIOSurfaceToTexture(gl);
+
+ layers::ShaderProgramOGL* program =
+ aManager->GetProgram(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ gfx::SurfaceFormat::R8G8B8A8);
+
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0);
+ gl::ScopedBindTexture texture(gl, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+
+ aManager->ActivateProgram(program);
+ program->SetProjectionMatrix(aManager->GetProjMatrix());
+ program->SetLayerTransform(gfx::Matrix4x4(aTransform).PostTranslate(aLocation.x, aLocation.y, 0));
+ program->SetTextureTransform(gfx::Matrix4x4());
+ program->SetRenderOffset(nsIntPoint(0, 0));
+ program->SetTexCoordMultiplier(mBufferSize.width, mBufferSize.height);
+ program->SetTextureUnit(0);
+
+ aManager->BindAndDrawQuad(program,
+ gfx::Rect(0.0, 0.0, mBufferSize.width, mBufferSize.height),
+ gfx::Rect(0.0, 0.0, 1.0f, 1.0f));
+}
+
+void
+RectTextureImage::DeleteTexture()
+{
+ if (mTexture) {
+ MOZ_ASSERT(mGLContext);
+ mGLContext->MakeCurrent();
+ mGLContext->fDeleteTextures(1, &mTexture);
+ mTexture = 0;
+ }
+}
+
+void
+RectTextureImage::BindIOSurfaceToTexture(gl::GLContext* aGL)
+{
+ if (!mTexture) {
+ MOZ_ASSERT(aGL);
+ aGL->fGenTextures(1, &mTexture);
+ aGL->fActiveTexture(LOCAL_GL_TEXTURE0);
+ gl::ScopedBindTexture texture(aGL, mTexture, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGL->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB,
+ LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+
+ mIOSurface->CGLTexImageIOSurface2D(gl::GLContextCGL::Cast(aGL)->GetCGLContext());
+ mGLContext = aGL;
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/cocoa/SwipeTracker.h b/widget/cocoa/SwipeTracker.h
new file mode 100644
index 0000000000..78940d15cc
--- /dev/null
+++ b/widget/cocoa/SwipeTracker.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef SwipeTracker_h
+#define SwipeTracker_h
+
+#include "EventForwards.h"
+#include "mozilla/layers/AxisPhysicsMSDModel.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsRefreshDriver.h"
+#include "Units.h"
+
+class nsIPresShell;
+
+namespace mozilla {
+
+class PanGestureInput;
+
+/**
+ * SwipeTracker turns PanGestureInput events into swipe events
+ * (WidgetSimpleGestureEvent) and dispatches them into Gecko.
+ * The swiping behavior mirrors the behavior of the Cocoa API
+ * -[NSEvent trackSwipeEventWithOptions:dampenAmountThresholdMin:max:usingHandler:].
+ * The advantage of using this class over the Cocoa API is that this class
+ * properly supports submitting queued up events to it, and that it hopefully
+ * doesn't intermittently break scrolling the way the Cocoa API does (bug 927702).
+ *
+ * The swipe direction is either left or right. It is determined before the
+ * SwipeTracker is created and stays fixed during the swipe.
+ * During the swipe, the swipe has a current "value" which is between 0 and the
+ * target value. The target value is either 1 (swiping left) or -1 (swiping
+ * right) - see SwipeSuccessTargetValue().
+ * A swipe can either succeed or fail. If it succeeds, the swipe animation
+ * animates towards the success target value; if it fails, it animates back to
+ * a value of 0. A swipe can only succeed if the user is swiping in an allowed
+ * direction. (Since both the allowed directions and the swipe direction are
+ * known at swipe start time, it's clear from the beginning whether a swipe is
+ * doomed to fail. In that case, the purpose of the SwipeTracker is to simulate
+ * a bounce-back animation.)
+ */
+class SwipeTracker final : public nsARefreshObserver {
+public:
+ NS_INLINE_DECL_REFCOUNTING(SwipeTracker, override)
+
+ SwipeTracker(nsChildView& aWidget,
+ const PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections,
+ uint32_t aSwipeDirection);
+
+ void Destroy();
+
+ nsEventStatus ProcessEvent(const PanGestureInput& aEvent);
+ void CancelSwipe();
+
+ static WidgetSimpleGestureEvent
+ CreateSwipeGestureEvent(EventMessage aMsg, nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPosition);
+
+
+ // nsARefreshObserver
+ void WillRefresh(mozilla::TimeStamp aTime) override;
+
+protected:
+ ~SwipeTracker();
+
+ bool SwipingInAllowedDirection() const { return mAllowedDirections & mSwipeDirection; }
+ double SwipeSuccessTargetValue() const;
+ double ClampToAllowedRange(double aGestureAmount) const;
+ bool ComputeSwipeSuccess() const;
+ void StartAnimating(double aTargetValue);
+ void SwipeFinished();
+ void UnregisterFromRefreshDriver();
+ bool SendSwipeEvent(EventMessage aMsg, uint32_t aDirection, double aDelta);
+
+ nsChildView& mWidget;
+ RefPtr<nsRefreshDriver> mRefreshDriver;
+ layers::AxisPhysicsMSDModel mAxis;
+ const LayoutDeviceIntPoint mEventPosition;
+ TimeStamp mLastEventTimeStamp;
+ TimeStamp mLastAnimationFrameTime;
+ const uint32_t mAllowedDirections;
+ const uint32_t mSwipeDirection;
+ double mGestureAmount;
+ double mCurrentVelocity;
+ bool mEventsAreControllingSwipe;
+ bool mEventsHaveStartedNewGesture;
+ bool mRegisteredWithRefreshDriver;
+};
+
+} // namespace mozilla
+
+#endif // SwipeTracker_h
diff --git a/widget/cocoa/SwipeTracker.mm b/widget/cocoa/SwipeTracker.mm
new file mode 100644
index 0000000000..51169171a4
--- /dev/null
+++ b/widget/cocoa/SwipeTracker.mm
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 8; 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 "SwipeTracker.h"
+
+#include "InputData.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "nsAlgorithm.h"
+#include "nsChildView.h"
+#include "UnitTransforms.h"
+
+// These values were tweaked to make the physics feel similar to the native swipe.
+static const double kSpringForce = 250.0;
+static const double kVelocityTwitchTolerance = 0.0000001;
+static const double kWholePagePixelSize = 1000.0;
+static const double kRubberBandResistanceFactor = 4.0;
+static const double kSwipeSuccessThreshold = 0.25;
+static const double kSwipeSuccessVelocityContribution = 0.3;
+
+namespace mozilla {
+
+static already_AddRefed<nsRefreshDriver>
+GetRefreshDriver(nsIWidget& aWidget)
+{
+ nsIWidgetListener* widgetListener = aWidget.GetWidgetListener();
+ nsIPresShell* presShell = widgetListener ? widgetListener->GetPresShell() : nullptr;
+ nsPresContext* presContext = presShell ? presShell->GetPresContext() : nullptr;
+ RefPtr<nsRefreshDriver> refreshDriver = presContext ? presContext->RefreshDriver() : nullptr;
+ return refreshDriver.forget();
+}
+
+SwipeTracker::SwipeTracker(nsChildView& aWidget,
+ const PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections,
+ uint32_t aSwipeDirection)
+ : mWidget(aWidget)
+ , mRefreshDriver(GetRefreshDriver(mWidget))
+ , mAxis(0.0, 0.0, 0.0, kSpringForce, 1.0)
+ , mEventPosition(RoundedToInt(ViewAs<LayoutDevicePixel>(aSwipeStartEvent.mPanStartPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent)))
+ , mLastEventTimeStamp(aSwipeStartEvent.mTimeStamp)
+ , mAllowedDirections(aAllowedDirections)
+ , mSwipeDirection(aSwipeDirection)
+ , mGestureAmount(0.0)
+ , mCurrentVelocity(0.0)
+ , mEventsAreControllingSwipe(true)
+ , mEventsHaveStartedNewGesture(false)
+ , mRegisteredWithRefreshDriver(false)
+{
+ SendSwipeEvent(eSwipeGestureStart, 0, 0.0);
+ ProcessEvent(aSwipeStartEvent);
+}
+
+void
+SwipeTracker::Destroy()
+{
+ UnregisterFromRefreshDriver();
+}
+
+SwipeTracker::~SwipeTracker()
+{
+ MOZ_ASSERT(!mRegisteredWithRefreshDriver, "Destroy needs to be called before deallocating");
+}
+
+double
+SwipeTracker::SwipeSuccessTargetValue() const
+{
+ return (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ? -1.0 : 1.0;
+}
+
+double
+SwipeTracker::ClampToAllowedRange(double aGestureAmount) const
+{
+ // gestureAmount needs to stay between -1 and 0 when swiping right and
+ // between 0 and 1 when swiping left.
+ double min = (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_RIGHT) ? -1.0 : 0.0;
+ double max = (mSwipeDirection == nsIDOMSimpleGestureEvent::DIRECTION_LEFT) ? 1.0 : 0.0;
+ return clamped(aGestureAmount, min, max);
+}
+
+bool
+SwipeTracker::ComputeSwipeSuccess() const
+{
+ double targetValue = SwipeSuccessTargetValue();
+
+ // If the fingers were moving away from the target direction when they were
+ // lifted from the touchpad, abort the swipe.
+ if (mCurrentVelocity * targetValue < -kVelocityTwitchTolerance) {
+ return false;
+ }
+
+ return (mGestureAmount * targetValue +
+ mCurrentVelocity * targetValue * kSwipeSuccessVelocityContribution) >= kSwipeSuccessThreshold;
+}
+
+nsEventStatus
+SwipeTracker::ProcessEvent(const PanGestureInput& aEvent)
+{
+ // If the fingers have already been lifted, don't process this event for swiping.
+ if (!mEventsAreControllingSwipe) {
+ // Return nsEventStatus_eConsumeNoDefault for events from the swipe gesture
+ // and nsEventStatus_eIgnore for events of subsequent scroll gestures.
+ if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART ||
+ aEvent.mType == PanGestureInput::PANGESTURE_START) {
+ mEventsHaveStartedNewGesture = true;
+ }
+ return mEventsHaveStartedNewGesture ? nsEventStatus_eIgnore : nsEventStatus_eConsumeNoDefault;
+ }
+
+ double delta = -aEvent.mPanDisplacement.x / mWidget.BackingScaleFactor() / kWholePagePixelSize;
+ if (!SwipingInAllowedDirection()) {
+ delta /= kRubberBandResistanceFactor;
+ }
+ mGestureAmount = ClampToAllowedRange(mGestureAmount + delta);
+ SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount);
+
+ if (aEvent.mType != PanGestureInput::PANGESTURE_END) {
+ double elapsedSeconds = std::max(0.008, (aEvent.mTimeStamp - mLastEventTimeStamp).ToSeconds());
+ mCurrentVelocity = delta / elapsedSeconds;
+ mLastEventTimeStamp = aEvent.mTimeStamp;
+ } else {
+ mEventsAreControllingSwipe = false;
+ bool didSwipeSucceed = SwipingInAllowedDirection() && ComputeSwipeSuccess();
+ double targetValue = 0.0;
+ if (didSwipeSucceed) {
+ SendSwipeEvent(eSwipeGesture, mSwipeDirection, 0.0);
+ targetValue = SwipeSuccessTargetValue();
+ }
+ StartAnimating(targetValue);
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+void
+SwipeTracker::StartAnimating(double aTargetValue)
+{
+ mAxis.SetPosition(mGestureAmount);
+ mAxis.SetDestination(aTargetValue);
+ mAxis.SetVelocity(mCurrentVelocity);
+
+ mLastAnimationFrameTime = TimeStamp::Now();
+
+ // Add ourselves as a refresh driver observer. The refresh driver
+ // will call WillRefresh for each animation frame until we
+ // unregister ourselves.
+ MOZ_ASSERT(!mRegisteredWithRefreshDriver);
+ if (mRefreshDriver) {
+ mRefreshDriver->AddRefreshObserver(this, Flush_Style);
+ mRegisteredWithRefreshDriver = true;
+ }
+}
+
+void
+SwipeTracker::WillRefresh(mozilla::TimeStamp aTime)
+{
+ TimeStamp now = TimeStamp::Now();
+ mAxis.Simulate(now - mLastAnimationFrameTime);
+ mLastAnimationFrameTime = now;
+
+ bool isFinished = mAxis.IsFinished(1.0 / kWholePagePixelSize);
+ mGestureAmount = (isFinished ? mAxis.GetDestination() : mAxis.GetPosition());
+ SendSwipeEvent(eSwipeGestureUpdate, 0, mGestureAmount);
+
+ if (isFinished) {
+ UnregisterFromRefreshDriver();
+ SwipeFinished();
+ }
+}
+
+void
+SwipeTracker::CancelSwipe()
+{
+ SendSwipeEvent(eSwipeGestureEnd, 0, 0.0);
+}
+
+void SwipeTracker::SwipeFinished()
+{
+ SendSwipeEvent(eSwipeGestureEnd, 0, 0.0);
+ mWidget.SwipeFinished();
+}
+
+void
+SwipeTracker::UnregisterFromRefreshDriver()
+{
+ if (mRegisteredWithRefreshDriver) {
+ MOZ_ASSERT(mRefreshDriver, "How were we able to register, then?");
+ mRefreshDriver->RemoveRefreshObserver(this, Flush_Style);
+ }
+ mRegisteredWithRefreshDriver = false;
+}
+
+/* static */ WidgetSimpleGestureEvent
+SwipeTracker::CreateSwipeGestureEvent(EventMessage aMsg, nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPosition)
+{
+ WidgetSimpleGestureEvent geckoEvent(true, aMsg, aWidget);
+ geckoEvent.mModifiers = 0;
+ geckoEvent.mTimeStamp = TimeStamp::Now();
+ geckoEvent.mRefPoint = aPosition;
+ geckoEvent.buttons = 0;
+ return geckoEvent;
+}
+
+bool
+SwipeTracker::SendSwipeEvent(EventMessage aMsg, uint32_t aDirection, double aDelta)
+{
+ WidgetSimpleGestureEvent geckoEvent =
+ CreateSwipeGestureEvent(aMsg, &mWidget, mEventPosition);
+ geckoEvent.mDirection = aDirection;
+ geckoEvent.mDelta = aDelta;
+ geckoEvent.mAllowedDirections = mAllowedDirections;
+ return mWidget.DispatchWindowEvent(geckoEvent);
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/TextInputHandler.h b/widget/cocoa/TextInputHandler.h
new file mode 100644
index 0000000000..86da354abb
--- /dev/null
+++ b/widget/cocoa/TextInputHandler.h
@@ -0,0 +1,1194 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef TextInputHandler_h_
+#define TextInputHandler_h_
+
+#include "nsCocoaUtils.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#include "mozView.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "WritingModes.h"
+
+class nsChildView;
+
+namespace mozilla {
+namespace widget {
+
+// Key code constants
+enum
+{
+#if !defined(MAC_OS_X_VERSION_10_12) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+ kVK_RightCommand = 0x36, // right command key
+#endif
+
+ kVK_PC_PrintScreen = kVK_F13,
+ kVK_PC_ScrollLock = kVK_F14,
+ kVK_PC_Pause = kVK_F15,
+
+ kVK_PC_Insert = kVK_Help,
+ kVK_PC_Backspace = kVK_Delete,
+ kVK_PC_Delete = kVK_ForwardDelete,
+
+ kVK_PC_ContextMenu = 0x6E,
+
+ kVK_Powerbook_KeypadEnter = 0x34 // Enter on Powerbook's keyboard is different
+};
+
+/**
+ * TISInputSourceWrapper is a wrapper for the TISInputSourceRef. If we get the
+ * TISInputSourceRef from InputSourceID, we need to release the CFArray instance
+ * which is returned by TISCreateInputSourceList. However, when we release the
+ * list, we cannot access the TISInputSourceRef. So, it's not usable, and it
+ * may cause the memory leak bugs. nsTISInputSource automatically releases the
+ * list when the instance is destroyed.
+ */
+class TISInputSourceWrapper
+{
+public:
+ static TISInputSourceWrapper& CurrentInputSource();
+ /**
+ * Shutdown() should be called when nobody doesn't need to use this class.
+ */
+ static void Shutdown();
+
+ TISInputSourceWrapper()
+ {
+ mInputSourceList = nullptr;
+ Clear();
+ }
+
+ explicit TISInputSourceWrapper(const char* aID)
+ {
+ mInputSourceList = nullptr;
+ InitByInputSourceID(aID);
+ }
+
+ explicit TISInputSourceWrapper(SInt32 aLayoutID)
+ {
+ mInputSourceList = nullptr;
+ InitByLayoutID(aLayoutID);
+ }
+
+ explicit TISInputSourceWrapper(TISInputSourceRef aInputSource)
+ {
+ mInputSourceList = nullptr;
+ InitByTISInputSourceRef(aInputSource);
+ }
+
+ ~TISInputSourceWrapper() { Clear(); }
+
+ void InitByInputSourceID(const char* aID);
+ void InitByInputSourceID(const nsAFlatString &aID);
+ void InitByInputSourceID(const CFStringRef aID);
+ /**
+ * InitByLayoutID() initializes the keyboard layout by the layout ID.
+ *
+ * @param aLayoutID An ID of keyboard layout.
+ * 0: US
+ * 1: Greek
+ * 2: German
+ * 3: Swedish-Pro
+ * 4: Dvorak-Qwerty Cmd
+ * 5: Thai
+ * 6: Arabic
+ * 7: French
+ * 8: Hebrew
+ * 9: Lithuanian
+ * 10: Norwegian
+ * 11: Spanish
+ * @param aOverrideKeyboard When testing set to TRUE, otherwise, set to
+ * FALSE. When TRUE, we use an ANSI keyboard
+ * instead of the actual keyboard.
+ */
+ void InitByLayoutID(SInt32 aLayoutID, bool aOverrideKeyboard = false);
+ void InitByCurrentInputSource();
+ void InitByCurrentKeyboardLayout();
+ void InitByCurrentASCIICapableInputSource();
+ void InitByCurrentASCIICapableKeyboardLayout();
+ void InitByCurrentInputMethodKeyboardLayoutOverride();
+ void InitByTISInputSourceRef(TISInputSourceRef aInputSource);
+ void InitByLanguage(CFStringRef aLanguage);
+
+ /**
+ * If the instance is initialized with a keyboard layout input source,
+ * returns it.
+ * If the instance is initialized with an IME mode input source, the result
+ * references the keyboard layout for the IME mode. However, this can be
+ * initialized only when the IME mode is actually selected. I.e, if IME mode
+ * input source is initialized with LayoutID or SourceID, this returns null.
+ */
+ TISInputSourceRef GetKeyboardLayoutInputSource() const
+ {
+ return mKeyboardLayout;
+ }
+ const UCKeyboardLayout* GetUCKeyboardLayout();
+
+ bool IsOpenedIMEMode();
+ bool IsIMEMode();
+ bool IsKeyboardLayout();
+
+ bool IsASCIICapable()
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetBoolProperty(kTISPropertyInputSourceIsASCIICapable);
+ }
+
+ bool IsEnabled()
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetBoolProperty(kTISPropertyInputSourceIsEnabled);
+ }
+
+ bool GetLanguageList(CFArrayRef &aLanguageList);
+ bool GetPrimaryLanguage(CFStringRef &aPrimaryLanguage);
+ bool GetPrimaryLanguage(nsAString &aPrimaryLanguage);
+
+ bool GetLocalizedName(CFStringRef &aName)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyLocalizedName, aName);
+ }
+
+ bool GetLocalizedName(nsAString &aName)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyLocalizedName, aName);
+ }
+
+ bool GetInputSourceID(CFStringRef &aID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceID, aID);
+ }
+
+ bool GetInputSourceID(nsAString &aID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceID, aID);
+ }
+
+ bool GetBundleID(CFStringRef &aBundleID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyBundleID, aBundleID);
+ }
+
+ bool GetBundleID(nsAString &aBundleID)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyBundleID, aBundleID);
+ }
+
+ bool GetInputSourceType(CFStringRef &aType)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceType, aType);
+ }
+
+ bool GetInputSourceType(nsAString &aType)
+ {
+ NS_ENSURE_TRUE(mInputSource, false);
+ return GetStringProperty(kTISPropertyInputSourceType, aType);
+ }
+
+ bool IsForRTLLanguage();
+ bool IsInitializedByCurrentInputSource();
+
+ enum {
+ // 40 is an actual result of the ::LMGetKbdType() when we connect an
+ // unknown keyboard and set the keyboard type to ANSI manually on the
+ // set up dialog.
+ eKbdType_ANSI = 40
+ };
+
+ void Select();
+ void Clear();
+
+ /**
+ * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event.
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ */
+ void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ const nsAString *aInsertString = nullptr);
+
+ /**
+ * WillDispatchKeyboardEvent() computes aKeyEvent.mAlternativeCharCodes and
+ * recompute aKeyEvent.mCharCode if it's necessary.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event. This must be
+ * eKeyPress event.
+ */
+ void WillDispatchKeyboardEvent(NSEvent* aNativeKeyEvent,
+ const nsAString* aInsertString,
+ WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * ComputeGeckoKeyCode() returns Gecko keycode for aNativeKeyCode on current
+ * keyboard layout.
+ *
+ * @param aNativeKeyCode A native keycode.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @param aCmdIsPressed TRUE if Cmd key is pressed. Otherwise, FALSE.
+ * @return The computed Gecko keycode.
+ */
+ uint32_t ComputeGeckoKeyCode(UInt32 aNativeKeyCode, UInt32 aKbType,
+ bool aCmdIsPressed);
+
+ /**
+ * ComputeGeckoKeyNameIndex() returns Gecko key name index for the key.
+ *
+ * @param aNativeKeyCode A native keycode.
+ */
+ static KeyNameIndex ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode);
+
+ /**
+ * ComputeGeckoCodeNameIndex() returns Gecko code name index for the key.
+ *
+ * @param aNativeKeyCode A native keycode.
+ */
+ static CodeNameIndex ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode);
+
+protected:
+ /**
+ * TranslateToString() computes the inputted text from the native keyCode,
+ * modifier flags and keyboard type.
+ *
+ * @param aKeyCode A native keyCode.
+ * @param aModifiers Combination of native modifier flags.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @param aStr Result, i.e., inputted text.
+ * The result can be two or more characters.
+ * @return If succeeded, TRUE. Otherwise, FALSE.
+ * Even if TRUE, aStr can be empty string.
+ */
+ bool TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
+ UInt32 aKbType, nsAString &aStr);
+
+ /**
+ * TranslateToChar() computes the inputted character from the native keyCode,
+ * modifier flags and keyboard type. If two or more characters would be
+ * input, this returns 0.
+ *
+ * @param aKeyCode A native keyCode.
+ * @param aModifiers Combination of native modifier flags.
+ * @param aKbType A native Keyboard Type value. Typically,
+ * this is a result of ::LMGetKbdType().
+ * @return If succeeded and the result is one character,
+ * returns the charCode of it. Otherwise,
+ * returns 0.
+ */
+ uint32_t TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers, UInt32 aKbType);
+
+ /**
+ * ComputeInsertString() computes string to be inserted with the key event.
+ *
+ * @param aNativeKeyEvent The native key event which causes our keyboard
+ * event(s).
+ * @param aKeyEvent A Gecko key event which was partially
+ * initialized with aNativeKeyEvent.
+ * @param aInsertString The string to be inputting by aNativeKeyEvent.
+ * This should be specified by InsertText().
+ * In other words, if the key event doesn't cause
+ * a call of InsertText(), this can be nullptr.
+ * @param aResult The string which should be set to charCode of
+ * keypress event(s).
+ */
+ void ComputeInsertStringForCharCode(NSEvent* aNativeKeyEvent,
+ const WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString,
+ nsAString& aResult);
+
+ /**
+ * IsPrintableKeyEvent() returns true if aNativeKeyEvent is caused by
+ * a printable key. Otherwise, returns false.
+ */
+ bool IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const;
+
+ /**
+ * GetKbdType() returns physical keyboard type.
+ */
+ UInt32 GetKbdType() const;
+
+ bool GetBoolProperty(const CFStringRef aKey);
+ bool GetStringProperty(const CFStringRef aKey, CFStringRef &aStr);
+ bool GetStringProperty(const CFStringRef aKey, nsAString &aStr);
+
+ TISInputSourceRef mInputSource;
+ TISInputSourceRef mKeyboardLayout;
+ CFArrayRef mInputSourceList;
+ const UCKeyboardLayout* mUCKeyboardLayout;
+ int8_t mIsRTL;
+
+ bool mOverrideKeyboard;
+
+ static TISInputSourceWrapper* sCurrentInputSource;
+};
+
+/**
+ * TextInputHandlerBase is a base class of IMEInputHandler and TextInputHandler.
+ * Utility methods should be implemented this level.
+ */
+
+class TextInputHandlerBase : public TextEventDispatcherListener
+{
+public:
+ /**
+ * Other TextEventDispatcherListener methods should be implemented in
+ * IMEInputHandler.
+ */
+ NS_DECL_ISUPPORTS
+
+ /**
+ * DispatchEvent() dispatches aEvent on mWidget.
+ *
+ * @param aEvent An event which you want to dispatch.
+ * @return TRUE if the event is consumed by web contents
+ * or chrome contents. Otherwise, FALSE.
+ */
+ bool DispatchEvent(WidgetGUIEvent& aEvent);
+
+ /**
+ * SetSelection() dispatches eSetSelection event for the aRange.
+ *
+ * @param aRange The range which will be selected.
+ * @return TRUE if setting selection is succeeded and
+ * the widget hasn't been destroyed.
+ * Otherwise, FALSE.
+ */
+ bool SetSelection(NSRange& aRange);
+
+ /**
+ * InitKeyEvent() initializes aKeyEvent for aNativeKeyEvent.
+ *
+ * @param aNativeKeyEvent A native key event for which you want to
+ * dispatch a Gecko key event.
+ * @param aKeyEvent The result -- a Gecko key event initialized
+ * from the native key event.
+ * @param aInsertString If caller expects that the event will cause
+ * a character to be input (say in an editor),
+ * the caller should set this. Otherwise,
+ * if caller sets null to this, this method will
+ * compute the character to be input from
+ * characters of aNativeKeyEvent.
+ */
+ void InitKeyEvent(NSEvent *aNativeKeyEvent, WidgetKeyboardEvent& aKeyEvent,
+ const nsAString *aInsertString = nullptr);
+
+ /**
+ * SynthesizeNativeKeyEvent() is an implementation of
+ * nsIWidget::SynthesizeNativeKeyEvent(). See the document in nsIWidget.h
+ * for the detail.
+ */
+ nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters);
+
+ /**
+ * Utility method intended for testing. Attempts to construct a native key
+ * event that would have been generated during an actual key press. This
+ * *does not dispatch* the native event. Instead, it is attached to the
+ * |mNativeKeyEvent| field of the Gecko event that is passed in.
+ * @param aKeyEvent Gecko key event to attach the native event to
+ */
+ NS_IMETHOD AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * GetWindowLevel() returns the window level of current focused (in Gecko)
+ * window. E.g., if an <input> element in XUL panel has focus, this returns
+ * the XUL panel's window level.
+ */
+ NSInteger GetWindowLevel();
+
+ /**
+ * IsSpecialGeckoKey() checks whether aNativeKeyCode is mapped to a special
+ * Gecko keyCode. A key is "special" if it isn't used for text input.
+ *
+ * @param aNativeKeyCode A native keycode.
+ * @return If the keycode is mapped to a special key,
+ * TRUE. Otherwise, FALSE.
+ */
+ static bool IsSpecialGeckoKey(UInt32 aNativeKeyCode);
+
+
+ /**
+ * EnableSecureEventInput() and DisableSecureEventInput() wrap the Carbon
+ * Event Manager APIs with the same names. In addition they keep track of
+ * how many times we've called them (in the same process) -- unlike the
+ * Carbon Event Manager APIs, which only keep track of how many times they've
+ * been called from any and all processes.
+ *
+ * The Carbon Event Manager's IsSecureEventInputEnabled() returns whether
+ * secure event input mode is enabled (in any process). This class's
+ * IsSecureEventInputEnabled() returns whether we've made any calls to
+ * EnableSecureEventInput() that are not (yet) offset by the calls we've
+ * made to DisableSecureEventInput().
+ */
+ static void EnableSecureEventInput();
+ static void DisableSecureEventInput();
+ static bool IsSecureEventInputEnabled();
+
+ /**
+ * EnsureSecureEventInputDisabled() calls DisableSecureEventInput() until
+ * our call count becomes 0.
+ */
+ static void EnsureSecureEventInputDisabled();
+
+public:
+ /**
+ * mWidget must not be destroyed without OnDestroyWidget being called.
+ *
+ * @param aDestroyingWidget Destroying widget. This might not be mWidget.
+ * @return This result doesn't have any meaning for
+ * callers. When aDstroyingWidget isn't the same
+ * as mWidget, FALSE. Then, inherited methods in
+ * sub classes should return from this method
+ * without cleaning up.
+ */
+ virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget);
+
+protected:
+ // The creator of this instance, client and its text event dispatcher.
+ // These members must not be nullptr after initialized until
+ // OnDestroyWidget() is called.
+ nsChildView* mWidget; // [WEAK]
+ RefPtr<TextEventDispatcher> mDispatcher;
+
+ // The native view for mWidget.
+ // This view handles the actual text inputting.
+ NSView<mozView>* mView; // [STRONG]
+
+ TextInputHandlerBase(nsChildView* aWidget, NSView<mozView> *aNativeView);
+ virtual ~TextInputHandlerBase();
+
+ bool Destroyed() { return !mWidget; }
+
+ /**
+ * mCurrentKeyEvent indicates what key event we are handling. While
+ * handling a native keydown event, we need to store the event for insertText,
+ * doCommandBySelector and various action message handlers of NSResponder
+ * such as [NSResponder insertNewline:sender].
+ */
+ struct KeyEventState
+ {
+ // Handling native key event
+ NSEvent* mKeyEvent;
+ // String specified by InsertText(). This is not null only during a
+ // call of InsertText().
+ nsAString* mInsertString;
+ // String which are included in [mKeyEvent characters] and already handled
+ // by InsertText() call(s).
+ nsString mInsertedString;
+ // Whether keydown event was consumed by web contents or chrome contents.
+ bool mKeyDownHandled;
+ // Whether keypress event was dispatched for mKeyEvent.
+ bool mKeyPressDispatched;
+ // Whether keypress event was consumed by web contents or chrome contents.
+ bool mKeyPressHandled;
+ // Whether the key event causes other key events via IME or something.
+ bool mCausedOtherKeyEvents;
+ // Whether the key event causes composition change or committing
+ // composition. So, even if InsertText() is called, this may be false
+ // if it dispatches keypress event.
+ bool mCompositionDispatched;
+
+ KeyEventState() : mKeyEvent(nullptr)
+ {
+ Clear();
+ }
+
+ explicit KeyEventState(NSEvent* aNativeKeyEvent) : mKeyEvent(nullptr)
+ {
+ Clear();
+ Set(aNativeKeyEvent);
+ }
+
+ KeyEventState(const KeyEventState &aOther) = delete;
+
+ ~KeyEventState()
+ {
+ Clear();
+ }
+
+ void Set(NSEvent* aNativeKeyEvent)
+ {
+ NS_PRECONDITION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
+ Clear();
+ mKeyEvent = [aNativeKeyEvent retain];
+ }
+
+ void Clear()
+ {
+ if (mKeyEvent) {
+ [mKeyEvent release];
+ mKeyEvent = nullptr;
+ }
+ mInsertString = nullptr;
+ mInsertedString.Truncate();
+ mKeyDownHandled = false;
+ mKeyPressDispatched = false;
+ mKeyPressHandled = false;
+ mCausedOtherKeyEvents = false;
+ mCompositionDispatched = false;
+ }
+
+ bool IsDefaultPrevented() const
+ {
+ return mKeyDownHandled || mKeyPressHandled || mCausedOtherKeyEvents ||
+ mCompositionDispatched;
+ }
+
+ bool CanDispatchKeyPressEvent() const
+ {
+ return !mKeyPressDispatched && !IsDefaultPrevented();
+ }
+
+ void InitKeyEvent(TextInputHandlerBase* aHandler,
+ WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * GetUnhandledString() returns characters of the event which have not been
+ * handled with InsertText() yet. For example, if there is a composition
+ * caused by a dead key press like '`' and it's committed by some key
+ * combinations like |Cmd+v|, then, the |v|'s KeyDown event's |characters|
+ * is |`v|. Then, after |`| is committed with a call of InsertString(),
+ * this returns only 'v'.
+ */
+ void GetUnhandledString(nsAString& aUnhandledString) const;
+ };
+
+ /**
+ * Helper classes for guaranteeing cleaning mCurrentKeyEvent
+ */
+ class AutoKeyEventStateCleaner
+ {
+ public:
+ explicit AutoKeyEventStateCleaner(TextInputHandlerBase* aHandler) :
+ mHandler(aHandler)
+ {
+ }
+
+ ~AutoKeyEventStateCleaner()
+ {
+ mHandler->RemoveCurrentKeyEvent();
+ }
+ private:
+ RefPtr<TextInputHandlerBase> mHandler;
+ };
+
+ class MOZ_STACK_CLASS AutoInsertStringClearer
+ {
+ public:
+ explicit AutoInsertStringClearer(KeyEventState* aState)
+ : mState(aState)
+ {
+ }
+ ~AutoInsertStringClearer();
+
+ private:
+ KeyEventState* mState;
+ };
+
+ /**
+ * mCurrentKeyEvents stores all key events which are being processed.
+ * When we call interpretKeyEvents, IME may generate other key events.
+ * mCurrentKeyEvents[0] is the latest key event.
+ */
+ nsTArray<KeyEventState*> mCurrentKeyEvents;
+
+ /**
+ * mFirstKeyEvent must be used for first key event. This member prevents
+ * memory fragmentation for most key events.
+ */
+ KeyEventState mFirstKeyEvent;
+
+ /**
+ * PushKeyEvent() adds the current key event to mCurrentKeyEvents.
+ */
+ KeyEventState* PushKeyEvent(NSEvent* aNativeKeyEvent)
+ {
+ uint32_t nestCount = mCurrentKeyEvents.Length();
+ for (uint32_t i = 0; i < nestCount; i++) {
+ // When the key event is caused by another key event, all key events
+ // which are being handled should be marked as "consumed".
+ mCurrentKeyEvents[i]->mCausedOtherKeyEvents = true;
+ }
+
+ KeyEventState* keyEvent = nullptr;
+ if (nestCount == 0) {
+ mFirstKeyEvent.Set(aNativeKeyEvent);
+ keyEvent = &mFirstKeyEvent;
+ } else {
+ keyEvent = new KeyEventState(aNativeKeyEvent);
+ }
+ return *mCurrentKeyEvents.AppendElement(keyEvent);
+ }
+
+ /**
+ * RemoveCurrentKeyEvent() removes the current key event from
+ * mCurrentKeyEvents.
+ */
+ void RemoveCurrentKeyEvent()
+ {
+ NS_ASSERTION(mCurrentKeyEvents.Length() > 0,
+ "RemoveCurrentKeyEvent() is called unexpectedly");
+ KeyEventState* keyEvent = GetCurrentKeyEvent();
+ mCurrentKeyEvents.RemoveElementAt(mCurrentKeyEvents.Length() - 1);
+ if (keyEvent == &mFirstKeyEvent) {
+ keyEvent->Clear();
+ } else {
+ delete keyEvent;
+ }
+ }
+
+ /**
+ * GetCurrentKeyEvent() returns current processing key event.
+ */
+ KeyEventState* GetCurrentKeyEvent()
+ {
+ if (mCurrentKeyEvents.Length() == 0) {
+ return nullptr;
+ }
+ return mCurrentKeyEvents[mCurrentKeyEvents.Length() - 1];
+ }
+
+ struct KeyboardLayoutOverride final
+ {
+ int32_t mKeyboardLayout;
+ bool mOverrideEnabled;
+
+ KeyboardLayoutOverride() :
+ mKeyboardLayout(0), mOverrideEnabled(false)
+ {
+ }
+ };
+
+ const KeyboardLayoutOverride& KeyboardLayoutOverrideRef() const
+ {
+ return mKeyboardOverride;
+ }
+
+ /**
+ * IsPrintableChar() checks whether the unicode character is
+ * a non-printable ASCII character or not. Note that this returns
+ * TRUE even if aChar is a non-printable UNICODE character.
+ *
+ * @param aChar A unicode character.
+ * @return TRUE if aChar is a printable ASCII character
+ * or a unicode character. Otherwise, i.e,
+ * if aChar is a non-printable ASCII character,
+ * FALSE.
+ */
+ static bool IsPrintableChar(char16_t aChar);
+
+ /**
+ * IsNormalCharInputtingEvent() checks whether aKeyEvent causes text input.
+ *
+ * @param aKeyEvent A key event.
+ * @return TRUE if the key event causes text input.
+ * Otherwise, FALSE.
+ */
+ static bool IsNormalCharInputtingEvent(const WidgetKeyboardEvent& aKeyEvent);
+
+ /**
+ * IsModifierKey() checks whether the native keyCode is for a modifier key.
+ *
+ * @param aNativeKeyCode A native keyCode.
+ * @return TRUE if aNativeKeyCode is for a modifier key.
+ * Otherwise, FALSE.
+ */
+ static bool IsModifierKey(UInt32 aNativeKeyCode);
+
+private:
+ KeyboardLayoutOverride mKeyboardOverride;
+
+ static int32_t sSecureEventInputCount;
+};
+
+/**
+ * IMEInputHandler manages:
+ * 1. The IME/keyboard layout statement of nsChildView.
+ * 2. The IME composition statement of nsChildView.
+ * And also provides the methods which controls the current IME transaction of
+ * the instance.
+ *
+ * Note that an nsChildView handles one or more NSView's events. E.g., even if
+ * a text editor on XUL panel element, the input events handled on the parent
+ * (or its ancestor) widget handles it (the native focus is set to it). The
+ * actual focused view is notified by OnFocusChangeInGecko.
+ */
+
+class IMEInputHandler : public TextInputHandlerBase
+{
+public:
+ // TextEventDispatcherListener methods
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(void) OnRemovedFrom(
+ TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void) WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData) override;
+
+public:
+ virtual bool OnDestroyWidget(nsChildView* aDestroyingWidget) override;
+
+ virtual void OnFocusChangeInGecko(bool aFocus);
+
+ void OnSelectionChange(const IMENotification& aIMENotification);
+
+ /**
+ * Call [NSTextInputContext handleEvent] for mouse event support of IME
+ */
+ bool OnHandleEvent(NSEvent* aEvent);
+
+ /**
+ * SetMarkedText() is a handler of setMarkedText of NSTextInput.
+ *
+ * @param aAttrString This mut be an instance of NSAttributedString.
+ * If the aString parameter to
+ * [ChildView setMarkedText:setSelectedRange:]
+ * isn't an instance of NSAttributedString,
+ * create an NSAttributedString from it and pass
+ * that instead.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current marked range.
+ */
+ void SetMarkedText(NSAttributedString* aAttrString,
+ NSRange& aSelectedRange,
+ NSRange* aReplacementRange = nullptr);
+
+ /**
+ * GetAttributedSubstringFromRange() returns an NSAttributedString instance
+ * which is allocated as autorelease for aRange.
+ *
+ * @param aRange The range of string which you want.
+ * @param aActualRange The actual range of the result.
+ * @return The string in aRange. If the string is empty,
+ * this returns nil. If succeeded, this returns
+ * an instance which is allocated as autorelease.
+ * If this has some troubles, returns nil.
+ */
+ NSAttributedString* GetAttributedSubstringFromRange(
+ NSRange& aRange,
+ NSRange* aActualRange = nullptr);
+
+ /**
+ * SelectedRange() returns current selected range.
+ *
+ * @return If an editor has focus, this returns selection
+ * range in the editor. Otherwise, this returns
+ * selection range in the focused document.
+ */
+ NSRange SelectedRange();
+
+ /**
+ * DrawsVerticallyForCharacterAtIndex() returns whether the character at
+ * the given index is being rendered vertically.
+ *
+ * @param aCharIndex The character offset to query.
+ *
+ * @return True if writing-mode is vertical at the given
+ * character offset; otherwise false.
+ */
+ bool DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex);
+
+ /**
+ * FirstRectForCharacterRange() returns first *character* rect in the range.
+ * Cocoa needs the first line rect in the range, but we cannot compute it
+ * on current implementation.
+ *
+ * @param aRange A range of text to examine. Its position is
+ * an offset from the beginning of the focused
+ * editor or document.
+ * @param aActualRange If this is not null, this returns the actual
+ * range used for computing the result.
+ * @return An NSRect containing the first character in
+ * aRange, in screen coordinates.
+ * If the length of aRange is 0, the width will
+ * be 0.
+ */
+ NSRect FirstRectForCharacterRange(NSRange& aRange,
+ NSRange* aActualRange = nullptr);
+
+ /**
+ * CharacterIndexForPoint() returns an offset of a character at aPoint.
+ * XXX This isn't implemented, always returns 0.
+ *
+ * @param The point in screen coordinates.
+ * @return The offset of the character at aPoint from
+ * the beginning of the focused editor or
+ * document.
+ */
+ NSUInteger CharacterIndexForPoint(NSPoint& aPoint);
+
+ /**
+ * GetValidAttributesForMarkedText() returns attributes which we support.
+ *
+ * @return Always empty array for now.
+ */
+ NSArray* GetValidAttributesForMarkedText();
+
+ bool HasMarkedText();
+ NSRange MarkedRange();
+
+ bool IsIMEComposing() { return mIsIMEComposing; }
+ bool IsIMEOpened();
+ bool IsIMEEnabled() { return mIsIMEEnabled; }
+ bool IsASCIICapableOnly() { return mIsASCIICapableOnly; }
+ bool IgnoreIMECommit() { return mIgnoreIMECommit; }
+
+ bool IgnoreIMEComposition()
+ {
+ // Ignore the IME composition events when we're pending to discard the
+ // composition and we are not to handle the IME composition now.
+ return (mPendingMethods & kDiscardIMEComposition) &&
+ (mIsInFocusProcessing || !IsFocused());
+ }
+
+ void CommitIMEComposition();
+ void CancelIMEComposition();
+
+ void EnableIME(bool aEnableIME);
+ void SetIMEOpenState(bool aOpen);
+ void SetASCIICapableOnly(bool aASCIICapableOnly);
+
+ /**
+ * True if OSX believes that our view has keyboard focus.
+ */
+ bool IsFocused();
+
+ static CFArrayRef CreateAllIMEModeList();
+ static void DebugPrintAllIMEModes();
+
+ // Don't use ::TSMGetActiveDocument() API directly, the document may not
+ // be what you want.
+ static TSMDocumentID GetCurrentTSMDocumentID();
+
+protected:
+ // We cannot do some jobs in the given stack by some reasons.
+ // Following flags and the timer provide the execution pending mechanism,
+ // See the comment in nsCocoaTextInputHandler.mm.
+ nsCOMPtr<nsITimer> mTimer;
+ enum {
+ kNotifyIMEOfFocusChangeInGecko = 1,
+ kDiscardIMEComposition = 2,
+ kSyncASCIICapableOnly = 4
+ };
+ uint32_t mPendingMethods;
+
+ IMEInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView);
+ virtual ~IMEInputHandler();
+
+ void ResetTimer();
+
+ virtual void ExecutePendingMethods();
+
+ /**
+ * InsertTextAsCommittingComposition() commits current composition. If there
+ * is no composition, this starts a composition and commits it immediately.
+ *
+ * @param aAttrString A string which is committed.
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current selection.
+ */
+ void InsertTextAsCommittingComposition(NSAttributedString* aAttrString,
+ NSRange* aReplacementRange);
+
+private:
+ // If mIsIMEComposing is true, the composition string is stored here.
+ NSString* mIMECompositionString;
+ // If mIsIMEComposing is true, the start offset of the composition string.
+ uint32_t mIMECompositionStart;
+
+ NSRange mMarkedRange;
+ NSRange mSelectedRange;
+
+ NSRange mRangeForWritingMode; // range within which mWritingMode applies
+ mozilla::WritingMode mWritingMode;
+
+ bool mIsIMEComposing;
+ bool mIsIMEEnabled;
+ bool mIsASCIICapableOnly;
+ bool mIgnoreIMECommit;
+ // This flag is enabled by OnFocusChangeInGecko, and will be cleared by
+ // ExecutePendingMethods. When this is true, IsFocus() returns TRUE. At
+ // that time, the focus processing in Gecko might not be finished yet. So,
+ // you cannot use WidgetQueryContentEvent or something.
+ bool mIsInFocusProcessing;
+ bool mIMEHasFocus;
+
+ void KillIMEComposition();
+ void SendCommittedText(NSString *aString);
+ void OpenSystemPreferredLanguageIME();
+
+ // Pending methods
+ void NotifyIMEOfFocusChangeInGecko();
+ void DiscardIMEComposition();
+ void SyncASCIICapableOnly();
+
+ static bool sStaticMembersInitialized;
+ static CFStringRef sLatestIMEOpenedModeInputSourceID;
+ static void InitStaticMembers();
+ static void OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
+ void* aObserver,
+ CFStringRef aName,
+ const void* aObject,
+ CFDictionaryRef aUserInfo);
+
+ static void FlushPendingMethods(nsITimer* aTimer, void* aClosure);
+
+ /**
+ * ConvertToTextRangeStyle converts the given native underline style to
+ * our defined text range type.
+ *
+ * @param aUnderlineStyle NSUnderlineStyleSingle or
+ * NSUnderlineStyleThick.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @return NS_TEXTRANGE_*.
+ */
+ TextRangeType ConvertToTextRangeType(uint32_t aUnderlineStyle,
+ NSRange& aSelectedRange);
+
+ /**
+ * GetRangeCount() computes the range count of aAttrString.
+ *
+ * @param aAttrString An NSAttributedString instance whose number of
+ * NSUnderlineStyleAttributeName ranges you with
+ * to know.
+ * @return The count of NSUnderlineStyleAttributeName
+ * ranges in aAttrString.
+ */
+ uint32_t GetRangeCount(NSAttributedString *aString);
+
+ /**
+ * CreateTextRangeArray() returns text ranges for clauses and/or caret.
+ *
+ * @param aAttrString An NSAttributedString instance which indicates
+ * current composition string.
+ * @param aSelectedRange Current selected range (or caret position).
+ * @return The result is set to the
+ * NSUnderlineStyleAttributeName ranges in
+ * aAttrString.
+ */
+ already_AddRefed<mozilla::TextRangeArray>
+ CreateTextRangeArray(NSAttributedString *aAttrString,
+ NSRange& aSelectedRange);
+
+ /**
+ * DispatchCompositionStartEvent() dispatches a compositionstart event and
+ * initializes the members indicating composition state.
+ *
+ * @return true if it can continues handling composition.
+ * Otherwise, e.g., canceled by the web page,
+ * this returns false.
+ */
+ bool DispatchCompositionStartEvent();
+
+ /**
+ * DispatchCompositionChangeEvent() dispatches a compositionchange event on
+ * mWidget and modifies the members indicating composition state.
+ *
+ * @param aText User text input.
+ * @param aAttrString An NSAttributedString instance which indicates
+ * current composition string.
+ * @param aSelectedRange Current selected range (or caret position).
+ *
+ * @return true if it can continues handling composition.
+ * Otherwise, e.g., canceled by the web page,
+ * this returns false.
+ */
+ bool DispatchCompositionChangeEvent(const nsString& aText,
+ NSAttributedString* aAttrString,
+ NSRange& aSelectedRange);
+
+ /**
+ * DispatchCompositionCommitEvent() dispatches a compositioncommit event or
+ * compositioncommitasis event. If aCommitString is null, dispatches
+ * compositioncommitasis event. I.e., if aCommitString is null, this
+ * commits the composition with the last data. Otherwise, commits the
+ * composition with aCommitString value.
+ *
+ * @return true if the widget isn't destroyed.
+ * Otherwise, false.
+ */
+ bool DispatchCompositionCommitEvent(const nsAString* aCommitString = nullptr);
+
+ // The focused IME handler. Please note that the handler might lost the
+ // actual focus by deactivating the application. If we are active, this
+ // must have the actual focused handle.
+ // We cannot access to the NSInputManager during we aren't active, so, the
+ // focused handler can have an IME transaction even if we are deactive.
+ static IMEInputHandler* sFocusedIMEHandler;
+
+ static bool sCachedIsForRTLLangage;
+};
+
+/**
+ * TextInputHandler implements the NSTextInput protocol.
+ */
+class TextInputHandler : public IMEInputHandler
+{
+public:
+ static NSUInteger sLastModifierState;
+
+ static CFArrayRef CreateAllKeyboardLayoutList();
+ static void DebugPrintAllKeyboardLayouts();
+
+ TextInputHandler(nsChildView* aWidget, NSView<mozView> *aNativeView);
+ virtual ~TextInputHandler();
+
+ /**
+ * KeyDown event handler.
+ *
+ * @param aNativeEvent A native NSKeyDown event.
+ * @return TRUE if the event is consumed by web contents
+ * or chrome contents. Otherwise, FALSE.
+ */
+ bool HandleKeyDownEvent(NSEvent* aNativeEvent);
+
+ /**
+ * KeyUp event handler.
+ *
+ * @param aNativeEvent A native NSKeyUp event.
+ */
+ void HandleKeyUpEvent(NSEvent* aNativeEvent);
+
+ /**
+ * FlagsChanged event handler.
+ *
+ * @param aNativeEvent A native NSFlagsChanged event.
+ */
+ void HandleFlagsChanged(NSEvent* aNativeEvent);
+
+ /**
+ * Insert the string to content. I.e., this is a text input event handler.
+ * If this is called during keydown event handling, this may dispatch a
+ * eKeyPress event. If this is called during composition, this commits
+ * the composition by the aAttrString.
+ *
+ * @param aAttrString An inserted string.
+ * @param aReplacementRange The range which will be replaced with the
+ * aAttrString instead of current selection.
+ */
+ void InsertText(NSAttributedString *aAttrString,
+ NSRange* aReplacementRange = nullptr);
+
+ /**
+ * doCommandBySelector event handler.
+ *
+ * @param aSelector A selector of the command.
+ * @return TRUE if the command is consumed. Otherwise,
+ * FALSE.
+ */
+ bool DoCommandBySelector(const char* aSelector);
+
+ /**
+ * KeyPressWasHandled() checks whether keypress event was handled or not.
+ *
+ * @return TRUE if keypress event for latest native key
+ * event was handled. Otherwise, FALSE.
+ * If this handler isn't handling any key events,
+ * always returns FALSE.
+ */
+ bool KeyPressWasHandled()
+ {
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+ return currentKeyEvent && currentKeyEvent->mKeyPressHandled;
+ }
+
+protected:
+ // Stores the association of device dependent modifier flags with a modifier
+ // keyCode. Being device dependent, this association may differ from one kind
+ // of hardware to the next.
+ struct ModifierKey
+ {
+ NSUInteger flags;
+ unsigned short keyCode;
+
+ ModifierKey(NSUInteger aFlags, unsigned short aKeyCode) :
+ flags(aFlags), keyCode(aKeyCode)
+ {
+ }
+
+ NSUInteger GetDeviceDependentFlags() const
+ {
+ return (flags & ~NSDeviceIndependentModifierFlagsMask);
+ }
+
+ NSUInteger GetDeviceIndependentFlags() const
+ {
+ return (flags & NSDeviceIndependentModifierFlagsMask);
+ }
+ };
+ typedef nsTArray<ModifierKey> ModifierKeyArray;
+ ModifierKeyArray mModifierKeys;
+
+ /**
+ * GetModifierKeyForNativeKeyCode() returns the stored ModifierKey for
+ * the key.
+ */
+ const ModifierKey*
+ GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const;
+
+ /**
+ * GetModifierKeyForDeviceDependentFlags() returns the stored ModifierKey for
+ * the device dependent flags.
+ */
+ const ModifierKey*
+ GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const;
+
+ /**
+ * DispatchKeyEventForFlagsChanged() dispatches keydown event or keyup event
+ * for the aNativeEvent.
+ *
+ * @param aNativeEvent A native flagschanged event which you want to
+ * dispatch our key event for.
+ * @param aDispatchKeyDown TRUE if you want to dispatch a keydown event.
+ * Otherwise, i.e., to dispatch keyup event,
+ * FALSE.
+ */
+ void DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
+ bool aDispatchKeyDown);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // TextInputHandler_h_
diff --git a/widget/cocoa/TextInputHandler.mm b/widget/cocoa/TextInputHandler.mm
new file mode 100644
index 0000000000..348d99ab69
--- /dev/null
+++ b/widget/cocoa/TextInputHandler.mm
@@ -0,0 +1,4533 @@
+/* -*- Mode: C++; tab-width: 2; 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 "TextInputHandler.h"
+
+#include "mozilla/Logging.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+
+#include "nsChildView.h"
+#include "nsObjCExceptions.h"
+#include "nsBidiUtils.h"
+#include "nsToolkit.h"
+#include "nsCocoaUtils.h"
+#include "WidgetUtils.h"
+#include "nsPrintfCString.h"
+#include "ComplexTextInputPanel.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+LazyLogModule gLog("TextInputHandlerWidgets");
+
+static const char*
+OnOrOff(bool aBool)
+{
+ return aBool ? "ON" : "off";
+}
+
+static const char*
+TrueOrFalse(bool aBool)
+{
+ return aBool ? "TRUE" : "FALSE";
+}
+
+static const char*
+GetKeyNameForNativeKeyCode(unsigned short aNativeKeyCode)
+{
+ switch (aNativeKeyCode) {
+ case kVK_Escape: return "Escape";
+ case kVK_RightCommand: return "Right-Command";
+ case kVK_Command: return "Command";
+ case kVK_Shift: return "Shift";
+ case kVK_CapsLock: return "CapsLock";
+ case kVK_Option: return "Option";
+ case kVK_Control: return "Control";
+ case kVK_RightShift: return "Right-Shift";
+ case kVK_RightOption: return "Right-Option";
+ case kVK_RightControl: return "Right-Control";
+ case kVK_ANSI_KeypadClear: return "Clear";
+
+ case kVK_F1: return "F1";
+ case kVK_F2: return "F2";
+ case kVK_F3: return "F3";
+ case kVK_F4: return "F4";
+ case kVK_F5: return "F5";
+ case kVK_F6: return "F6";
+ case kVK_F7: return "F7";
+ case kVK_F8: return "F8";
+ case kVK_F9: return "F9";
+ case kVK_F10: return "F10";
+ case kVK_F11: return "F11";
+ case kVK_F12: return "F12";
+ case kVK_F13: return "F13/PrintScreen";
+ case kVK_F14: return "F14/ScrollLock";
+ case kVK_F15: return "F15/Pause";
+
+ case kVK_ANSI_Keypad0: return "NumPad-0";
+ case kVK_ANSI_Keypad1: return "NumPad-1";
+ case kVK_ANSI_Keypad2: return "NumPad-2";
+ case kVK_ANSI_Keypad3: return "NumPad-3";
+ case kVK_ANSI_Keypad4: return "NumPad-4";
+ case kVK_ANSI_Keypad5: return "NumPad-5";
+ case kVK_ANSI_Keypad6: return "NumPad-6";
+ case kVK_ANSI_Keypad7: return "NumPad-7";
+ case kVK_ANSI_Keypad8: return "NumPad-8";
+ case kVK_ANSI_Keypad9: return "NumPad-9";
+
+ case kVK_ANSI_KeypadMultiply: return "NumPad-*";
+ case kVK_ANSI_KeypadPlus: return "NumPad-+";
+ case kVK_ANSI_KeypadMinus: return "NumPad--";
+ case kVK_ANSI_KeypadDecimal: return "NumPad-.";
+ case kVK_ANSI_KeypadDivide: return "NumPad-/";
+ case kVK_ANSI_KeypadEquals: return "NumPad-=";
+ case kVK_ANSI_KeypadEnter: return "NumPad-Enter";
+ case kVK_Return: return "Return";
+ case kVK_Powerbook_KeypadEnter: return "NumPad-EnterOnPowerBook";
+
+ case kVK_PC_Insert: return "Insert/Help";
+ case kVK_PC_Delete: return "Delete";
+ case kVK_Tab: return "Tab";
+ case kVK_PC_Backspace: return "Backspace";
+ case kVK_Home: return "Home";
+ case kVK_End: return "End";
+ case kVK_PageUp: return "PageUp";
+ case kVK_PageDown: return "PageDown";
+ case kVK_LeftArrow: return "LeftArrow";
+ case kVK_RightArrow: return "RightArrow";
+ case kVK_UpArrow: return "UpArrow";
+ case kVK_DownArrow: return "DownArrow";
+ case kVK_PC_ContextMenu: return "ContextMenu";
+
+ case kVK_Function: return "Function";
+ case kVK_VolumeUp: return "VolumeUp";
+ case kVK_VolumeDown: return "VolumeDown";
+ case kVK_Mute: return "Mute";
+
+ case kVK_ISO_Section: return "ISO_Section";
+
+ case kVK_JIS_Yen: return "JIS_Yen";
+ case kVK_JIS_Underscore: return "JIS_Underscore";
+ case kVK_JIS_KeypadComma: return "JIS_KeypadComma";
+ case kVK_JIS_Eisu: return "JIS_Eisu";
+ case kVK_JIS_Kana: return "JIS_Kana";
+
+ case kVK_ANSI_A: return "A";
+ case kVK_ANSI_B: return "B";
+ case kVK_ANSI_C: return "C";
+ case kVK_ANSI_D: return "D";
+ case kVK_ANSI_E: return "E";
+ case kVK_ANSI_F: return "F";
+ case kVK_ANSI_G: return "G";
+ case kVK_ANSI_H: return "H";
+ case kVK_ANSI_I: return "I";
+ case kVK_ANSI_J: return "J";
+ case kVK_ANSI_K: return "K";
+ case kVK_ANSI_L: return "L";
+ case kVK_ANSI_M: return "M";
+ case kVK_ANSI_N: return "N";
+ case kVK_ANSI_O: return "O";
+ case kVK_ANSI_P: return "P";
+ case kVK_ANSI_Q: return "Q";
+ case kVK_ANSI_R: return "R";
+ case kVK_ANSI_S: return "S";
+ case kVK_ANSI_T: return "T";
+ case kVK_ANSI_U: return "U";
+ case kVK_ANSI_V: return "V";
+ case kVK_ANSI_W: return "W";
+ case kVK_ANSI_X: return "X";
+ case kVK_ANSI_Y: return "Y";
+ case kVK_ANSI_Z: return "Z";
+
+ case kVK_ANSI_1: return "1";
+ case kVK_ANSI_2: return "2";
+ case kVK_ANSI_3: return "3";
+ case kVK_ANSI_4: return "4";
+ case kVK_ANSI_5: return "5";
+ case kVK_ANSI_6: return "6";
+ case kVK_ANSI_7: return "7";
+ case kVK_ANSI_8: return "8";
+ case kVK_ANSI_9: return "9";
+ case kVK_ANSI_0: return "0";
+ case kVK_ANSI_Equal: return "Equal";
+ case kVK_ANSI_Minus: return "Minus";
+ case kVK_ANSI_RightBracket: return "RightBracket";
+ case kVK_ANSI_LeftBracket: return "LeftBracket";
+ case kVK_ANSI_Quote: return "Quote";
+ case kVK_ANSI_Semicolon: return "Semicolon";
+ case kVK_ANSI_Backslash: return "Backslash";
+ case kVK_ANSI_Comma: return "Comma";
+ case kVK_ANSI_Slash: return "Slash";
+ case kVK_ANSI_Period: return "Period";
+ case kVK_ANSI_Grave: return "Grave";
+
+ default: return "undefined";
+ }
+}
+
+static const char*
+GetCharacters(const NSString* aString)
+{
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString(aString, str);
+ if (str.IsEmpty()) {
+ return "";
+ }
+
+ nsAutoString escapedStr;
+ for (uint32_t i = 0; i < str.Length(); i++) {
+ char16_t ch = str[i];
+ if (ch < 0x20) {
+ nsPrintfCString utf8str("(U+%04X)", ch);
+ escapedStr += NS_ConvertUTF8toUTF16(utf8str);
+ } else if (ch <= 0x7E) {
+ escapedStr += ch;
+ } else {
+ nsPrintfCString utf8str("(U+%04X)", ch);
+ escapedStr += ch;
+ escapedStr += NS_ConvertUTF8toUTF16(utf8str);
+ }
+ }
+
+ // the result will be freed automatically by cocoa.
+ NSString* result = nsCocoaUtils::ToNSString(escapedStr);
+ return [result UTF8String];
+}
+
+static const char*
+GetCharacters(const CFStringRef aString)
+{
+ const NSString* str = reinterpret_cast<const NSString*>(aString);
+ return GetCharacters(str);
+}
+
+static const char*
+GetNativeKeyEventType(NSEvent* aNativeEvent)
+{
+ switch ([aNativeEvent type]) {
+ case NSKeyDown: return "NSKeyDown";
+ case NSKeyUp: return "NSKeyUp";
+ case NSFlagsChanged: return "NSFlagsChanged";
+ default: return "not key event";
+ }
+}
+
+static const char*
+GetGeckoKeyEventType(const WidgetEvent& aEvent)
+{
+ switch (aEvent.mMessage) {
+ case eKeyDown: return "eKeyDown";
+ case eKeyUp: return "eKeyUp";
+ case eKeyPress: return "eKeyPress";
+ default: return "not key event";
+ }
+}
+
+static const char*
+GetWindowLevelName(NSInteger aWindowLevel)
+{
+ switch (aWindowLevel) {
+ case kCGBaseWindowLevelKey:
+ return "kCGBaseWindowLevelKey (NSNormalWindowLevel)";
+ case kCGMinimumWindowLevelKey:
+ return "kCGMinimumWindowLevelKey";
+ case kCGDesktopWindowLevelKey:
+ return "kCGDesktopWindowLevelKey";
+ case kCGBackstopMenuLevelKey:
+ return "kCGBackstopMenuLevelKey";
+ case kCGNormalWindowLevelKey:
+ return "kCGNormalWindowLevelKey";
+ case kCGFloatingWindowLevelKey:
+ return "kCGFloatingWindowLevelKey (NSFloatingWindowLevel)";
+ case kCGTornOffMenuWindowLevelKey:
+ return "kCGTornOffMenuWindowLevelKey (NSSubmenuWindowLevel, NSTornOffMenuWindowLevel)";
+ case kCGDockWindowLevelKey:
+ return "kCGDockWindowLevelKey (NSDockWindowLevel)";
+ case kCGMainMenuWindowLevelKey:
+ return "kCGMainMenuWindowLevelKey (NSMainMenuWindowLevel)";
+ case kCGStatusWindowLevelKey:
+ return "kCGStatusWindowLevelKey (NSStatusWindowLevel)";
+ case kCGModalPanelWindowLevelKey:
+ return "kCGModalPanelWindowLevelKey (NSModalPanelWindowLevel)";
+ case kCGPopUpMenuWindowLevelKey:
+ return "kCGPopUpMenuWindowLevelKey (NSPopUpMenuWindowLevel)";
+ case kCGDraggingWindowLevelKey:
+ return "kCGDraggingWindowLevelKey";
+ case kCGScreenSaverWindowLevelKey:
+ return "kCGScreenSaverWindowLevelKey (NSScreenSaverWindowLevel)";
+ case kCGMaximumWindowLevelKey:
+ return "kCGMaximumWindowLevelKey";
+ case kCGOverlayWindowLevelKey:
+ return "kCGOverlayWindowLevelKey";
+ case kCGHelpWindowLevelKey:
+ return "kCGHelpWindowLevelKey";
+ case kCGUtilityWindowLevelKey:
+ return "kCGUtilityWindowLevelKey";
+ case kCGDesktopIconWindowLevelKey:
+ return "kCGDesktopIconWindowLevelKey";
+ case kCGCursorWindowLevelKey:
+ return "kCGCursorWindowLevelKey";
+ case kCGNumberOfWindowLevelKeys:
+ return "kCGNumberOfWindowLevelKeys";
+ default:
+ return "unknown window level";
+ }
+}
+
+static bool
+IsControlChar(uint32_t aCharCode)
+{
+ return aCharCode < ' ' || aCharCode == 0x7F;
+}
+
+static uint32_t gHandlerInstanceCount = 0;
+
+static void
+EnsureToLogAllKeyboardLayoutsAndIMEs()
+{
+ static bool sDone = false;
+ if (!sDone) {
+ sDone = true;
+ TextInputHandler::DebugPrintAllKeyboardLayouts();
+ IMEInputHandler::DebugPrintAllIMEModes();
+ }
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TISInputSourceWrapper implementation
+ *
+ ******************************************************************************/
+
+TISInputSourceWrapper* TISInputSourceWrapper::sCurrentInputSource = nullptr;
+
+// static
+TISInputSourceWrapper&
+TISInputSourceWrapper::CurrentInputSource()
+{
+ if (!sCurrentInputSource) {
+ sCurrentInputSource = new TISInputSourceWrapper();
+ }
+ if (!sCurrentInputSource->IsInitializedByCurrentInputSource()) {
+ sCurrentInputSource->InitByCurrentInputSource();
+ }
+ return *sCurrentInputSource;
+}
+
+// static
+void
+TISInputSourceWrapper::Shutdown()
+{
+ if (!sCurrentInputSource) {
+ return;
+ }
+ sCurrentInputSource->Clear();
+ delete sCurrentInputSource;
+ sCurrentInputSource = nullptr;
+}
+
+bool
+TISInputSourceWrapper::TranslateToString(UInt32 aKeyCode, UInt32 aModifiers,
+ UInt32 aKbType, nsAString &aStr)
+{
+ aStr.Truncate();
+
+ const UCKeyboardLayout* UCKey = GetUCKeyboardLayout();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, aKeyCode=0x%X, "
+ "aModifiers=0x%X, aKbType=0x%X UCKey=%p\n "
+ "Shift: %s, Ctrl: %s, Opt: %s, Cmd: %s, CapsLock: %s, NumLock: %s",
+ this, aKeyCode, aModifiers, aKbType, UCKey,
+ OnOrOff(aModifiers & shiftKey), OnOrOff(aModifiers & controlKey),
+ OnOrOff(aModifiers & optionKey), OnOrOff(aModifiers & cmdKey),
+ OnOrOff(aModifiers & alphaLock),
+ OnOrOff(aModifiers & kEventKeyModifierNumLockMask)));
+
+ NS_ENSURE_TRUE(UCKey, false);
+
+ UInt32 deadKeyState = 0;
+ UniCharCount len;
+ UniChar chars[5];
+ OSStatus err = ::UCKeyTranslate(UCKey, aKeyCode,
+ kUCKeyActionDown, aModifiers >> 8,
+ aKbType, kUCKeyTranslateNoDeadKeysMask,
+ &deadKeyState, 5, &len, chars);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, err=0x%X, len=%llu",
+ this, err, len));
+
+ NS_ENSURE_TRUE(err == noErr, false);
+ if (len == 0) {
+ return true;
+ }
+ NS_ENSURE_TRUE(EnsureStringLength(aStr, len), false);
+ NS_ASSERTION(sizeof(char16_t) == sizeof(UniChar),
+ "size of char16_t and size of UniChar are different");
+ memcpy(aStr.BeginWriting(), chars, len * sizeof(char16_t));
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::TranslateToString, aStr=\"%s\"",
+ this, NS_ConvertUTF16toUTF8(aStr).get()));
+
+ return true;
+}
+
+uint32_t
+TISInputSourceWrapper::TranslateToChar(UInt32 aKeyCode, UInt32 aModifiers,
+ UInt32 aKbType)
+{
+ nsAutoString str;
+ if (!TranslateToString(aKeyCode, aModifiers, aKbType, str) ||
+ str.Length() != 1) {
+ return 0;
+ }
+ return static_cast<uint32_t>(str.CharAt(0));
+}
+
+void
+TISInputSourceWrapper::InitByInputSourceID(const char* aID)
+{
+ Clear();
+ if (!aID)
+ return;
+
+ CFStringRef idstr = ::CFStringCreateWithCString(kCFAllocatorDefault, aID,
+ kCFStringEncodingASCII);
+ InitByInputSourceID(idstr);
+ ::CFRelease(idstr);
+}
+
+void
+TISInputSourceWrapper::InitByInputSourceID(const nsAFlatString &aID)
+{
+ Clear();
+ if (aID.IsEmpty())
+ return;
+ CFStringRef idstr = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+ reinterpret_cast<const UniChar*>(aID.get()),
+ aID.Length());
+ InitByInputSourceID(idstr);
+ ::CFRelease(idstr);
+}
+
+void
+TISInputSourceWrapper::InitByInputSourceID(const CFStringRef aID)
+{
+ Clear();
+ if (!aID)
+ return;
+ const void* keys[] = { kTISPropertyInputSourceID };
+ const void* values[] = { aID };
+ CFDictionaryRef filter =
+ ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ mInputSourceList = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ if (::CFArrayGetCount(mInputSourceList) > 0) {
+ mInputSource = static_cast<TISInputSourceRef>(
+ const_cast<void *>(::CFArrayGetValueAtIndex(mInputSourceList, 0)));
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+ }
+}
+
+void
+TISInputSourceWrapper::InitByLayoutID(SInt32 aLayoutID,
+ bool aOverrideKeyboard)
+{
+ // NOTE: Doument new layout IDs in TextInputHandler.h when you add ones.
+ switch (aLayoutID) {
+ case 0:
+ InitByInputSourceID("com.apple.keylayout.US");
+ break;
+ case 1:
+ InitByInputSourceID("com.apple.keylayout.Greek");
+ break;
+ case 2:
+ InitByInputSourceID("com.apple.keylayout.German");
+ break;
+ case 3:
+ InitByInputSourceID("com.apple.keylayout.Swedish-Pro");
+ break;
+ case 4:
+ InitByInputSourceID("com.apple.keylayout.DVORAK-QWERTYCMD");
+ break;
+ case 5:
+ InitByInputSourceID("com.apple.keylayout.Thai");
+ break;
+ case 6:
+ InitByInputSourceID("com.apple.keylayout.Arabic");
+ break;
+ case 7:
+ InitByInputSourceID("com.apple.keylayout.ArabicPC");
+ break;
+ case 8:
+ InitByInputSourceID("com.apple.keylayout.French");
+ break;
+ case 9:
+ InitByInputSourceID("com.apple.keylayout.Hebrew");
+ break;
+ case 10:
+ InitByInputSourceID("com.apple.keylayout.Lithuanian");
+ break;
+ case 11:
+ InitByInputSourceID("com.apple.keylayout.Norwegian");
+ break;
+ case 12:
+ InitByInputSourceID("com.apple.keylayout.Spanish");
+ break;
+ default:
+ Clear();
+ break;
+ }
+ mOverrideKeyboard = aOverrideKeyboard;
+}
+
+void
+TISInputSourceWrapper::InitByCurrentInputSource()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentKeyboardInputSource();
+ mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
+ if (!mKeyboardLayout) {
+ mKeyboardLayout = ::TISCopyCurrentKeyboardLayoutInputSource();
+ }
+ // If this causes composition, the current keyboard layout may input non-ASCII
+ // characters such as Japanese Kana characters or Hangul characters.
+ // However, we need to set ASCII characters to DOM key events for consistency
+ // with other platforms.
+ if (IsOpenedIMEMode()) {
+ TISInputSourceWrapper tis(mKeyboardLayout);
+ if (!tis.IsASCIICapable()) {
+ mKeyboardLayout =
+ ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ }
+ }
+}
+
+void
+TISInputSourceWrapper::InitByCurrentKeyboardLayout()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentKeyboardLayoutInputSource();
+ mKeyboardLayout = mInputSource;
+}
+
+void
+TISInputSourceWrapper::InitByCurrentASCIICapableInputSource()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentASCIICapableKeyboardInputSource();
+ mKeyboardLayout = ::TISCopyInputMethodKeyboardLayoutOverride();
+ if (mKeyboardLayout) {
+ TISInputSourceWrapper tis(mKeyboardLayout);
+ if (!tis.IsASCIICapable()) {
+ mKeyboardLayout = nullptr;
+ }
+ }
+ if (!mKeyboardLayout) {
+ mKeyboardLayout =
+ ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ }
+}
+
+void
+TISInputSourceWrapper::InitByCurrentASCIICapableKeyboardLayout()
+{
+ Clear();
+ mInputSource = ::TISCopyCurrentASCIICapableKeyboardLayoutInputSource();
+ mKeyboardLayout = mInputSource;
+}
+
+void
+TISInputSourceWrapper::InitByCurrentInputMethodKeyboardLayoutOverride()
+{
+ Clear();
+ mInputSource = ::TISCopyInputMethodKeyboardLayoutOverride();
+ mKeyboardLayout = mInputSource;
+}
+
+void
+TISInputSourceWrapper::InitByTISInputSourceRef(TISInputSourceRef aInputSource)
+{
+ Clear();
+ mInputSource = aInputSource;
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+}
+
+void
+TISInputSourceWrapper::InitByLanguage(CFStringRef aLanguage)
+{
+ Clear();
+ mInputSource = ::TISCopyInputSourceForLanguage(aLanguage);
+ if (IsKeyboardLayout()) {
+ mKeyboardLayout = mInputSource;
+ }
+}
+
+const UCKeyboardLayout*
+TISInputSourceWrapper::GetUCKeyboardLayout()
+{
+ NS_ENSURE_TRUE(mKeyboardLayout, nullptr);
+ if (mUCKeyboardLayout) {
+ return mUCKeyboardLayout;
+ }
+ CFDataRef uchr = static_cast<CFDataRef>(
+ ::TISGetInputSourceProperty(mKeyboardLayout,
+ kTISPropertyUnicodeKeyLayoutData));
+
+ // We should be always able to get the layout here.
+ NS_ENSURE_TRUE(uchr, nullptr);
+ mUCKeyboardLayout =
+ reinterpret_cast<const UCKeyboardLayout*>(CFDataGetBytePtr(uchr));
+ return mUCKeyboardLayout;
+}
+
+bool
+TISInputSourceWrapper::GetBoolProperty(const CFStringRef aKey)
+{
+ CFBooleanRef ret = static_cast<CFBooleanRef>(
+ ::TISGetInputSourceProperty(mInputSource, aKey));
+ return ::CFBooleanGetValue(ret);
+}
+
+bool
+TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
+ CFStringRef &aStr)
+{
+ aStr = static_cast<CFStringRef>(
+ ::TISGetInputSourceProperty(mInputSource, aKey));
+ return aStr != nullptr;
+}
+
+bool
+TISInputSourceWrapper::GetStringProperty(const CFStringRef aKey,
+ nsAString &aStr)
+{
+ CFStringRef str;
+ GetStringProperty(aKey, str);
+ nsCocoaUtils::GetStringForNSString((const NSString*)str, aStr);
+ return !aStr.IsEmpty();
+}
+
+bool
+TISInputSourceWrapper::IsOpenedIMEMode()
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ if (!IsIMEMode())
+ return false;
+ return !IsASCIICapable();
+}
+
+bool
+TISInputSourceWrapper::IsIMEMode()
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef str;
+ GetInputSourceType(str);
+ NS_ENSURE_TRUE(str, false);
+ return ::CFStringCompare(kTISTypeKeyboardInputMode,
+ str, 0) == kCFCompareEqualTo;
+}
+
+bool
+TISInputSourceWrapper::IsKeyboardLayout()
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef str;
+ GetInputSourceType(str);
+ NS_ENSURE_TRUE(str, false);
+ return ::CFStringCompare(kTISTypeKeyboardLayout,
+ str, 0) == kCFCompareEqualTo;
+}
+
+bool
+TISInputSourceWrapper::GetLanguageList(CFArrayRef &aLanguageList)
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ aLanguageList = static_cast<CFArrayRef>(
+ ::TISGetInputSourceProperty(mInputSource,
+ kTISPropertyInputSourceLanguages));
+ return aLanguageList != nullptr;
+}
+
+bool
+TISInputSourceWrapper::GetPrimaryLanguage(CFStringRef &aPrimaryLanguage)
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFArrayRef langList;
+ NS_ENSURE_TRUE(GetLanguageList(langList), false);
+ if (::CFArrayGetCount(langList) == 0)
+ return false;
+ aPrimaryLanguage =
+ static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, 0));
+ return aPrimaryLanguage != nullptr;
+}
+
+bool
+TISInputSourceWrapper::GetPrimaryLanguage(nsAString &aPrimaryLanguage)
+{
+ NS_ENSURE_TRUE(mInputSource, false);
+ CFStringRef primaryLanguage;
+ NS_ENSURE_TRUE(GetPrimaryLanguage(primaryLanguage), false);
+ nsCocoaUtils::GetStringForNSString((const NSString*)primaryLanguage,
+ aPrimaryLanguage);
+ return !aPrimaryLanguage.IsEmpty();
+}
+
+bool
+TISInputSourceWrapper::IsForRTLLanguage()
+{
+ if (mIsRTL < 0) {
+ // Get the input character of the 'A' key of ANSI keyboard layout.
+ nsAutoString str;
+ bool ret = TranslateToString(kVK_ANSI_A, 0, eKbdType_ANSI, str);
+ NS_ENSURE_TRUE(ret, ret);
+ char16_t ch = str.IsEmpty() ? char16_t(0) : str.CharAt(0);
+ mIsRTL = UCS2_CHAR_IS_BIDI(ch);
+ }
+ return mIsRTL != 0;
+}
+
+bool
+TISInputSourceWrapper::IsInitializedByCurrentInputSource()
+{
+ return mInputSource == ::TISCopyCurrentKeyboardInputSource();
+}
+
+void
+TISInputSourceWrapper::Select()
+{
+ if (!mInputSource)
+ return;
+ ::TISSelectInputSource(mInputSource);
+}
+
+void
+TISInputSourceWrapper::Clear()
+{
+ // Clear() is always called when TISInputSourceWrappper is created.
+ EnsureToLogAllKeyboardLayoutsAndIMEs();
+
+ if (mInputSourceList) {
+ ::CFRelease(mInputSourceList);
+ }
+ mInputSourceList = nullptr;
+ mInputSource = nullptr;
+ mKeyboardLayout = nullptr;
+ mIsRTL = -1;
+ mUCKeyboardLayout = nullptr;
+ mOverrideKeyboard = false;
+}
+
+bool
+TISInputSourceWrapper::IsPrintableKeyEvent(NSEvent* aNativeKeyEvent) const
+{
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+
+ bool isPrintableKey = !TextInputHandler::IsSpecialGeckoKey(nativeKeyCode);
+ if (isPrintableKey &&
+ [aNativeKeyEvent type] != NSKeyDown &&
+ [aNativeKeyEvent type] != NSKeyUp) {
+ NS_WARNING("Why the printable key doesn't cause NSKeyDown or NSKeyUp?");
+ isPrintableKey = false;
+ }
+ return isPrintableKey;
+}
+
+UInt32
+TISInputSourceWrapper::GetKbdType() const
+{
+ // If a keyboard layout override is set, we also need to force the keyboard
+ // type to something ANSI to avoid test failures on machines with JIS
+ // keyboards (since the pair of keyboard layout and physical keyboard type
+ // form the actual key layout). This assumes that the test setting the
+ // override was written assuming an ANSI keyboard.
+ return mOverrideKeyboard ? eKbdType_ANSI : ::LMGetKbdType();
+}
+
+void
+TISInputSourceWrapper::ComputeInsertStringForCharCode(
+ NSEvent* aNativeKeyEvent,
+ const WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString,
+ nsAString& aResult)
+{
+ if (aInsertString) {
+ // If the caller expects that the aInsertString will be input, we shouldn't
+ // change it.
+ aResult = *aInsertString;
+ } else if (IsPrintableKeyEvent(aNativeKeyEvent)) {
+ // If IME is open, [aNativeKeyEvent characters] may be a character
+ // which will be appended to the composition string. However, especially,
+ // while IME is disabled, most users and developers expect the key event
+ // works as IME closed. So, we should compute the aResult with
+ // the ASCII capable keyboard layout.
+ // NOTE: Such keyboard layouts typically change the layout to its ASCII
+ // capable layout when Command key is pressed. And we don't worry
+ // when Control key is pressed too because it causes inputting
+ // control characters.
+ // Additionally, if the key event doesn't input any text, the event may be
+ // dead key event. In this case, the charCode value should be the dead
+ // character.
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+ if ((!aKeyEvent.IsMeta() && !aKeyEvent.IsControl() && IsOpenedIMEMode()) ||
+ ![[aNativeKeyEvent characters] length]) {
+ UInt32 state =
+ nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
+ uint32_t ch = TranslateToChar(nativeKeyCode, state, GetKbdType());
+ if (ch) {
+ aResult = ch;
+ }
+ } else {
+ // If the caller isn't sure what string will be input, let's use
+ // characters of NSEvent.
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], aResult);
+ }
+
+ // If control key is pressed and the eventChars is a non-printable control
+ // character, we should convert it to ASCII alphabet.
+ if (aKeyEvent.IsControl() &&
+ !aResult.IsEmpty() && aResult[0] <= char16_t(26)) {
+ aResult = (aKeyEvent.IsShift() ^ aKeyEvent.IsCapsLocked()) ?
+ static_cast<char16_t>(aResult[0] + ('A' - 1)) :
+ static_cast<char16_t>(aResult[0] + ('a' - 1));
+ }
+ // If Meta key is pressed, it may cause to switch the keyboard layout like
+ // Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
+ else if (aKeyEvent.IsMeta() &&
+ !(aKeyEvent.IsControl() || aKeyEvent.IsAlt())) {
+ UInt32 kbType = GetKbdType();
+ UInt32 numLockState =
+ aKeyEvent.IsNumLocked() ? kEventKeyModifierNumLockMask : 0;
+ UInt32 capsLockState = aKeyEvent.IsCapsLocked() ? alphaLock : 0;
+ UInt32 shiftState = aKeyEvent.IsShift() ? shiftKey : 0;
+ uint32_t uncmdedChar =
+ TranslateToChar(nativeKeyCode, numLockState, kbType);
+ uint32_t cmdedChar =
+ TranslateToChar(nativeKeyCode, cmdKey | numLockState, kbType);
+ // If we can make a good guess at the characters that the user would
+ // expect this key combination to produce (with and without Shift) then
+ // use those characters. This also corrects for CapsLock.
+ uint32_t ch = 0;
+ if (uncmdedChar == cmdedChar) {
+ // The characters produced with Command seem similar to those without
+ // Command.
+ ch = TranslateToChar(nativeKeyCode,
+ shiftState | capsLockState | numLockState, kbType);
+ } else {
+ TISInputSourceWrapper USLayout("com.apple.keylayout.US");
+ uint32_t uncmdedUSChar =
+ USLayout.TranslateToChar(nativeKeyCode, numLockState, kbType);
+ // If it looks like characters from US keyboard layout when Command key
+ // is pressed, we should compute a character in the layout.
+ if (uncmdedUSChar == cmdedChar) {
+ ch = USLayout.TranslateToChar(nativeKeyCode,
+ shiftState | capsLockState | numLockState, kbType);
+ }
+ }
+
+ // If there is a more preferred character for the commanded key event,
+ // we should use it.
+ if (ch) {
+ aResult = ch;
+ }
+ }
+ }
+
+ // Remove control characters which shouldn't be inputted on editor.
+ // XXX Currently, we don't find any cases inserting control characters with
+ // printable character. So, just checking first character is enough.
+ if (!aResult.IsEmpty() && IsControlChar(aResult[0])) {
+ aResult.Truncate();
+ }
+}
+
+void
+TISInputSourceWrapper::InitKeyEvent(NSEvent *aNativeKeyEvent,
+ WidgetKeyboardEvent& aKeyEvent,
+ const nsAString *aInsertString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::InitKeyEvent, aNativeKeyEvent=%p, "
+ "aKeyEvent.mMessage=%s, aInsertString=%p, IsOpenedIMEMode()=%s",
+ this, aNativeKeyEvent, GetGeckoKeyEventType(aKeyEvent), aInsertString,
+ TrueOrFalse(IsOpenedIMEMode())));
+
+ NS_ENSURE_TRUE(aNativeKeyEvent, );
+
+ nsCocoaUtils::InitInputEvent(aKeyEvent, aNativeKeyEvent);
+
+ // This is used only while dispatching the event (which is a synchronous
+ // call), so there is no need to retain and release this data.
+ aKeyEvent.mNativeKeyEvent = aNativeKeyEvent;
+
+ // Fill in fields used for Cocoa NPAPI plugins
+ if ([aNativeKeyEvent type] == NSKeyDown ||
+ [aNativeKeyEvent type] == NSKeyUp) {
+ aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
+ aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
+ nsAutoString nativeChars;
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], nativeChars);
+ aKeyEvent.mNativeCharacters.Assign(nativeChars);
+ nsAutoString nativeCharsIgnoringModifiers;
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent charactersIgnoringModifiers], nativeCharsIgnoringModifiers);
+ aKeyEvent.mNativeCharactersIgnoringModifiers.Assign(nativeCharsIgnoringModifiers);
+ } else if ([aNativeKeyEvent type] == NSFlagsChanged) {
+ aKeyEvent.mNativeKeyCode = [aNativeKeyEvent keyCode];
+ aKeyEvent.mNativeModifierFlags = [aNativeKeyEvent modifierFlags];
+ }
+
+ aKeyEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ aKeyEvent.mIsChar = false; // XXX not used in XP level
+
+ UInt32 kbType = GetKbdType();
+ UInt32 nativeKeyCode = [aNativeKeyEvent keyCode];
+
+ aKeyEvent.mKeyCode =
+ ComputeGeckoKeyCode(nativeKeyCode, kbType, aKeyEvent.IsMeta());
+
+ switch (nativeKeyCode) {
+ case kVK_Command:
+ case kVK_Shift:
+ case kVK_Option:
+ case kVK_Control:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_LEFT;
+ break;
+
+ case kVK_RightCommand:
+ case kVK_RightShift:
+ case kVK_RightOption:
+ case kVK_RightControl:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_RIGHT;
+ break;
+
+ case kVK_ANSI_Keypad0:
+ case kVK_ANSI_Keypad1:
+ case kVK_ANSI_Keypad2:
+ case kVK_ANSI_Keypad3:
+ case kVK_ANSI_Keypad4:
+ case kVK_ANSI_Keypad5:
+ case kVK_ANSI_Keypad6:
+ case kVK_ANSI_Keypad7:
+ case kVK_ANSI_Keypad8:
+ case kVK_ANSI_Keypad9:
+ case kVK_ANSI_KeypadMultiply:
+ case kVK_ANSI_KeypadPlus:
+ case kVK_ANSI_KeypadMinus:
+ case kVK_ANSI_KeypadDecimal:
+ case kVK_ANSI_KeypadDivide:
+ case kVK_ANSI_KeypadEquals:
+ case kVK_ANSI_KeypadEnter:
+ case kVK_JIS_KeypadComma:
+ case kVK_Powerbook_KeypadEnter:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_NUMPAD;
+ break;
+
+ default:
+ aKeyEvent.mLocation = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
+ break;
+ }
+
+ aKeyEvent.mIsRepeat =
+ ([aNativeKeyEvent type] == NSKeyDown) ? [aNativeKeyEvent isARepeat] : false;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::InitKeyEvent, "
+ "shift=%s, ctrl=%s, alt=%s, meta=%s",
+ this, OnOrOff(aKeyEvent.IsShift()), OnOrOff(aKeyEvent.IsControl()),
+ OnOrOff(aKeyEvent.IsAlt()), OnOrOff(aKeyEvent.IsMeta())));
+
+ if (IsPrintableKeyEvent(aNativeKeyEvent)) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ // If insertText calls this method, let's use the string.
+ if (aInsertString && !aInsertString->IsEmpty() &&
+ !IsControlChar((*aInsertString)[0])) {
+ aKeyEvent.mKeyValue = *aInsertString;
+ }
+ // If meta key is pressed, the printable key layout may be switched from
+ // non-ASCII capable layout to ASCII capable, or from Dvorak to QWERTY.
+ // KeyboardEvent.key value should be the switched layout's character.
+ else if (aKeyEvent.IsMeta()) {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
+ aKeyEvent.mKeyValue);
+ }
+ // If control key is pressed, some keys may produce printable character via
+ // [aNativeKeyEvent characters]. Otherwise, translate input character of
+ // the key without control key.
+ else if (aKeyEvent.IsControl()) {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
+ aKeyEvent.mKeyValue);
+ if (aKeyEvent.mKeyValue.IsEmpty() ||
+ IsControlChar(aKeyEvent.mKeyValue[0])) {
+ NSUInteger cocoaState =
+ [aNativeKeyEvent modifierFlags] & ~NSControlKeyMask;
+ UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+ aKeyEvent.mKeyValue =
+ TranslateToChar(nativeKeyCode, carbonState, kbType);
+ }
+ }
+ // Otherwise, KeyboardEvent.key expose
+ // [aNativeKeyEvent characters] value. However, if IME is open and the
+ // keyboard layout isn't ASCII capable, exposing the non-ASCII character
+ // doesn't match with other platform's behavior. For the compatibility
+ // with other platform's Gecko, we need to set a translated character.
+ else if (IsOpenedIMEMode()) {
+ UInt32 state =
+ nsCocoaUtils::ConvertToCarbonModifier([aNativeKeyEvent modifierFlags]);
+ aKeyEvent.mKeyValue = TranslateToChar(nativeKeyCode, state, kbType);
+ } else {
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters],
+ aKeyEvent.mKeyValue);
+ // If the key value is empty, the event may be a dead key event.
+ // If TranslateToChar() returns non-zero value, that means that
+ // the key may input a character with different dead key state.
+ if (aKeyEvent.mKeyValue.IsEmpty()) {
+ NSUInteger cocoaState = [aNativeKeyEvent modifierFlags];
+ UInt32 carbonState = nsCocoaUtils::ConvertToCarbonModifier(cocoaState);
+ if (TranslateToChar(nativeKeyCode, carbonState, kbType)) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_Dead;
+ }
+ }
+ }
+
+ // Last resort. If .key value becomes empty string, we should use
+ // charactersIgnoringModifiers, if it's available.
+ if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING &&
+ (aKeyEvent.mKeyValue.IsEmpty() ||
+ IsControlChar(aKeyEvent.mKeyValue[0]))) {
+ nsCocoaUtils::GetStringForNSString(
+ [aNativeKeyEvent charactersIgnoringModifiers], aKeyEvent.mKeyValue);
+ // But don't expose it if it's a control character.
+ if (!aKeyEvent.mKeyValue.IsEmpty() &&
+ IsControlChar(aKeyEvent.mKeyValue[0])) {
+ aKeyEvent.mKeyValue.Truncate();
+ }
+ }
+ } else {
+ // Compute the key for non-printable keys and some special printable keys.
+ aKeyEvent.mKeyNameIndex = ComputeGeckoKeyNameIndex(nativeKeyCode);
+ }
+
+ aKeyEvent.mCodeNameIndex = ComputeGeckoCodeNameIndex(nativeKeyCode);
+ MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void
+TISInputSourceWrapper::WillDispatchKeyboardEvent(
+ NSEvent* aNativeKeyEvent,
+ const nsAString* aInsertString,
+ WidgetKeyboardEvent& aKeyEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Nothing to do here if the native key event is neither NSKeyDown nor
+ // NSKeyUp because accessing [aNativeKeyEvent characters] causes throwing
+ // an exception.
+ if ([aNativeKeyEvent type] != NSKeyDown &&
+ [aNativeKeyEvent type] != NSKeyUp) {
+ return;
+ }
+
+ UInt32 kbType = GetKbdType();
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ nsAutoString chars;
+ nsCocoaUtils::GetStringForNSString([aNativeKeyEvent characters], chars);
+ NS_ConvertUTF16toUTF8 utf8Chars(chars);
+ char16_t uniChar = static_cast<char16_t>(aKeyEvent.mCharCode);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aNativeKeyEvent=%p, [aNativeKeyEvent characters]=\"%s\", "
+ "aKeyEvent={ mMessage=%s, mCharCode=0x%X(%s) }, kbType=0x%X, "
+ "IsOpenedIMEMode()=%s",
+ this, aNativeKeyEvent, utf8Chars.get(),
+ GetGeckoKeyEventType(aKeyEvent), aKeyEvent.mCharCode,
+ uniChar ? NS_ConvertUTF16toUTF8(&uniChar, 1).get() : "",
+ kbType, TrueOrFalse(IsOpenedIMEMode())));
+ }
+
+ nsAutoString insertStringForCharCode;
+ ComputeInsertStringForCharCode(aNativeKeyEvent, aKeyEvent, aInsertString,
+ insertStringForCharCode);
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ uint32_t charCode =
+ insertStringForCharCode.IsEmpty() ? 0 : insertStringForCharCode[0];
+ aKeyEvent.SetCharCode(charCode);
+ // this is not a special key XXX not used in XP
+ aKeyEvent.mIsChar = (aKeyEvent.mMessage == eKeyPress);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aKeyEvent.mKeyCode=0x%X, aKeyEvent.mCharCode=0x%X",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
+
+ TISInputSourceWrapper USLayout("com.apple.keylayout.US");
+ bool isRomanKeyboardLayout = IsASCIICapable();
+
+ UInt32 key = [aNativeKeyEvent keyCode];
+
+ // Caps lock and num lock modifier state:
+ UInt32 lockState = 0;
+ if ([aNativeKeyEvent modifierFlags] & NSAlphaShiftKeyMask) {
+ lockState |= alphaLock;
+ }
+ if ([aNativeKeyEvent modifierFlags] & NSNumericPadKeyMask) {
+ lockState |= kEventKeyModifierNumLockMask;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "isRomanKeyboardLayout=%s, key=0x%X",
+ this, TrueOrFalse(isRomanKeyboardLayout), kbType, key));
+
+ nsString str;
+
+ // normal chars
+ uint32_t unshiftedChar = TranslateToChar(key, lockState, kbType);
+ UInt32 shiftLockMod = shiftKey | lockState;
+ uint32_t shiftedChar = TranslateToChar(key, shiftLockMod, kbType);
+
+ // characters generated with Cmd key
+ // XXX we should remove CapsLock state, which changes characters from
+ // Latin to Cyrillic with Russian layout on 10.4 only when Cmd key
+ // is pressed.
+ UInt32 numState = (lockState & ~alphaLock); // only num lock state
+ uint32_t uncmdedChar = TranslateToChar(key, numState, kbType);
+ UInt32 shiftNumMod = numState | shiftKey;
+ uint32_t uncmdedShiftChar = TranslateToChar(key, shiftNumMod, kbType);
+ uint32_t uncmdedUSChar = USLayout.TranslateToChar(key, numState, kbType);
+ UInt32 cmdNumMod = cmdKey | numState;
+ uint32_t cmdedChar = TranslateToChar(key, cmdNumMod, kbType);
+ UInt32 cmdShiftNumMod = shiftKey | cmdNumMod;
+ uint32_t cmdedShiftChar = TranslateToChar(key, cmdShiftNumMod, kbType);
+
+ // Is the keyboard layout changed by Cmd key?
+ // E.g., Arabic, Russian, Hebrew, Greek and Dvorak-QWERTY.
+ bool isCmdSwitchLayout = uncmdedChar != cmdedChar;
+ // Is the keyboard layout for Latin, but Cmd key switches the layout?
+ // I.e., Dvorak-QWERTY
+ bool isDvorakQWERTY = isCmdSwitchLayout && isRomanKeyboardLayout;
+
+ // If the current keyboard is not Dvorak-QWERTY or Cmd is not pressed,
+ // we should append unshiftedChar and shiftedChar for handling the
+ // normal characters. These are the characters that the user is most
+ // likely to associate with this key.
+ if ((unshiftedChar || shiftedChar) &&
+ (!aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
+ AlternativeCharCode altCharCodes(unshiftedChar, shiftedChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "aKeyEvent.isMeta=%s, isDvorakQWERTY=%s, "
+ "unshiftedChar=U+%X, shiftedChar=U+%X",
+ this, OnOrOff(aKeyEvent.IsMeta()), TrueOrFalse(isDvorakQWERTY),
+ unshiftedChar, shiftedChar));
+
+ // Most keyboard layouts provide the same characters in the NSEvents
+ // with Command+Shift as with Command. However, with Command+Shift we
+ // want the character on the second level. e.g. With a US QWERTY
+ // layout, we want "?" when the "/","?" key is pressed with
+ // Command+Shift.
+
+ // On a German layout, the OS gives us '/' with Cmd+Shift+SS(eszett)
+ // even though Cmd+SS is 'SS' and Shift+'SS' is '?'. This '/' seems
+ // like a hack to make the Cmd+"?" event look the same as the Cmd+"?"
+ // event on a US keyboard. The user thinks they are typing Cmd+"?", so
+ // we'll prefer the "?" character, replacing mCharCode with shiftedChar
+ // when Shift is pressed. However, in case there is a layout where the
+ // character unique to Cmd+Shift is the character that the user expects,
+ // we'll send it as an alternative char.
+ bool hasCmdShiftOnlyChar =
+ cmdedChar != cmdedShiftChar && uncmdedShiftChar != cmdedShiftChar;
+ uint32_t originalCmdedShiftChar = cmdedShiftChar;
+
+ // If we can make a good guess at the characters that the user would
+ // expect this key combination to produce (with and without Shift) then
+ // use those characters. This also corrects for CapsLock, which was
+ // ignored above.
+ if (!isCmdSwitchLayout) {
+ // The characters produced with Command seem similar to those without
+ // Command.
+ if (unshiftedChar) {
+ cmdedChar = unshiftedChar;
+ }
+ if (shiftedChar) {
+ cmdedShiftChar = shiftedChar;
+ }
+ } else if (uncmdedUSChar == cmdedChar) {
+ // It looks like characters from a US layout are provided when Command
+ // is down.
+ uint32_t ch = USLayout.TranslateToChar(key, lockState, kbType);
+ if (ch) {
+ cmdedChar = ch;
+ }
+ ch = USLayout.TranslateToChar(key, shiftLockMod, kbType);
+ if (ch) {
+ cmdedShiftChar = ch;
+ }
+ }
+
+ // If the current keyboard layout is switched by the Cmd key,
+ // we should append cmdedChar and shiftedCmdChar that are
+ // Latin char for the key.
+ // If the keyboard layout is Dvorak-QWERTY, we should append them only when
+ // command key is pressed because when command key isn't pressed, uncmded
+ // chars have been appended already.
+ if ((cmdedChar || cmdedShiftChar) && isCmdSwitchLayout &&
+ (aKeyEvent.IsMeta() || !isDvorakQWERTY)) {
+ AlternativeCharCode altCharCodes(cmdedChar, cmdedShiftChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "hasCmdShiftOnlyChar=%s, isCmdSwitchLayout=%s, isDvorakQWERTY=%s, "
+ "cmdedChar=U+%X, cmdedShiftChar=U+%X",
+ this, TrueOrFalse(hasCmdShiftOnlyChar), TrueOrFalse(isDvorakQWERTY),
+ TrueOrFalse(isDvorakQWERTY), cmdedChar, cmdedShiftChar));
+ // Special case for 'SS' key of German layout. See the comment of
+ // hasCmdShiftOnlyChar definition for the detail.
+ if (hasCmdShiftOnlyChar && originalCmdedShiftChar) {
+ AlternativeCharCode altCharCodes(0, originalCmdedShiftChar);
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::WillDispatchKeyboardEvent, "
+ "hasCmdShiftOnlyChar=%s, originalCmdedShiftChar=U+%X",
+ this, TrueOrFalse(hasCmdShiftOnlyChar), originalCmdedShiftChar));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+uint32_t
+TISInputSourceWrapper::ComputeGeckoKeyCode(UInt32 aNativeKeyCode,
+ UInt32 aKbType,
+ bool aCmdIsPressed)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TISInputSourceWrapper::ComputeGeckoKeyCode, aNativeKeyCode=0x%X, "
+ "aKbType=0x%X, aCmdIsPressed=%s, IsOpenedIMEMode()=%s, "
+ "IsASCIICapable()=%s",
+ this, aNativeKeyCode, aKbType, TrueOrFalse(aCmdIsPressed),
+ TrueOrFalse(IsOpenedIMEMode()), TrueOrFalse(IsASCIICapable())));
+
+ switch (aNativeKeyCode) {
+ case kVK_Space: return NS_VK_SPACE;
+ case kVK_Escape: return NS_VK_ESCAPE;
+
+ // modifiers
+ case kVK_RightCommand:
+ case kVK_Command: return NS_VK_META;
+ case kVK_RightShift:
+ case kVK_Shift: return NS_VK_SHIFT;
+ case kVK_CapsLock: return NS_VK_CAPS_LOCK;
+ case kVK_RightControl:
+ case kVK_Control: return NS_VK_CONTROL;
+ case kVK_RightOption:
+ case kVK_Option: return NS_VK_ALT;
+
+ case kVK_ANSI_KeypadClear: return NS_VK_CLEAR;
+
+ // function keys
+ case kVK_F1: return NS_VK_F1;
+ case kVK_F2: return NS_VK_F2;
+ case kVK_F3: return NS_VK_F3;
+ case kVK_F4: return NS_VK_F4;
+ case kVK_F5: return NS_VK_F5;
+ case kVK_F6: return NS_VK_F6;
+ case kVK_F7: return NS_VK_F7;
+ case kVK_F8: return NS_VK_F8;
+ case kVK_F9: return NS_VK_F9;
+ case kVK_F10: return NS_VK_F10;
+ case kVK_F11: return NS_VK_F11;
+ case kVK_F12: return NS_VK_F12;
+ // case kVK_F13: return NS_VK_F13; // clash with the 3 below
+ // case kVK_F14: return NS_VK_F14;
+ // case kVK_F15: return NS_VK_F15;
+ case kVK_F16: return NS_VK_F16;
+ case kVK_F17: return NS_VK_F17;
+ case kVK_F18: return NS_VK_F18;
+ case kVK_F19: return NS_VK_F19;
+
+ case kVK_PC_Pause: return NS_VK_PAUSE;
+ case kVK_PC_ScrollLock: return NS_VK_SCROLL_LOCK;
+ case kVK_PC_PrintScreen: return NS_VK_PRINTSCREEN;
+
+ // keypad
+ case kVK_ANSI_Keypad0: return NS_VK_NUMPAD0;
+ case kVK_ANSI_Keypad1: return NS_VK_NUMPAD1;
+ case kVK_ANSI_Keypad2: return NS_VK_NUMPAD2;
+ case kVK_ANSI_Keypad3: return NS_VK_NUMPAD3;
+ case kVK_ANSI_Keypad4: return NS_VK_NUMPAD4;
+ case kVK_ANSI_Keypad5: return NS_VK_NUMPAD5;
+ case kVK_ANSI_Keypad6: return NS_VK_NUMPAD6;
+ case kVK_ANSI_Keypad7: return NS_VK_NUMPAD7;
+ case kVK_ANSI_Keypad8: return NS_VK_NUMPAD8;
+ case kVK_ANSI_Keypad9: return NS_VK_NUMPAD9;
+
+ case kVK_ANSI_KeypadMultiply: return NS_VK_MULTIPLY;
+ case kVK_ANSI_KeypadPlus: return NS_VK_ADD;
+ case kVK_ANSI_KeypadMinus: return NS_VK_SUBTRACT;
+ case kVK_ANSI_KeypadDecimal: return NS_VK_DECIMAL;
+ case kVK_ANSI_KeypadDivide: return NS_VK_DIVIDE;
+
+ case kVK_JIS_KeypadComma: return NS_VK_SEPARATOR;
+
+ // IME keys
+ case kVK_JIS_Eisu: return NS_VK_EISU;
+ case kVK_JIS_Kana: return NS_VK_KANA;
+
+ // these may clash with forward delete and help
+ case kVK_PC_Insert: return NS_VK_INSERT;
+ case kVK_PC_Delete: return NS_VK_DELETE;
+
+ case kVK_PC_Backspace: return NS_VK_BACK;
+ case kVK_Tab: return NS_VK_TAB;
+
+ case kVK_Home: return NS_VK_HOME;
+ case kVK_End: return NS_VK_END;
+
+ case kVK_PageUp: return NS_VK_PAGE_UP;
+ case kVK_PageDown: return NS_VK_PAGE_DOWN;
+
+ case kVK_LeftArrow: return NS_VK_LEFT;
+ case kVK_RightArrow: return NS_VK_RIGHT;
+ case kVK_UpArrow: return NS_VK_UP;
+ case kVK_DownArrow: return NS_VK_DOWN;
+
+ case kVK_PC_ContextMenu: return NS_VK_CONTEXT_MENU;
+
+ case kVK_ANSI_1: return NS_VK_1;
+ case kVK_ANSI_2: return NS_VK_2;
+ case kVK_ANSI_3: return NS_VK_3;
+ case kVK_ANSI_4: return NS_VK_4;
+ case kVK_ANSI_5: return NS_VK_5;
+ case kVK_ANSI_6: return NS_VK_6;
+ case kVK_ANSI_7: return NS_VK_7;
+ case kVK_ANSI_8: return NS_VK_8;
+ case kVK_ANSI_9: return NS_VK_9;
+ case kVK_ANSI_0: return NS_VK_0;
+
+ case kVK_ANSI_KeypadEnter:
+ case kVK_Return:
+ case kVK_Powerbook_KeypadEnter: return NS_VK_RETURN;
+ }
+
+ // If Cmd key is pressed, that causes switching keyboard layout temporarily.
+ // E.g., Dvorak-QWERTY. Therefore, if Cmd key is pressed, we should honor it.
+ UInt32 modifiers = aCmdIsPressed ? cmdKey : 0;
+
+ uint32_t charCode = TranslateToChar(aNativeKeyCode, modifiers, aKbType);
+
+ // Special case for Mac. Mac inputs Yen sign (U+00A5) directly instead of
+ // Back slash (U+005C). We should return NS_VK_BACK_SLASH for compatibility
+ // with other platforms.
+ // XXX How about Won sign (U+20A9) which has same problem as Yen sign?
+ if (charCode == 0x00A5) {
+ return NS_VK_BACK_SLASH;
+ }
+
+ uint32_t keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
+ if (keyCode) {
+ return keyCode;
+ }
+
+ // If the unshifed char isn't an ASCII character, use shifted char.
+ charCode = TranslateToChar(aNativeKeyCode, modifiers | shiftKey, aKbType);
+ keyCode = WidgetUtils::ComputeKeyCodeFromChar(charCode);
+ if (keyCode) {
+ return keyCode;
+ }
+
+ // If this is ASCII capable, give up to compute it.
+ if (IsASCIICapable()) {
+ return 0;
+ }
+
+ // Retry with ASCII capable keyboard layout.
+ TISInputSourceWrapper currentKeyboardLayout;
+ currentKeyboardLayout.InitByCurrentASCIICapableKeyboardLayout();
+ NS_ENSURE_TRUE(mInputSource != currentKeyboardLayout.mInputSource, 0);
+ keyCode = currentKeyboardLayout.ComputeGeckoKeyCode(aNativeKeyCode, aKbType,
+ aCmdIsPressed);
+
+ // However, if keyCode isn't for an alphabet keys or a numeric key, we should
+ // ignore it. For example, comma key of Thai layout is same as close-square-
+ // bracket key of US layout and an unicode character key of Thai layout is
+ // same as comma key of US layout. If we return NS_VK_COMMA for latter key,
+ // web application developers cannot distinguish with the former key.
+ return ((keyCode >= NS_VK_A && keyCode <= NS_VK_Z) ||
+ (keyCode >= NS_VK_0 && keyCode <= NS_VK_9)) ? keyCode : 0;
+}
+
+// static
+KeyNameIndex
+TISInputSourceWrapper::ComputeGeckoKeyNameIndex(UInt32 aNativeKeyCode)
+{
+ // NOTE:
+ // When unsupported keys like Convert, Nonconvert of Japanese keyboard is
+ // pressed:
+ // on 10.6.x, 'A' key event is fired (and also actually 'a' is inserted).
+ // on 10.7.x, Nothing happens.
+ // on 10.8.x, Nothing happens.
+ // on 10.9.x, FlagsChanged event is fired with keyCode 0xFF.
+ switch (aNativeKeyCode) {
+
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+// static
+CodeNameIndex
+TISInputSourceWrapper::ComputeGeckoCodeNameIndex(UInt32 aNativeKeyCode)
+{
+ switch (aNativeKeyCode) {
+
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandler implementation (static methods)
+ *
+ ******************************************************************************/
+
+NSUInteger TextInputHandler::sLastModifierState = 0;
+
+// static
+CFArrayRef
+TextInputHandler::CreateAllKeyboardLayoutList()
+{
+ const void* keys[] = { kTISPropertyInputSourceType };
+ const void* values[] = { kTISTypeKeyboardLayout };
+ CFDictionaryRef filter =
+ ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ CFArrayRef list = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ return list;
+}
+
+// static
+void
+TextInputHandler::DebugPrintAllKeyboardLayouts()
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFArrayRef list = CreateAllKeyboardLayoutList();
+ MOZ_LOG(gLog, LogLevel::Info, ("Keyboard layout configuration:"));
+ CFIndex idx = ::CFArrayGetCount(list);
+ TISInputSourceWrapper tis;
+ for (CFIndex i = 0; i < idx; ++i) {
+ TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
+ const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
+ tis.InitByTISInputSourceRef(inputSource);
+ nsAutoString name, isid;
+ tis.GetLocalizedName(name);
+ tis.GetInputSourceID(isid);
+ MOZ_LOG(gLog, LogLevel::Info,
+ (" %s\t<%s>%s%s\n",
+ NS_ConvertUTF16toUTF8(name).get(),
+ NS_ConvertUTF16toUTF8(isid).get(),
+ tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
+ tis.IsKeyboardLayout() && tis.GetUCKeyboardLayout() ?
+ "" : "\t(uchr is NOT AVAILABLE)"));
+ }
+ ::CFRelease(list);
+ }
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandler implementation
+ *
+ ******************************************************************************/
+
+TextInputHandler::TextInputHandler(nsChildView* aWidget,
+ NSView<mozView> *aNativeView) :
+ IMEInputHandler(aWidget, aNativeView)
+{
+ EnsureToLogAllKeyboardLayoutsAndIMEs();
+ [mView installTextInputHandler:this];
+}
+
+TextInputHandler::~TextInputHandler()
+{
+ [mView uninstallTextInputHandler];
+}
+
+bool
+TextInputHandler::HandleKeyDownEvent(NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "widget has been already destroyed", this));
+ return false;
+ }
+
+ // Insert empty line to the log for easier to read.
+ MOZ_LOG(gLog, LogLevel::Info, (""));
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, aNativeEvent=%p, "
+ "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
+ "charactersIgnoringModifiers=\"%s\"",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ [aNativeEvent keyCode], [aNativeEvent keyCode],
+ [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
+ GetCharacters([aNativeEvent charactersIgnoringModifiers])));
+
+ // Except when Command key is pressed, we should hide mouse cursor until
+ // next mousemove. Handling here means that:
+ // - Don't hide mouse cursor at pressing modifier key
+ // - Hide mouse cursor even if the key event will be handled by IME (i.e.,
+ // even without dispatching eKeyPress events)
+ // - Hide mouse cursor even when a plugin has focus
+ if (!([aNativeEvent modifierFlags] & NSCommandKeyMask)) {
+ [NSCursor setHiddenUntilMouseMoves:YES];
+ }
+
+ RefPtr<nsChildView> widget(mWidget);
+
+ KeyEventState* currentKeyEvent = PushKeyEvent(aNativeEvent);
+ AutoKeyEventStateCleaner remover(this);
+
+ ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+ if (ctiPanel && ctiPanel->IsInComposition()) {
+ nsAutoString committed;
+ ctiPanel->InterpretKeyEvent(aNativeEvent, committed);
+ if (!committed.IsEmpty()) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyDownEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keydown for ComplexTextInputPanel", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent imeEvent(true, eKeyDown, widget);
+ currentKeyEvent->InitKeyEvent(this, imeEvent);
+ imeEvent.mPluginTextEventString.Assign(committed);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(eKeyDown, imeEvent, status,
+ currentKeyEvent);
+ }
+ return true;
+ }
+
+ NSResponder* firstResponder = [[mView window] firstResponder];
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyDownEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keydown for ordinal cases", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent keydownEvent(true, eKeyDown, widget);
+ currentKeyEvent->InitKeyEvent(this, keydownEvent);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, status,
+ currentKeyEvent);
+ currentKeyEvent->mKeyDownHandled =
+ (status == nsEventStatus_eConsumeNoDefault);
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "widget was destroyed by keydown event", this));
+ return currentKeyEvent->IsDefaultPrevented();
+ }
+
+ // The key down event may have shifted the focus, in which
+ // case we should not fire the key press.
+ // XXX This is a special code only on Cocoa widget, why is this needed?
+ if (firstResponder != [[mView window] firstResponder]) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "view lost focus by keydown event", this));
+ return currentKeyEvent->IsDefaultPrevented();
+ }
+
+ if (currentKeyEvent->IsDefaultPrevented()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "keydown event's default is prevented", this));
+ return true;
+ }
+
+ // Let Cocoa interpret the key events, caching IsIMEComposing first.
+ bool wasComposing = IsIMEComposing();
+ bool interpretKeyEventsCalled = false;
+ // Don't call interpretKeyEvents when a plugin has focus. If we call it,
+ // for example, a character is inputted twice during a composition in e10s
+ // mode.
+ if (!widget->IsPluginFocused() && (IsIMEEnabled() || IsASCIICapableOnly())) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, calling interpretKeyEvents",
+ this));
+ [mView interpretKeyEvents:[NSArray arrayWithObject:aNativeEvent]];
+ interpretKeyEventsCalled = true;
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, called interpretKeyEvents",
+ this));
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, widget was destroyed",
+ this));
+ return currentKeyEvent->IsDefaultPrevented();
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, wasComposing=%s, "
+ "IsIMEComposing()=%s",
+ this, TrueOrFalse(wasComposing), TrueOrFalse(IsIMEComposing())));
+
+ if (currentKeyEvent->CanDispatchKeyPressEvent() &&
+ !wasComposing && !IsIMEComposing()) {
+ rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyDownEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keypress", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ currentKeyEvent->InitKeyEvent(this, keypressEvent);
+
+ // If we called interpretKeyEvents and this isn't normal character input
+ // then IME probably ate the event for some reason. We do not want to
+ // send a key press event in that case.
+ // TODO:
+ // There are some other cases which IME eats the current event.
+ // 1. If key events were nested during calling interpretKeyEvents, it means
+ // that IME did something. Then, we should do nothing.
+ // 2. If one or more commands are called like "deleteBackward", we should
+ // dispatch keypress event at that time. Note that the command may have
+ // been a converted or generated action by IME. Then, we shouldn't do
+ // our default action for this key.
+ if (!(interpretKeyEventsCalled &&
+ IsNormalCharInputtingEvent(keypressEvent))) {
+ currentKeyEvent->mKeyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ currentKeyEvent);
+ currentKeyEvent->mKeyPressHandled =
+ (status == nsEventStatus_eConsumeNoDefault);
+ currentKeyEvent->mKeyPressDispatched = true;
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, keypress event dispatched",
+ this));
+ }
+ }
+
+ // Note: mWidget might have become null here. Don't count on it from here on.
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyDownEvent, "
+ "keydown handled=%s, keypress handled=%s, causedOtherKeyEvents=%s, "
+ "compositionDispatched=%s",
+ this, TrueOrFalse(currentKeyEvent->mKeyDownHandled),
+ TrueOrFalse(currentKeyEvent->mKeyPressHandled),
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents),
+ TrueOrFalse(currentKeyEvent->mCompositionDispatched)));
+ // Insert empty line to the log for easier to read.
+ MOZ_LOG(gLog, LogLevel::Info, (""));
+ return currentKeyEvent->IsDefaultPrevented();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void
+TextInputHandler::HandleKeyUpEvent(NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyUpEvent, aNativeEvent=%p, "
+ "type=%s, keyCode=%lld (0x%X), modifierFlags=0x%X, characters=\"%s\", "
+ "charactersIgnoringModifiers=\"%s\", "
+ "IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ [aNativeEvent keyCode], [aNativeEvent keyCode],
+ [aNativeEvent modifierFlags], GetCharacters([aNativeEvent characters]),
+ GetCharacters([aNativeEvent charactersIgnoringModifiers]),
+ TrueOrFalse(IsIMEComposing())));
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleKeyUpEvent, "
+ "widget has been already destroyed", this));
+ return;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyUpEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return;
+ }
+
+ WidgetKeyboardEvent keyupEvent(true, eKeyUp, mWidget);
+ InitKeyEvent(aNativeEvent, keyupEvent);
+
+ KeyEventState currentKeyEvent(aNativeEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status,
+ &currentKeyEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+TextInputHandler::HandleFlagsChanged(NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleFlagsChanged, "
+ "widget has been already destroyed", this));
+ return;
+ }
+
+ RefPtr<nsChildView> kungFuDeathGrip(mWidget);
+ mozilla::Unused << kungFuDeathGrip; // Not referenced within this function
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::HandleFlagsChanged, aNativeEvent=%p, "
+ "type=%s, keyCode=%s (0x%X), modifierFlags=0x%08X, "
+ "sLastModifierState=0x%08X, IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
+ [aNativeEvent modifierFlags], sLastModifierState,
+ TrueOrFalse(IsIMEComposing())));
+
+ MOZ_ASSERT([aNativeEvent type] == NSFlagsChanged);
+
+ NSUInteger diff = [aNativeEvent modifierFlags] ^ sLastModifierState;
+ // Device dependent flags for left-control key, both shift keys, both command
+ // keys and both option keys have been defined in Next's SDK. But we
+ // shouldn't use it directly as far as possible since Cocoa SDK doesn't
+ // define them. Fortunately, we need them only when we dispatch keyup
+ // events. So, we can usually know the actual relation between keyCode and
+ // device dependent flags. However, we need to remove following flags first
+ // since the differences don't indicate modifier key state.
+ // NX_STYLUSPROXIMITYMASK: Probably used for pen like device.
+ // kCGEventFlagMaskNonCoalesced (= NX_NONCOALSESCEDMASK): See the document for
+ // Quartz Event Services.
+ diff &= ~(NX_STYLUSPROXIMITYMASK | kCGEventFlagMaskNonCoalesced);
+
+ switch ([aNativeEvent keyCode]) {
+ // CapsLock state and other modifier states are different:
+ // CapsLock state does not revert when the CapsLock key goes up, as the
+ // modifier state does for other modifier keys on key up.
+ case kVK_CapsLock: {
+ // Fire key down event for caps lock.
+ DispatchKeyEventForFlagsChanged(aNativeEvent, true);
+ // XXX should we fire keyup event too? The keyup event for CapsLock key
+ // is never dispatched on Gecko.
+ // XXX WebKit dispatches keydown event when CapsLock is locked, otherwise,
+ // keyup event. If we do so, we cannot keep the consistency with other
+ // platform's behavior...
+ break;
+ }
+
+ // If the event is caused by pressing or releasing a modifier key, just
+ // dispatch the key's event.
+ case kVK_Shift:
+ case kVK_RightShift:
+ case kVK_Command:
+ case kVK_RightCommand:
+ case kVK_Control:
+ case kVK_RightControl:
+ case kVK_Option:
+ case kVK_RightOption:
+ case kVK_Help: {
+ // We assume that at most one modifier is changed per event if the event
+ // is caused by pressing or releasing a modifier key.
+ bool isKeyDown = ([aNativeEvent modifierFlags] & diff) != 0;
+ DispatchKeyEventForFlagsChanged(aNativeEvent, isKeyDown);
+ // XXX Some applications might send the event with incorrect device-
+ // dependent flags.
+ if (isKeyDown && ((diff & ~NSDeviceIndependentModifierFlagsMask) != 0)) {
+ unsigned short keyCode = [aNativeEvent keyCode];
+ const ModifierKey* modifierKey =
+ GetModifierKeyForDeviceDependentFlags(diff);
+ if (modifierKey && modifierKey->keyCode != keyCode) {
+ // Although, we're not sure the actual cause of this case, the stored
+ // modifier information and the latest key event information may be
+ // mismatched. Then, let's reset the stored information.
+ // NOTE: If this happens, it may fail to handle NSFlagsChanged event
+ // in the default case (below). However, it's the rare case handler
+ // and this case occurs rarely. So, we can ignore the edge case bug.
+ NS_WARNING("Resetting stored modifier key information");
+ mModifierKeys.Clear();
+ modifierKey = nullptr;
+ }
+ if (!modifierKey) {
+ mModifierKeys.AppendElement(ModifierKey(diff, keyCode));
+ }
+ }
+ break;
+ }
+
+ // Currently we don't support Fn key since other browsers don't dispatch
+ // events for it and we don't have keyCode for this key.
+ // It should be supported when we implement .key and .char.
+ case kVK_Function:
+ break;
+
+ // If the event is caused by something else than pressing or releasing a
+ // single modifier key (for example by the app having been deactivated
+ // using command-tab), use the modifiers themselves to determine which
+ // key's event to dispatch, and whether it's a keyup or keydown event.
+ // In all cases we assume one or more modifiers are being deactivated
+ // (never activated) -- otherwise we'd have received one or more events
+ // corresponding to a single modifier key being pressed.
+ default: {
+ NSUInteger modifiers = sLastModifierState;
+ for (int32_t bit = 0; bit < 32; ++bit) {
+ NSUInteger flag = 1 << bit;
+ if (!(diff & flag)) {
+ continue;
+ }
+
+ // Given correct information from the application, a flag change here
+ // will normally be a deactivation (except for some lockable modifiers
+ // such as CapsLock). But some applications (like VNC) can send an
+ // activating event with a zero keyCode. So we need to check for that
+ // here.
+ bool dispatchKeyDown = ((flag & [aNativeEvent modifierFlags]) != 0);
+
+ unsigned short keyCode = 0;
+ if (flag & NSDeviceIndependentModifierFlagsMask) {
+ switch (flag) {
+ case NSAlphaShiftKeyMask:
+ keyCode = kVK_CapsLock;
+ dispatchKeyDown = true;
+ break;
+
+ case NSNumericPadKeyMask:
+ // NSNumericPadKeyMask is fired by VNC a lot. But not all of
+ // these events can really be Clear key events, so we just ignore
+ // them.
+ continue;
+
+ case NSHelpKeyMask:
+ keyCode = kVK_Help;
+ break;
+
+ case NSFunctionKeyMask:
+ // An NSFunctionKeyMask change here will normally be a
+ // deactivation. But sometimes it will be an activation send (by
+ // VNC for example) with a zero keyCode.
+ continue;
+
+ // These cases (NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
+ // and NSCommandKeyMask) should be handled by the other branch of
+ // the if statement, below (which handles device dependent flags).
+ // However, some applications (like VNC) can send key events without
+ // any device dependent flags, so we handle them here instead.
+ case NSShiftKeyMask:
+ keyCode = (modifiers & 0x0004) ? kVK_RightShift : kVK_Shift;
+ break;
+ case NSControlKeyMask:
+ keyCode = (modifiers & 0x2000) ? kVK_RightControl : kVK_Control;
+ break;
+ case NSAlternateKeyMask:
+ keyCode = (modifiers & 0x0040) ? kVK_RightOption : kVK_Option;
+ break;
+ case NSCommandKeyMask:
+ keyCode = (modifiers & 0x0010) ? kVK_RightCommand : kVK_Command;
+ break;
+
+ default:
+ continue;
+ }
+ } else {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForDeviceDependentFlags(flag);
+ if (!modifierKey) {
+ // See the note above (in the other branch of the if statement)
+ // about the NSShiftKeyMask, NSControlKeyMask, NSAlternateKeyMask
+ // and NSCommandKeyMask cases.
+ continue;
+ }
+ keyCode = modifierKey->keyCode;
+ }
+
+ // Remove flags
+ modifiers &= ~flag;
+ switch (keyCode) {
+ case kVK_Shift: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightShift);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSShiftKeyMask;
+ }
+ break;
+ }
+ case kVK_RightShift: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Shift);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSShiftKeyMask;
+ }
+ break;
+ }
+ case kVK_Command: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightCommand);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSCommandKeyMask;
+ }
+ break;
+ }
+ case kVK_RightCommand: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Command);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSCommandKeyMask;
+ }
+ break;
+ }
+ case kVK_Control: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightControl);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSControlKeyMask;
+ }
+ break;
+ }
+ case kVK_RightControl: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Control);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSControlKeyMask;
+ }
+ break;
+ }
+ case kVK_Option: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_RightOption);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSAlternateKeyMask;
+ }
+ break;
+ }
+ case kVK_RightOption: {
+ const ModifierKey* modifierKey =
+ GetModifierKeyForNativeKeyCode(kVK_Option);
+ if (!modifierKey ||
+ !(modifiers & modifierKey->GetDeviceDependentFlags())) {
+ modifiers &= ~NSAlternateKeyMask;
+ }
+ break;
+ }
+ case kVK_Help:
+ modifiers &= ~NSHelpKeyMask;
+ break;
+ default:
+ break;
+ }
+
+ NSEvent* event =
+ [NSEvent keyEventWithType:NSFlagsChanged
+ location:[aNativeEvent locationInWindow]
+ modifierFlags:modifiers
+ timestamp:[aNativeEvent timestamp]
+ windowNumber:[aNativeEvent windowNumber]
+ context:[aNativeEvent context]
+ characters:@""
+ charactersIgnoringModifiers:@""
+ isARepeat:NO
+ keyCode:keyCode];
+ DispatchKeyEventForFlagsChanged(event, dispatchKeyDown);
+ if (Destroyed()) {
+ break;
+ }
+
+ // Stop if focus has changed.
+ // Check to see if mView is still the first responder.
+ if (![mView isFirstResponder]) {
+ break;
+ }
+
+ }
+ break;
+ }
+ }
+
+ // Be aware, the widget may have been destroyed.
+ sLastModifierState = [aNativeEvent modifierFlags];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+const TextInputHandler::ModifierKey*
+TextInputHandler::GetModifierKeyForNativeKeyCode(unsigned short aKeyCode) const
+{
+ for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
+ if (mModifierKeys[i].keyCode == aKeyCode) {
+ return &((ModifierKey&)mModifierKeys[i]);
+ }
+ }
+ return nullptr;
+}
+
+const TextInputHandler::ModifierKey*
+TextInputHandler::GetModifierKeyForDeviceDependentFlags(NSUInteger aFlags) const
+{
+ for (ModifierKeyArray::index_type i = 0; i < mModifierKeys.Length(); ++i) {
+ if (mModifierKeys[i].GetDeviceDependentFlags() ==
+ (aFlags & ~NSDeviceIndependentModifierFlagsMask)) {
+ return &((ModifierKey&)mModifierKeys[i]);
+ }
+ }
+ return nullptr;
+}
+
+void
+TextInputHandler::DispatchKeyEventForFlagsChanged(NSEvent* aNativeEvent,
+ bool aDispatchKeyDown)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ return;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DispatchKeyEventForFlagsChanged, aNativeEvent=%p, "
+ "type=%s, keyCode=%s (0x%X), aDispatchKeyDown=%s, IsIMEComposing()=%s",
+ this, aNativeEvent, GetNativeKeyEventType(aNativeEvent),
+ GetKeyNameForNativeKeyCode([aNativeEvent keyCode]), [aNativeEvent keyCode],
+ TrueOrFalse(aDispatchKeyDown), TrueOrFalse(IsIMEComposing())));
+
+ if ([aNativeEvent type] != NSFlagsChanged || IsIMEComposing()) {
+ return;
+ }
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchKeyEventForFlagsChanged, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return;
+ }
+
+ EventMessage message = aDispatchKeyDown ? eKeyDown : eKeyUp;
+
+ // Fire a key event.
+ WidgetKeyboardEvent keyEvent(true, message, mWidget);
+ InitKeyEvent(aNativeEvent, keyEvent);
+
+ // Attach a plugin event, in case keyEvent gets dispatched to a plugin. Only
+ // one field is needed -- the type. The other fields can be constructed as
+ // the need arises. But Gecko doesn't have anything equivalent to the
+ // NPCocoaEventFlagsChanged type, and this needs to be passed accurately to
+ // any plugin to which this event is sent.
+ NPCocoaEvent cocoaEvent;
+ nsCocoaUtils::InitNPCocoaEvent(&cocoaEvent);
+ cocoaEvent.type = NPCocoaEventFlagsChanged;
+ keyEvent.mPluginEvent.Copy(cocoaEvent);
+
+ KeyEventState currentKeyEvent(aNativeEvent);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mDispatcher->DispatchKeyboardEvent(message, keyEvent, status,
+ &currentKeyEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+TextInputHandler::InsertText(NSAttributedString* aAttrString,
+ NSRange* aReplacementRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Destroyed()) {
+ return;
+ }
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::InsertText, aAttrString=\"%s\", "
+ "aReplacementRange=%p { location=%llu, length=%llu }, "
+ "IsIMEComposing()=%s, IgnoreIMEComposition()=%s, "
+ "keyevent=%p, keydownHandled=%s, keypressDispatched=%s, "
+ "causedOtherKeyEvents=%s, compositionDispatched=%s",
+ this, GetCharacters([aAttrString string]), aReplacementRange,
+ aReplacementRange ? aReplacementRange->location : 0,
+ aReplacementRange ? aReplacementRange->length : 0,
+ TrueOrFalse(IsIMEComposing()), TrueOrFalse(IgnoreIMEComposition()),
+ currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
+
+ if (IgnoreIMEComposition()) {
+ return;
+ }
+
+ InputContext context = mWidget->GetInputContext();
+ bool isEditable = (context.mIMEState.mEnabled == IMEState::ENABLED ||
+ context.mIMEState.mEnabled == IMEState::PASSWORD);
+ NSRange selectedRange = SelectedRange();
+
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ AutoInsertStringClearer clearer(currentKeyEvent);
+ if (currentKeyEvent) {
+ currentKeyEvent->mInsertString = &str;
+ }
+
+ if (!IsIMEComposing() && str.IsEmpty()) {
+ // nothing to do if there is no content which can be removed.
+ if (!isEditable) {
+ return;
+ }
+ // If replacement range is specified, we need to remove the range.
+ // Otherwise, we need to remove the selected range if it's not collapsed.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound) {
+ // nothing to do since the range is collapsed.
+ if (aReplacementRange->length == 0) {
+ return;
+ }
+ // If the replacement range is different from current selected range,
+ // select the range.
+ if (!NSEqualRanges(selectedRange, *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+ selectedRange = SelectedRange();
+ }
+ NS_ENSURE_TRUE_VOID(selectedRange.location != NSNotFound);
+ if (selectedRange.length == 0) {
+ return; // nothing to do
+ }
+ // If this is caused by a key input, the keypress event which will be
+ // dispatched later should cause the delete. Therefore, nothing to do here.
+ // Although, we're not sure if such case is actually possible.
+ if (!currentKeyEvent) {
+ return;
+ }
+ // Delete the selected range.
+ RefPtr<TextInputHandler> kungFuDeathGrip(this);
+ WidgetContentCommandEvent deleteCommandEvent(true, eContentCommandDelete,
+ mWidget);
+ DispatchEvent(deleteCommandEvent);
+ NS_ENSURE_TRUE_VOID(deleteCommandEvent.mSucceeded);
+ // Be aware! The widget might be destroyed here.
+ return;
+ }
+
+ bool isReplacingSpecifiedRange =
+ isEditable && aReplacementRange &&
+ aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(selectedRange, *aReplacementRange);
+
+ // If this is not caused by pressing a key, there is a composition or
+ // replacing a range which is different from current selection, let's
+ // insert the text as committing a composition.
+ // If InsertText() is called two or more times, we should insert all
+ // text with composition events.
+ // XXX When InsertText() is called multiple times, Chromium dispatches
+ // only one composition event. So, we need to store InsertText()
+ // calls and flush later.
+ if (!currentKeyEvent || currentKeyEvent->mCompositionDispatched ||
+ IsIMEComposing() || isReplacingSpecifiedRange) {
+ InsertTextAsCommittingComposition(aAttrString, aReplacementRange);
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+ }
+ return;
+ }
+
+ // Don't let the same event be fired twice when hitting
+ // enter/return for Bug 420502. However, Korean IME (or some other
+ // simple IME) may work without marked text. For example, composing
+ // character may be inserted as committed text and it's modified with
+ // aReplacementRange. When a keydown starts new composition with
+ // committing previous character, InsertText() may be called twice,
+ // one is for committing previous character and then, inserting new
+ // composing character as committed character. In the latter case,
+ // |CanDispatchKeyPressEvent()| returns true but we need to dispatch
+ // keypress event for the new character. So, when IME tries to insert
+ // printable characters, we should ignore current key event state even
+ // after the keydown has already caused dispatching composition event.
+ // XXX Anyway, we should sort out around this at fixing bug 1338460.
+ if (currentKeyEvent && !currentKeyEvent->CanDispatchKeyPressEvent() &&
+ (str.IsEmpty() || (str.Length() == 1 && !IsPrintableChar(str[0])))) {
+ return;
+ }
+
+ // XXX Shouldn't we hold mDispatcher instead of mWidget?
+ RefPtr<nsChildView> widget(mWidget);
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::HandleKeyUpEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return;
+ }
+
+ // Dispatch keypress event with char instead of compositionchange event
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ // XXX Why do we need to dispatch keypress event for not inputting any
+ // string? If it wants to delete the specified range, should we
+ // dispatch an eContentCommandDelete event instead? Because this
+ // must not be caused by a key operation, a part of IME's processing.
+ keypressEvent.mIsChar = IsPrintableChar(str.CharAt(0));
+
+ // Don't set other modifiers from the current event, because here in
+ // -insertText: they've already been taken into account in creating
+ // the input string.
+
+ if (currentKeyEvent) {
+ currentKeyEvent->InitKeyEvent(this, keypressEvent);
+ } else {
+ nsCocoaUtils::InitInputEvent(keypressEvent, static_cast<NSEvent*>(nullptr));
+ keypressEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ keypressEvent.mKeyValue = str;
+ // FYI: TextEventDispatcher will set mKeyCode to 0 for printable key's
+ // keypress events even if they don't cause inputting non-empty string.
+ }
+
+ // Remove basic modifiers from keypress event because if they are included,
+ // nsPlaintextEditor ignores the event.
+ keypressEvent.mModifiers &= ~(MODIFIER_CONTROL |
+ MODIFIER_ALT |
+ MODIFIER_META);
+
+ // TODO:
+ // If mCurrentKeyEvent.mKeyEvent is null, the text should be inputted as
+ // composition events.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool keyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ currentKeyEvent);
+ bool keyPressHandled = (status == nsEventStatus_eConsumeNoDefault);
+
+ // Note: mWidget might have become null here. Don't count on it from here on.
+
+ if (currentKeyEvent) {
+ currentKeyEvent->mKeyPressHandled = keyPressHandled;
+ currentKeyEvent->mKeyPressDispatched = keyPressDispatched;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool
+TextInputHandler::DoCommandBySelector(const char* aSelector)
+{
+ RefPtr<nsChildView> widget(mWidget);
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DoCommandBySelector, aSelector=\"%s\", "
+ "Destroyed()=%s, keydownHandled=%s, keypressHandled=%s, "
+ "causedOtherKeyEvents=%s",
+ this, aSelector ? aSelector : "", TrueOrFalse(Destroyed()),
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyPressHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A"));
+
+ if (currentKeyEvent && currentKeyEvent->CanDispatchKeyPressEvent()) {
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DoCommandBySelector, "
+ "FAILED, due to BeginNativeInputTransaction() failure "
+ "at dispatching keypress", this));
+ return false;
+ }
+
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, widget);
+ currentKeyEvent->InitKeyEvent(this, keypressEvent);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ currentKeyEvent->mKeyPressDispatched =
+ mDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ currentKeyEvent);
+ currentKeyEvent->mKeyPressHandled =
+ (status == nsEventStatus_eConsumeNoDefault);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandler::DoCommandBySelector, keypress event "
+ "dispatched, Destroyed()=%s, keypressHandled=%s",
+ this, TrueOrFalse(Destroyed()),
+ TrueOrFalse(currentKeyEvent->mKeyPressHandled)));
+ }
+
+ return (!Destroyed() && currentKeyEvent &&
+ currentKeyEvent->IsDefaultPrevented());
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation (static methods)
+ *
+ ******************************************************************************/
+
+bool IMEInputHandler::sStaticMembersInitialized = false;
+bool IMEInputHandler::sCachedIsForRTLLangage = false;
+CFStringRef IMEInputHandler::sLatestIMEOpenedModeInputSourceID = nullptr;
+IMEInputHandler* IMEInputHandler::sFocusedIMEHandler = nullptr;
+
+// static
+void
+IMEInputHandler::InitStaticMembers()
+{
+ if (sStaticMembersInitialized)
+ return;
+ sStaticMembersInitialized = true;
+ // We need to check the keyboard layout changes on all applications.
+ CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
+ // XXX Don't we need to remove the observer at shut down?
+ // Mac Dev Center's document doesn't say how to remove the observer if
+ // the second parameter is NULL.
+ ::CFNotificationCenterAddObserver(center, NULL,
+ OnCurrentTextInputSourceChange,
+ kTISNotifySelectedKeyboardInputSourceChanged, NULL,
+ CFNotificationSuspensionBehaviorDeliverImmediately);
+ // Initiailize with the current keyboard layout
+ OnCurrentTextInputSourceChange(NULL, NULL,
+ kTISNotifySelectedKeyboardInputSourceChanged,
+ NULL, NULL);
+}
+
+// static
+void
+IMEInputHandler::OnCurrentTextInputSourceChange(CFNotificationCenterRef aCenter,
+ void* aObserver,
+ CFStringRef aName,
+ const void* aObject,
+ CFDictionaryRef aUserInfo)
+{
+ // Cache the latest IME opened mode to sLatestIMEOpenedModeInputSourceID.
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentInputSource();
+ if (tis.IsOpenedIMEMode()) {
+ tis.GetInputSourceID(sLatestIMEOpenedModeInputSourceID);
+ }
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ static CFStringRef sLastTIS = nullptr;
+ CFStringRef newTIS;
+ tis.GetInputSourceID(newTIS);
+ if (!sLastTIS ||
+ ::CFStringCompare(sLastTIS, newTIS, 0) != kCFCompareEqualTo) {
+ TISInputSourceWrapper tis1, tis2, tis3, tis4, tis5;
+ tis1.InitByCurrentKeyboardLayout();
+ tis2.InitByCurrentASCIICapableInputSource();
+ tis3.InitByCurrentASCIICapableKeyboardLayout();
+ tis4.InitByCurrentInputMethodKeyboardLayoutOverride();
+ tis5.InitByTISInputSourceRef(tis.GetKeyboardLayoutInputSource());
+ CFStringRef is0 = nullptr, is1 = nullptr, is2 = nullptr, is3 = nullptr,
+ is4 = nullptr, is5 = nullptr, type0 = nullptr,
+ lang0 = nullptr, bundleID0 = nullptr;
+ tis.GetInputSourceID(is0);
+ tis1.GetInputSourceID(is1);
+ tis2.GetInputSourceID(is2);
+ tis3.GetInputSourceID(is3);
+ tis4.GetInputSourceID(is4);
+ tis5.GetInputSourceID(is5);
+ tis.GetInputSourceType(type0);
+ tis.GetPrimaryLanguage(lang0);
+ tis.GetBundleID(bundleID0);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("IMEInputHandler::OnCurrentTextInputSourceChange,\n"
+ " Current Input Source is changed to:\n"
+ " currentInputContext=%p\n"
+ " %s\n"
+ " type=%s %s\n"
+ " overridden keyboard layout=%s\n"
+ " used keyboard layout for translation=%s\n"
+ " primary language=%s\n"
+ " bundle ID=%s\n"
+ " current ASCII capable Input Source=%s\n"
+ " current Keyboard Layout=%s\n"
+ " current ASCII capable Keyboard Layout=%s",
+ [NSTextInputContext currentInputContext], GetCharacters(is0),
+ GetCharacters(type0), tis.IsASCIICapable() ? "- ASCII capable " : "",
+ GetCharacters(is4), GetCharacters(is5),
+ GetCharacters(lang0), GetCharacters(bundleID0),
+ GetCharacters(is2), GetCharacters(is1), GetCharacters(is3)));
+ }
+ sLastTIS = newTIS;
+ }
+
+ /**
+ * When the direction is changed, all the children are notified.
+ * No need to treat the initial case separately because it is covered
+ * by the general case (sCachedIsForRTLLangage is initially false)
+ */
+ if (sCachedIsForRTLLangage != tis.IsForRTLLanguage()) {
+ WidgetUtils::SendBidiKeyboardInfoToContent();
+ sCachedIsForRTLLangage = tis.IsForRTLLanguage();
+ }
+}
+
+// static
+void
+IMEInputHandler::FlushPendingMethods(nsITimer* aTimer, void* aClosure)
+{
+ NS_ASSERTION(aClosure, "aClosure is null");
+ static_cast<IMEInputHandler*>(aClosure)->ExecutePendingMethods();
+}
+
+// static
+CFArrayRef
+IMEInputHandler::CreateAllIMEModeList()
+{
+ const void* keys[] = { kTISPropertyInputSourceType };
+ const void* values[] = { kTISTypeKeyboardInputMode };
+ CFDictionaryRef filter =
+ ::CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, NULL, NULL);
+ NS_ASSERTION(filter, "failed to create the filter");
+ CFArrayRef list = ::TISCreateInputSourceList(filter, true);
+ ::CFRelease(filter);
+ return list;
+}
+
+// static
+void
+IMEInputHandler::DebugPrintAllIMEModes()
+{
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFArrayRef list = CreateAllIMEModeList();
+ MOZ_LOG(gLog, LogLevel::Info, ("IME mode configuration:"));
+ CFIndex idx = ::CFArrayGetCount(list);
+ TISInputSourceWrapper tis;
+ for (CFIndex i = 0; i < idx; ++i) {
+ TISInputSourceRef inputSource = static_cast<TISInputSourceRef>(
+ const_cast<void *>(::CFArrayGetValueAtIndex(list, i)));
+ tis.InitByTISInputSourceRef(inputSource);
+ nsAutoString name, isid;
+ tis.GetLocalizedName(name);
+ tis.GetInputSourceID(isid);
+ MOZ_LOG(gLog, LogLevel::Info,
+ (" %s\t<%s>%s%s\n",
+ NS_ConvertUTF16toUTF8(name).get(),
+ NS_ConvertUTF16toUTF8(isid).get(),
+ tis.IsASCIICapable() ? "" : "\t(Isn't ASCII capable)",
+ tis.IsEnabled() ? "" : "\t(Isn't Enabled)"));
+ }
+ ::CFRelease(list);
+ }
+}
+
+//static
+TSMDocumentID
+IMEInputHandler::GetCurrentTSMDocumentID()
+{
+ // At least on Mac OS X 10.6.x and 10.7.x, ::TSMGetActiveDocument() has a bug.
+ // The result of ::TSMGetActiveDocument() isn't modified for new active text
+ // input context until [NSTextInputContext currentInputContext] is called.
+ // Therefore, we need to call it here.
+ [NSTextInputContext currentInputContext];
+ return ::TSMGetActiveDocument();
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation #1
+ * The methods are releated to the pending methods. Some jobs should be
+ * run after the stack is finished, e.g, some methods cannot run the jobs
+ * during processing the focus event. And also some other jobs should be
+ * run at the next focus event is processed.
+ * The pending methods are recorded in mPendingMethods. They are executed
+ * by ExecutePendingMethods via FlushPendingMethods.
+ *
+ ******************************************************************************/
+
+NS_IMETHODIMP
+IMEInputHandler::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification)
+{
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ CommitIMEComposition();
+ return NS_OK;
+ case REQUEST_TO_CANCEL_COMPOSITION:
+ CancelIMEComposition();
+ return NS_OK;
+ case NOTIFY_IME_OF_FOCUS:
+ if (IsFocused()) {
+ nsIWidget* widget = aTextEventDispatcher->GetWidget();
+ if (widget && widget->GetInputContext().IsPasswordEditor()) {
+ EnableSecureEventInput();
+ } else {
+ EnsureSecureEventInputDisabled();
+ }
+ }
+ OnFocusChangeInGecko(true);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ OnFocusChangeInGecko(false);
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE:
+ OnSelectionChange(aNotification);
+ return NS_OK;
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP_(void)
+IMEInputHandler::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
+{
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+IMEInputHandler::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress,
+ void* aData)
+{
+ // If the keyboard event is not caused by a native key event, we can do
+ // nothing here.
+ if (!aData) {
+ return;
+ }
+
+ KeyEventState* currentKeyEvent = static_cast<KeyEventState*>(aData);
+ NSEvent* nativeEvent = currentKeyEvent->mKeyEvent;
+ nsAString* insertString = currentKeyEvent->mInsertString;
+ if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
+ TISInputSourceWrapper tis;
+ tis.InitByLayoutID(KeyboardLayoutOverrideRef().mKeyboardLayout, true);
+ tis.WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent);
+ return;
+ }
+ TISInputSourceWrapper::CurrentInputSource().
+ WillDispatchKeyboardEvent(nativeEvent, insertString, aKeyboardEvent);
+}
+
+void
+IMEInputHandler::NotifyIMEOfFocusChangeInGecko()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::NotifyIMEOfFocusChangeInGecko, "
+ "Destroyed()=%s, IsFocused()=%s, inputContext=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ mView ? [mView inputContext] : nullptr));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
+ return;
+ }
+
+ MOZ_ASSERT(mView);
+ NSTextInputContext* inputContext = [mView inputContext];
+ NS_ENSURE_TRUE_VOID(inputContext);
+
+ // When an <input> element on a XUL <panel> element gets focus from an <input>
+ // element on the opener window of the <panel> element, the owner window
+ // still has native focus. Therefore, IMEs may store the opener window's
+ // level at this time because they don't know the actual focus is moved to
+ // different window. If IMEs try to get the newest window level after the
+ // focus change, we return the window level of the XUL <panel>'s widget.
+ // Therefore, let's emulate the native focus change. Then, IMEs can refresh
+ // the stored window level.
+ [inputContext deactivate];
+ [inputContext activate];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::DiscardIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DiscardIMEComposition, "
+ "Destroyed()=%s, IsFocused()=%s, mView=%p, inputContext=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ mView, mView ? [mView inputContext] : nullptr));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kDiscardIMEComposition;
+ return;
+ }
+
+ NS_ENSURE_TRUE_VOID(mView);
+ NSTextInputContext* inputContext = [mView inputContext];
+ NS_ENSURE_TRUE_VOID(inputContext);
+ mIgnoreIMECommit = true;
+ [inputContext discardMarkedText];
+ mIgnoreIMECommit = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void
+IMEInputHandler::SyncASCIICapableOnly()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SyncASCIICapableOnly, "
+ "Destroyed()=%s, IsFocused()=%s, mIsASCIICapableOnly=%s, "
+ "GetCurrentTSMDocumentID()=%p",
+ this, TrueOrFalse(Destroyed()), TrueOrFalse(IsFocused()),
+ TrueOrFalse(mIsASCIICapableOnly), GetCurrentTSMDocumentID()));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (!IsFocused()) {
+ // retry at next focus event
+ mPendingMethods |= kSyncASCIICapableOnly;
+ return;
+ }
+
+ TSMDocumentID doc = GetCurrentTSMDocumentID();
+ if (!doc) {
+ // retry
+ mPendingMethods |= kSyncASCIICapableOnly;
+ NS_WARNING("Application is active but there is no active document");
+ ResetTimer();
+ return;
+ }
+
+ if (mIsASCIICapableOnly) {
+ CFArrayRef ASCIICapableTISList = ::TISCreateASCIICapableInputSourceList();
+ ::TSMSetDocumentProperty(doc,
+ kTSMDocumentEnabledInputSourcesPropertyTag,
+ sizeof(CFArrayRef),
+ &ASCIICapableTISList);
+ ::CFRelease(ASCIICapableTISList);
+ } else {
+ ::TSMRemoveDocumentProperty(doc,
+ kTSMDocumentEnabledInputSourcesPropertyTag);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::ResetTimer()
+{
+ NS_ASSERTION(mPendingMethods != 0,
+ "There are not pending methods, why this is called?");
+ if (mTimer) {
+ mTimer->Cancel();
+ } else {
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ NS_ENSURE_TRUE(mTimer, );
+ }
+ mTimer->InitWithFuncCallback(FlushPendingMethods, this, 0,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+IMEInputHandler::ExecutePendingMethods()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ if (![[NSApplication sharedApplication] isActive]) {
+ mIsInFocusProcessing = false;
+ // If we're not active, we should retry at focus event
+ return;
+ }
+
+ uint32_t pendingMethods = mPendingMethods;
+ // First, reset the pending method flags because if each methods cannot
+ // run now, they can reentry to the pending flags by theirselves.
+ mPendingMethods = 0;
+
+ if (pendingMethods & kDiscardIMEComposition)
+ DiscardIMEComposition();
+ if (pendingMethods & kSyncASCIICapableOnly)
+ SyncASCIICapableOnly();
+ if (pendingMethods & kNotifyIMEOfFocusChangeInGecko) {
+ NotifyIMEOfFocusChangeInGecko();
+ }
+
+ mIsInFocusProcessing = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation (native event handlers)
+ *
+ ******************************************************************************/
+
+TextRangeType
+IMEInputHandler::ConvertToTextRangeType(uint32_t aUnderlineStyle,
+ NSRange& aSelectedRange)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::ConvertToTextRangeType, "
+ "aUnderlineStyle=%llu, aSelectedRange.length=%llu,",
+ this, aUnderlineStyle, aSelectedRange.length));
+
+ // We assume that aUnderlineStyle is NSUnderlineStyleSingle or
+ // NSUnderlineStyleThick. NSUnderlineStyleThick should indicate a selected
+ // clause. Otherwise, should indicate non-selected clause.
+
+ if (aSelectedRange.length == 0) {
+ switch (aUnderlineStyle) {
+ case NSUnderlineStyleSingle:
+ return TextRangeType::eRawClause;
+ case NSUnderlineStyleThick:
+ return TextRangeType::eSelectedRawClause;
+ default:
+ NS_WARNING("Unexpected line style");
+ return TextRangeType::eSelectedRawClause;
+ }
+ }
+
+ switch (aUnderlineStyle) {
+ case NSUnderlineStyleSingle:
+ return TextRangeType::eConvertedClause;
+ case NSUnderlineStyleThick:
+ return TextRangeType::eSelectedClause;
+ default:
+ NS_WARNING("Unexpected line style");
+ return TextRangeType::eSelectedClause;
+ }
+}
+
+uint32_t
+IMEInputHandler::GetRangeCount(NSAttributedString *aAttrString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Iterate through aAttrString for the NSUnderlineStyleAttributeName and
+ // count the different segments adjusting limitRange as we go.
+ uint32_t count = 0;
+ NSRange effectiveRange;
+ NSRange limitRange = NSMakeRange(0, [aAttrString length]);
+ while (limitRange.length > 0) {
+ [aAttrString attribute:NSUnderlineStyleAttributeName
+ atIndex:limitRange.location
+ longestEffectiveRange:&effectiveRange
+ inRange:limitRange];
+ limitRange =
+ NSMakeRange(NSMaxRange(effectiveRange),
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
+ count++;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetRangeCount, aAttrString=\"%s\", count=%llu",
+ this, GetCharacters([aAttrString string]), count));
+
+ return count;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+already_AddRefed<mozilla::TextRangeArray>
+IMEInputHandler::CreateTextRangeArray(NSAttributedString *aAttrString,
+ NSRange& aSelectedRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ RefPtr<mozilla::TextRangeArray> textRangeArray =
+ new mozilla::TextRangeArray();
+
+ // Note that we shouldn't append ranges when composition string
+ // is empty because it may cause TextComposition confused.
+ if (![aAttrString length]) {
+ return textRangeArray.forget();
+ }
+
+ // Convert the Cocoa range into the TextRange Array used in Gecko.
+ // Iterate through the attributed string and map the underline attribute to
+ // Gecko IME textrange attributes. We may need to change the code here if
+ // we change the implementation of validAttributesForMarkedText.
+ NSRange limitRange = NSMakeRange(0, [aAttrString length]);
+ uint32_t rangeCount = GetRangeCount(aAttrString);
+ for (uint32_t i = 0; i < rangeCount && limitRange.length > 0; i++) {
+ NSRange effectiveRange;
+ id attributeValue = [aAttrString attribute:NSUnderlineStyleAttributeName
+ atIndex:limitRange.location
+ longestEffectiveRange:&effectiveRange
+ inRange:limitRange];
+
+ TextRange range;
+ range.mStartOffset = effectiveRange.location;
+ range.mEndOffset = NSMaxRange(effectiveRange);
+ range.mRangeType =
+ ConvertToTextRangeType([attributeValue intValue], aSelectedRange);
+ textRangeArray->AppendElement(range);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CreateTextRangeArray, "
+ "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
+ this, range.mStartOffset, range.mEndOffset,
+ ToChar(range.mRangeType)));
+
+ limitRange =
+ NSMakeRange(NSMaxRange(effectiveRange),
+ NSMaxRange(limitRange) - NSMaxRange(effectiveRange));
+ }
+
+ // Get current caret position.
+ TextRange range;
+ range.mStartOffset = aSelectedRange.location + aSelectedRange.length;
+ range.mEndOffset = range.mStartOffset;
+ range.mRangeType = TextRangeType::eCaret;
+ textRangeArray->AppendElement(range);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CreateTextRangeArray, "
+ "range={ mStartOffset=%llu, mEndOffset=%llu, mRangeType=%s }",
+ this, range.mStartOffset, range.mEndOffset,
+ ToChar(range.mRangeType)));
+
+ return textRangeArray.forget();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+bool
+IMEInputHandler::DispatchCompositionStartEvent()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "mSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, "
+ "mView=%p, mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
+ this, SelectedRange().location, mSelectedRange.length,
+ TrueOrFalse(Destroyed()), mView, mWidget,
+ mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return false;
+ }
+
+ NS_ASSERTION(!mIsIMEComposing, "There is a composition already");
+ mIsIMEComposing = true;
+
+ nsEventStatus status;
+ rv = mDispatcher->StartComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "FAILED, due to StartComposition() failure", this));
+ return false;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionStartEvent, "
+ "destroyed by compositionstart event", this));
+ return false;
+ }
+
+ // FYI: compositionstart may cause committing composition by the webapp.
+ if (!mIsIMEComposing) {
+ return false;
+ }
+
+ // FYI: The selection range might have been modified by a compositionstart
+ // event handler.
+ mIMECompositionStart = SelectedRange().location;
+ return true;
+}
+
+bool
+IMEInputHandler::DispatchCompositionChangeEvent(const nsString& aText,
+ NSAttributedString* aAttrString,
+ NSRange& aSelectedRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "aText=\"%s\", aAttrString=\"%s\", "
+ "aSelectedRange={ location=%llu, length=%llu }, Destroyed()=%s, mView=%p, "
+ "mWidget=%p, inputContext=%p, mIsIMEComposing=%s",
+ this, NS_ConvertUTF16toUTF8(aText).get(),
+ GetCharacters([aAttrString string]),
+ aSelectedRange.location, aSelectedRange.length,
+ TrueOrFalse(Destroyed()), mView, mWidget,
+ mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ NS_ENSURE_TRUE(!Destroyed(), false);
+
+ NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ return false;
+ }
+
+ RefPtr<TextRangeArray> rangeArray =
+ CreateTextRangeArray(aAttrString, aSelectedRange);
+
+ rv = mDispatcher->SetPendingComposition(aText, rangeArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to SetPendingComposition() failure", this));
+ return false;
+ }
+
+ mSelectedRange.location = mIMECompositionStart + aSelectedRange.location;
+ mSelectedRange.length = aSelectedRange.length;
+
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ }
+ mIMECompositionString = [[aAttrString string] retain];
+
+ nsEventStatus status;
+ rv = mDispatcher->FlushPendingComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "FAILED, due to FlushPendingComposition() failure", this));
+ return false;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionChangeEvent, "
+ "destroyed by compositionchange event", this));
+ return false;
+ }
+
+ // FYI: compositionstart may cause committing composition by the webapp.
+ return mIsIMEComposing;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool
+IMEInputHandler::DispatchCompositionCommitEvent(const nsAString* aCommitString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "aCommitString=0x%p (\"%s\"), Destroyed()=%s, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s",
+ this, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : "",
+ TrueOrFalse(Destroyed()), mView, mWidget,
+ mView ? [mView inputContext] : nullptr, TrueOrFalse(mIsIMEComposing)));
+
+ NS_ASSERTION(mIsIMEComposing, "We're not in composition");
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ if (!Destroyed()) {
+ // IME may query selection immediately after this, however, in e10s mode,
+ // OnSelectionChange() will be called asynchronously. Until then, we
+ // should emulate expected selection range if the webapp does nothing.
+ mSelectedRange.location = mIMECompositionStart;
+ if (aCommitString) {
+ mSelectedRange.location += aCommitString->Length();
+ } else if (mIMECompositionString) {
+ nsAutoString commitString;
+ nsCocoaUtils::GetStringForNSString(mIMECompositionString, commitString);
+ mSelectedRange.location += commitString.Length();
+ }
+ mSelectedRange.length = 0;
+
+ nsresult rv = mDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ } else {
+ nsEventStatus status;
+ rv = mDispatcher->CommitComposition(status, aCommitString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gLog, LogLevel::Error,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "FAILED, due to BeginNativeInputTransaction() failure", this));
+ }
+ }
+ }
+
+ mIsIMEComposing = false;
+ mIMECompositionStart = UINT32_MAX;
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ mIMECompositionString = nullptr;
+ }
+
+ if (Destroyed()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::DispatchCompositionCommitEvent, "
+ "destroyed by compositioncommit event", this));
+ return false;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void
+IMEInputHandler::InsertTextAsCommittingComposition(
+ NSAttributedString* aAttrString,
+ NSRange* aReplacementRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "aAttrString=\"%s\", aReplacementRange=%p { location=%llu, length=%llu }, "
+ "Destroyed()=%s, IsIMEComposing()=%s, "
+ "mMarkedRange={ location=%llu, length=%llu }",
+ this, GetCharacters([aAttrString string]), aReplacementRange,
+ aReplacementRange ? aReplacementRange->location : 0,
+ aReplacementRange ? aReplacementRange->length : 0,
+ TrueOrFalse(Destroyed()), TrueOrFalse(IsIMEComposing()),
+ mMarkedRange.location, mMarkedRange.length));
+
+ if (IgnoreIMECommit()) {
+ MOZ_CRASH("IMEInputHandler::InsertTextAsCommittingComposition() must not"
+ "be called while canceling the composition");
+ }
+
+ if (Destroyed()) {
+ return;
+ }
+
+ // First, commit current composition with the latest composition string if the
+ // replacement range is different from marked range.
+ if (IsIMEComposing() && aReplacementRange &&
+ aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
+ if (!DispatchCompositionCommitEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "destroyed by commiting composition for setting replacement range",
+ this));
+ return;
+ }
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ nsString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ if (!IsIMEComposing()) {
+ // If there is no selection and replacement range is specified, set the
+ // range as selection.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+
+ if (!DispatchCompositionStartEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "cannot continue handling composition after compositionstart", this));
+ return;
+ }
+ }
+
+ if (!DispatchCompositionCommitEvent(&str)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::InsertTextAsCommittingComposition, "
+ "destroyed by compositioncommit event", this));
+ return;
+ }
+
+ mMarkedRange = NSMakeRange(NSNotFound, 0);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::SetMarkedText(NSAttributedString* aAttrString,
+ NSRange& aSelectedRange,
+ NSRange* aReplacementRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ KeyEventState* currentKeyEvent = GetCurrentKeyEvent();
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "aAttrString=\"%s\", aSelectedRange={ location=%llu, length=%llu }, "
+ "aReplacementRange=%p { location=%llu, length=%llu }, "
+ "Destroyed()=%s, IgnoreIMEComposition()=%s, IsIMEComposing()=%s, "
+ "mMarkedRange={ location=%llu, length=%llu }, keyevent=%p, "
+ "keydownHandled=%s, keypressDispatched=%s, causedOtherKeyEvents=%s, "
+ "compositionDispatched=%s",
+ this, GetCharacters([aAttrString string]),
+ aSelectedRange.location, aSelectedRange.length, aReplacementRange,
+ aReplacementRange ? aReplacementRange->location : 0,
+ aReplacementRange ? aReplacementRange->length : 0,
+ TrueOrFalse(Destroyed()), TrueOrFalse(IgnoreIMEComposition()),
+ TrueOrFalse(IsIMEComposing()),
+ mMarkedRange.location, mMarkedRange.length,
+ currentKeyEvent ? currentKeyEvent->mKeyEvent : nullptr,
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyDownHandled) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mKeyPressDispatched) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCausedOtherKeyEvents) : "N/A",
+ currentKeyEvent ?
+ TrueOrFalse(currentKeyEvent->mCompositionDispatched) : "N/A"));
+
+ // If SetMarkedText() is called during handling a key press, that means that
+ // the key event caused this composition. So, keypress event shouldn't
+ // be dispatched later, let's mark the key event causing composition event.
+ if (currentKeyEvent) {
+ currentKeyEvent->mCompositionDispatched = true;
+ }
+
+ if (Destroyed() || IgnoreIMEComposition()) {
+ return;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ // First, commit current composition with the latest composition string if the
+ // replacement range is different from marked range.
+ if (IsIMEComposing() && aReplacementRange &&
+ aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(MarkedRange(), *aReplacementRange)) {
+ AutoRestore<bool> ignoreIMECommit(mIgnoreIMECommit);
+ mIgnoreIMECommit = false;
+ if (!DispatchCompositionCommitEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "destroyed by commiting composition for setting replacement range",
+ this));
+ return;
+ }
+ }
+
+ nsString str;
+ nsCocoaUtils::GetStringForNSString([aAttrString string], str);
+
+ mMarkedRange.length = str.Length();
+
+ if (!IsIMEComposing() && !str.IsEmpty()) {
+ // If there is no selection and replacement range is specified, set the
+ // range as selection.
+ if (aReplacementRange && aReplacementRange->location != NSNotFound &&
+ !NSEqualRanges(SelectedRange(), *aReplacementRange)) {
+ NS_ENSURE_TRUE_VOID(SetSelection(*aReplacementRange));
+ }
+
+ mMarkedRange.location = SelectedRange().location;
+
+ if (!DispatchCompositionStartEvent()) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+ "composition after dispatching compositionstart", this));
+ return;
+ }
+ }
+
+ if (!str.IsEmpty()) {
+ if (!DispatchCompositionChangeEvent(str, aAttrString, aSelectedRange)) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, cannot continue handling "
+ "composition after dispatching compositionchange", this));
+ }
+ return;
+ }
+
+ // If the composition string becomes empty string, we should commit
+ // current composition.
+ if (!DispatchCompositionCommitEvent(&EmptyString())) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SetMarkedText, "
+ "destroyed by compositioncommit event", this));
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NSAttributedString*
+IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange,
+ NSRange* aActualRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "aRange={ location=%llu, length=%llu }, aActualRange=%p, Destroyed()=%s",
+ this, aRange.location, aRange.length, aActualRange,
+ TrueOrFalse(Destroyed())));
+
+ if (aActualRange) {
+ *aActualRange = NSMakeRange(NSNotFound, 0);
+ }
+
+ if (Destroyed() || aRange.location == NSNotFound || aRange.length == 0) {
+ return nil;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ // If we're in composing, the queried range may be in the composition string.
+ // In such case, we should use mIMECompositionString since if the composition
+ // string is handled by a remote process, the content cache may be out of
+ // date.
+ // XXX Should we set composition string attributes? Although, Blink claims
+ // that some attributes of marked text are supported, but they return
+ // just marked string without any style. So, let's keep current behavior
+ // at least for now.
+ NSUInteger compositionLength =
+ mIMECompositionString ? [mIMECompositionString length] : 0;
+ if (mIMECompositionStart != UINT32_MAX &&
+ mIMECompositionStart >= aRange.location &&
+ mIMECompositionStart + compositionLength <=
+ aRange.location + aRange.length) {
+ NSRange range =
+ NSMakeRange(aRange.location - mIMECompositionStart, aRange.length);
+ NSString* nsstr = [mIMECompositionString substringWithRange:range];
+ NSMutableAttributedString* result =
+ [[[NSMutableAttributedString alloc] initWithString:nsstr
+ attributes:nil] autorelease];
+ // XXX We cannot return font information in this case. However, this
+ // case must occur only when IME tries to confirm if composing string
+ // is handled as expected.
+ if (aActualRange) {
+ *aActualRange = aRange;
+ }
+
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ nsAutoString str;
+ nsCocoaUtils::GetStringForNSString(nsstr, str);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "computed with mIMECompositionString (result string=\"%s\")",
+ this, NS_ConvertUTF16toUTF8(str).get()));
+ }
+ return result;
+ }
+
+ nsAutoString str;
+ WidgetQueryContentEvent textContent(true, eQueryTextContent, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ textContent.InitForQueryTextContent(startOffset, aRange.length, options);
+ textContent.RequestFontRanges();
+ DispatchEvent(textContent);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetAttributedSubstringFromRange, "
+ "textContent={ mSucceeded=%s, mReply={ mString=\"%s\", mOffset=%u } }",
+ this, TrueOrFalse(textContent.mSucceeded),
+ NS_ConvertUTF16toUTF8(textContent.mReply.mString).get(),
+ textContent.mReply.mOffset));
+
+ if (!textContent.mSucceeded) {
+ return nil;
+ }
+
+ // We don't set vertical information at this point. If required,
+ // OS will calls drawsVerticallyForCharacterAtIndex.
+ NSMutableAttributedString* result =
+ nsCocoaUtils::GetNSMutableAttributedString(textContent.mReply.mString,
+ textContent.mReply.mFontRanges,
+ false,
+ mWidget->BackingScaleFactor());
+ if (aActualRange) {
+ aActualRange->location = textContent.mReply.mOffset;
+ aActualRange->length = textContent.mReply.mString.Length();
+ }
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool
+IMEInputHandler::HasMarkedText()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::HasMarkedText, "
+ "mMarkedRange={ location=%llu, length=%llu }",
+ this, mMarkedRange.location, mMarkedRange.length));
+
+ return (mMarkedRange.location != NSNotFound) && (mMarkedRange.length != 0);
+}
+
+NSRange
+IMEInputHandler::MarkedRange()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::MarkedRange, "
+ "mMarkedRange={ location=%llu, length=%llu }",
+ this, mMarkedRange.location, mMarkedRange.length));
+
+ if (!HasMarkedText()) {
+ return NSMakeRange(NSNotFound, 0);
+ }
+ return mMarkedRange;
+}
+
+NSRange
+IMEInputHandler::SelectedRange()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SelectedRange, Destroyed()=%s, mSelectedRange={ "
+ "location=%llu, length=%llu }",
+ this, TrueOrFalse(Destroyed()), mSelectedRange.location,
+ mSelectedRange.length));
+
+ if (Destroyed()) {
+ return mSelectedRange;
+ }
+
+ if (mSelectedRange.location != NSNotFound) {
+ MOZ_ASSERT(mIMEHasFocus);
+ return mSelectedRange;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ WidgetQueryContentEvent selection(true, eQuerySelectedText, mWidget);
+ DispatchEvent(selection);
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SelectedRange, selection={ mSucceeded=%s, "
+ "mReply={ mOffset=%u, mString.Length()=%u } }",
+ this, TrueOrFalse(selection.mSucceeded), selection.mReply.mOffset,
+ selection.mReply.mString.Length()));
+
+ if (!selection.mSucceeded) {
+ return mSelectedRange;
+ }
+
+ mWritingMode = selection.GetWritingMode();
+ mRangeForWritingMode = NSMakeRange(selection.mReply.mOffset,
+ selection.mReply.mString.Length());
+
+ if (mIMEHasFocus) {
+ mSelectedRange = mRangeForWritingMode;
+ }
+
+ return mRangeForWritingMode;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(mSelectedRange);
+}
+
+bool
+IMEInputHandler::DrawsVerticallyForCharacterAtIndex(uint32_t aCharIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (Destroyed()) {
+ return false;
+ }
+
+ if (mRangeForWritingMode.location == NSNotFound) {
+ // Update cached writing-mode value for the current selection.
+ SelectedRange();
+ }
+
+ if (aCharIndex < mRangeForWritingMode.location ||
+ aCharIndex > mRangeForWritingMode.location + mRangeForWritingMode.length) {
+ // It's not clear to me whether this ever happens in practice, but if an
+ // IME ever wants to query writing mode at an offset outside the current
+ // selection, the writing-mode value may not be correct for the index.
+ // In that case, use FirstRectForCharacterRange to get a fresh value.
+ // This does more work than strictly necessary (we don't need the rect here),
+ // but should be a rare case.
+ NS_WARNING("DrawsVerticallyForCharacterAtIndex not using cached writing mode");
+ NSRange range = NSMakeRange(aCharIndex, 1);
+ NSRange actualRange;
+ FirstRectForCharacterRange(range, &actualRange);
+ }
+
+ return mWritingMode.IsVertical();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+NSRect
+IMEInputHandler::FirstRectForCharacterRange(NSRange& aRange,
+ NSRange* aActualRange)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::FirstRectForCharacterRange, Destroyed()=%s, "
+ "aRange={ location=%llu, length=%llu }, aActualRange=%p }",
+ this, TrueOrFalse(Destroyed()), aRange.location, aRange.length,
+ aActualRange));
+
+ // XXX this returns first character rect or caret rect, it is limitation of
+ // now. We need more work for returns first line rect. But current
+ // implementation is enough for IMEs.
+
+ NSRect rect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
+ NSRange actualRange = NSMakeRange(NSNotFound, 0);
+ if (aActualRange) {
+ *aActualRange = actualRange;
+ }
+ if (Destroyed() || aRange.location == NSNotFound) {
+ return rect;
+ }
+
+ RefPtr<IMEInputHandler> kungFuDeathGrip(this);
+
+ LayoutDeviceIntRect r;
+ bool useCaretRect = (aRange.length == 0);
+ if (!useCaretRect) {
+ WidgetQueryContentEvent charRect(true, eQueryTextRect, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ charRect.InitForQueryTextRect(startOffset, 1, options);
+ DispatchEvent(charRect);
+ if (charRect.mSucceeded) {
+ r = charRect.mReply.mRect;
+ actualRange.location = charRect.mReply.mOffset;
+ actualRange.length = charRect.mReply.mString.Length();
+ mWritingMode = charRect.GetWritingMode();
+ mRangeForWritingMode = actualRange;
+ } else {
+ useCaretRect = true;
+ }
+ }
+
+ if (useCaretRect) {
+ WidgetQueryContentEvent caretRect(true, eQueryCaretRect, mWidget);
+ WidgetQueryContentEvent::Options options;
+ int64_t startOffset = aRange.location;
+ if (IsIMEComposing()) {
+ // The composition may be at different offset from the selection start
+ // offset at dispatching compositionstart because start of composition
+ // is fixed when composition string becomes non-empty in the editor.
+ // Therefore, we need to use query event which is relative to insertion
+ // point.
+ options.mRelativeToInsertionPoint = true;
+ startOffset -= mIMECompositionStart;
+ }
+ caretRect.InitForQueryCaretRect(startOffset, options);
+ DispatchEvent(caretRect);
+ if (!caretRect.mSucceeded) {
+ return rect;
+ }
+ r = caretRect.mReply.mRect;
+ r.width = 0;
+ actualRange.location = caretRect.mReply.mOffset;
+ actualRange.length = 0;
+ }
+
+ nsIWidget* rootWidget = mWidget->GetTopLevelWidget();
+ NSWindow* rootWindow =
+ static_cast<NSWindow*>(rootWidget->GetNativeData(NS_NATIVE_WINDOW));
+ NSView* rootView =
+ static_cast<NSView*>(rootWidget->GetNativeData(NS_NATIVE_WIDGET));
+ if (!rootWindow || !rootView) {
+ return rect;
+ }
+ rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, mWidget->BackingScaleFactor());
+ rect = [rootView convertRect:rect toView:nil];
+ rect.origin = nsCocoaUtils::ConvertPointToScreen(rootWindow, rect.origin);
+
+ if (aActualRange) {
+ *aActualRange = actualRange;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::FirstRectForCharacterRange, "
+ "useCaretRect=%s rect={ x=%f, y=%f, width=%f, height=%f }, "
+ "actualRange={ location=%llu, length=%llu }",
+ this, TrueOrFalse(useCaretRect), rect.origin.x, rect.origin.y,
+ rect.size.width, rect.size.height, actualRange.location,
+ actualRange.length));
+
+ return rect;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
+}
+
+NSUInteger
+IMEInputHandler::CharacterIndexForPoint(NSPoint& aPoint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CharacterIndexForPoint, aPoint={ x=%f, y=%f }",
+ this, aPoint.x, aPoint.y));
+
+ NSWindow* mainWindow = [NSApp mainWindow];
+ if (!mWidget || !mainWindow) {
+ return NSNotFound;
+ }
+
+ WidgetQueryContentEvent charAt(true, eQueryCharacterAtPoint, mWidget);
+ NSPoint ptInWindow = nsCocoaUtils::ConvertPointFromScreen(mainWindow, aPoint);
+ NSPoint ptInView = [mView convertPoint:ptInWindow fromView:nil];
+ charAt.mRefPoint.x =
+ static_cast<int32_t>(ptInView.x) * mWidget->BackingScaleFactor();
+ charAt.mRefPoint.y =
+ static_cast<int32_t>(ptInView.y) * mWidget->BackingScaleFactor();
+ mWidget->DispatchWindowEvent(charAt);
+ if (!charAt.mSucceeded ||
+ charAt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND ||
+ charAt.mReply.mOffset >= static_cast<uint32_t>(NSNotFound)) {
+ return NSNotFound;
+ }
+
+ return charAt.mReply.mOffset;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNotFound);
+}
+
+extern "C" {
+extern NSString *NSTextInputReplacementRangeAttributeName;
+}
+
+NSArray*
+IMEInputHandler::GetValidAttributesForMarkedText()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::GetValidAttributesForMarkedText", this));
+
+ // Return same attributes as Chromium (see render_widget_host_view_mac.mm)
+ // because most IMEs must be tested with Safari (OS default) and Chrome
+ // (having most market share). Therefore, we need to follow their behavior.
+ // XXX It might be better to reuse an array instance for this result because
+ // this may be called a lot. Note that Chromium does so.
+ return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName,
+ NSUnderlineColorAttributeName,
+ NSMarkedClauseSegmentAttributeName,
+ NSTextInputReplacementRangeAttributeName,
+ nil];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * IMEInputHandler implementation #2
+ *
+ ******************************************************************************/
+
+IMEInputHandler::IMEInputHandler(nsChildView* aWidget,
+ NSView<mozView> *aNativeView)
+ : TextInputHandlerBase(aWidget, aNativeView)
+ , mPendingMethods(0)
+ , mIMECompositionString(nullptr)
+ , mIMECompositionStart(UINT32_MAX)
+ , mIsIMEComposing(false)
+ , mIsIMEEnabled(true)
+ , mIsASCIICapableOnly(false)
+ , mIgnoreIMECommit(false)
+ , mIsInFocusProcessing(false)
+ , mIMEHasFocus(false)
+{
+ InitStaticMembers();
+
+ mMarkedRange.location = NSNotFound;
+ mMarkedRange.length = 0;
+ mSelectedRange.location = NSNotFound;
+ mSelectedRange.length = 0;
+}
+
+IMEInputHandler::~IMEInputHandler()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ if (sFocusedIMEHandler == this) {
+ sFocusedIMEHandler = nullptr;
+ }
+ if (mIMECompositionString) {
+ [mIMECompositionString release];
+ mIMECompositionString = nullptr;
+ }
+}
+
+void
+IMEInputHandler::OnFocusChangeInGecko(bool aFocus)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnFocusChangeInGecko, aFocus=%s, Destroyed()=%s, "
+ "sFocusedIMEHandler=%p",
+ this, TrueOrFalse(aFocus), TrueOrFalse(Destroyed()), sFocusedIMEHandler));
+
+ mSelectedRange.location = NSNotFound; // Marking dirty
+ mIMEHasFocus = aFocus;
+
+ // This is called when the native focus is changed and when the native focus
+ // isn't changed but the focus is changed in Gecko.
+ if (!aFocus) {
+ if (sFocusedIMEHandler == this)
+ sFocusedIMEHandler = nullptr;
+ return;
+ }
+
+ sFocusedIMEHandler = this;
+ mIsInFocusProcessing = true;
+
+ // We need to notify IME of focus change in Gecko as native focus change
+ // because the window level of the focused element in Gecko may be changed.
+ mPendingMethods |= kNotifyIMEOfFocusChangeInGecko;
+ ResetTimer();
+}
+
+bool
+IMEInputHandler::OnDestroyWidget(nsChildView* aDestroyingWidget)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnDestroyWidget, aDestroyingWidget=%p, "
+ "sFocusedIMEHandler=%p, IsIMEComposing()=%s",
+ this, aDestroyingWidget, sFocusedIMEHandler,
+ TrueOrFalse(IsIMEComposing())));
+
+ // If we're not focused, the focused IMEInputHandler may have been
+ // created by another widget/nsChildView.
+ if (sFocusedIMEHandler && sFocusedIMEHandler != this) {
+ sFocusedIMEHandler->OnDestroyWidget(aDestroyingWidget);
+ }
+
+ if (!TextInputHandlerBase::OnDestroyWidget(aDestroyingWidget)) {
+ return false;
+ }
+
+ if (IsIMEComposing()) {
+ // If our view is in the composition, we should clean up it.
+ CancelIMEComposition();
+ }
+
+ mSelectedRange.location = NSNotFound; // Marking dirty
+ mIMEHasFocus = false;
+
+ return true;
+}
+
+void
+IMEInputHandler::SendCommittedText(NSString *aString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SendCommittedText, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s",
+ this, mView, mWidget, mView ? [mView inputContext] : nullptr,
+ TrueOrFalse(mIsIMEComposing), mWidget));
+
+ NS_ENSURE_TRUE(mWidget, );
+ // XXX We should send the string without mView.
+ if (!mView) {
+ return;
+ }
+
+ NSAttributedString* attrStr =
+ [[NSAttributedString alloc] initWithString:aString];
+ if ([mView conformsToProtocol:@protocol(NSTextInputClient)]) {
+ NSObject<NSTextInputClient>* textInputClient =
+ static_cast<NSObject<NSTextInputClient>*>(mView);
+ [textInputClient insertText:attrStr
+ replacementRange:NSMakeRange(NSNotFound, 0)];
+ }
+
+ // Last resort. If we cannot retrieve NSTextInputProtocol from mView
+ // or blocking to call our InsertText(), we should call InsertText()
+ // directly to commit composition forcibly.
+ if (mIsIMEComposing) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::SendCommittedText, trying to insert text directly "
+ "due to IME not calling our InsertText()", this));
+ static_cast<TextInputHandler*>(this)->InsertText(attrStr);
+ MOZ_ASSERT(!mIsIMEComposing);
+ }
+
+ [attrStr release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::KillIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::KillIMEComposition, mView=%p, mWidget=%p, "
+ "inputContext=%p, mIsIMEComposing=%s, "
+ "Destroyed()=%s, IsFocused()=%s",
+ this, mView, mWidget, mView ? [mView inputContext] : nullptr,
+ TrueOrFalse(mIsIMEComposing), TrueOrFalse(Destroyed()),
+ TrueOrFalse(IsFocused())));
+
+ if (Destroyed()) {
+ return;
+ }
+
+ if (IsFocused()) {
+ NS_ENSURE_TRUE_VOID(mView);
+ NSTextInputContext* inputContext = [mView inputContext];
+ NS_ENSURE_TRUE_VOID(inputContext);
+ [inputContext discardMarkedText];
+ return;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::KillIMEComposition, Pending...", this));
+
+ // Commit the composition internally.
+ SendCommittedText(mIMECompositionString);
+ NS_ASSERTION(!mIsIMEComposing, "We're still in a composition");
+ // The pending method will be fired by the next focus event.
+ mPendingMethods |= kDiscardIMEComposition;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::CommitIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!IsIMEComposing())
+ return;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CommitIMEComposition, mIMECompositionString=%s",
+ this, GetCharacters(mIMECompositionString)));
+
+ KillIMEComposition();
+
+ if (!IsIMEComposing())
+ return;
+
+ // If the composition is still there, KillIMEComposition only kills the
+ // composition in TSM. We also need to finish the our composition too.
+ SendCommittedText(mIMECompositionString);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+IMEInputHandler::CancelIMEComposition()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!IsIMEComposing())
+ return;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::CancelIMEComposition, mIMECompositionString=%s",
+ this, GetCharacters(mIMECompositionString)));
+
+ // For canceling the current composing, we need to ignore the param of
+ // insertText. But this code is ugly...
+ mIgnoreIMECommit = true;
+ KillIMEComposition();
+ mIgnoreIMECommit = false;
+
+ if (!IsIMEComposing())
+ return;
+
+ // If the composition is still there, KillIMEComposition only kills the
+ // composition in TSM. We also need to kill the our composition too.
+ SendCommittedText(@"");
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool
+IMEInputHandler::IsFocused()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(!Destroyed(), false);
+ NSWindow* window = [mView window];
+ NS_ENSURE_TRUE(window, false);
+ return [window firstResponder] == mView &&
+ [window isKeyWindow] &&
+ [[NSApplication sharedApplication] isActive];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool
+IMEInputHandler::IsIMEOpened()
+{
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentInputSource();
+ return tis.IsOpenedIMEMode();
+}
+
+void
+IMEInputHandler::SetASCIICapableOnly(bool aASCIICapableOnly)
+{
+ if (aASCIICapableOnly == mIsASCIICapableOnly)
+ return;
+
+ CommitIMEComposition();
+ mIsASCIICapableOnly = aASCIICapableOnly;
+ SyncASCIICapableOnly();
+}
+
+void
+IMEInputHandler::EnableIME(bool aEnableIME)
+{
+ if (aEnableIME == mIsIMEEnabled)
+ return;
+
+ CommitIMEComposition();
+ mIsIMEEnabled = aEnableIME;
+}
+
+void
+IMEInputHandler::SetIMEOpenState(bool aOpenIME)
+{
+ if (!IsFocused() || IsIMEOpened() == aOpenIME)
+ return;
+
+ if (!aOpenIME) {
+ TISInputSourceWrapper tis;
+ tis.InitByCurrentASCIICapableInputSource();
+ tis.Select();
+ return;
+ }
+
+ // If we know the latest IME opened mode, we should select it.
+ if (sLatestIMEOpenedModeInputSourceID) {
+ TISInputSourceWrapper tis;
+ tis.InitByInputSourceID(sLatestIMEOpenedModeInputSourceID);
+ tis.Select();
+ return;
+ }
+
+ // XXX If the current input source is a mode of IME, we should turn on it,
+ // but we haven't found such way...
+
+ // Finally, we should refer the system locale but this is a little expensive,
+ // we shouldn't retry this (if it was succeeded, we already set
+ // sLatestIMEOpenedModeInputSourceID at that time).
+ static bool sIsPrefferredIMESearched = false;
+ if (sIsPrefferredIMESearched)
+ return;
+ sIsPrefferredIMESearched = true;
+ OpenSystemPreferredLanguageIME();
+}
+
+void
+IMEInputHandler::OpenSystemPreferredLanguageIME()
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME", this));
+
+ CFArrayRef langList = ::CFLocaleCopyPreferredLanguages();
+ if (!langList) {
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, langList is NULL",
+ this));
+ return;
+ }
+ CFIndex count = ::CFArrayGetCount(langList);
+ for (CFIndex i = 0; i < count; i++) {
+ CFLocaleRef locale =
+ ::CFLocaleCreate(kCFAllocatorDefault,
+ static_cast<CFStringRef>(::CFArrayGetValueAtIndex(langList, i)));
+ if (!locale) {
+ continue;
+ }
+
+ bool changed = false;
+ CFStringRef lang = static_cast<CFStringRef>(
+ ::CFLocaleGetValue(locale, kCFLocaleLanguageCode));
+ NS_ASSERTION(lang, "lang is null");
+ if (lang) {
+ TISInputSourceWrapper tis;
+ tis.InitByLanguage(lang);
+ if (tis.IsOpenedIMEMode()) {
+ if (MOZ_LOG_TEST(gLog, LogLevel::Info)) {
+ CFStringRef foundTIS;
+ tis.GetInputSourceID(foundTIS);
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OpenSystemPreferredLanguageIME, "
+ "foundTIS=%s, lang=%s",
+ this, GetCharacters(foundTIS), GetCharacters(lang)));
+ }
+ tis.Select();
+ changed = true;
+ }
+ }
+ ::CFRelease(locale);
+ if (changed) {
+ break;
+ }
+ }
+ ::CFRelease(langList);
+}
+
+void
+IMEInputHandler::OnSelectionChange(const IMENotification& aIMENotification)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p IMEInputHandler::OnSelectionChange", this));
+
+ if (aIMENotification.mSelectionChangeData.mOffset == UINT32_MAX) {
+ mSelectedRange.location = NSNotFound;
+ mSelectedRange.length = 0;
+ mRangeForWritingMode.location = NSNotFound;
+ mRangeForWritingMode.length = 0;
+ return;
+ }
+
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+ mRangeForWritingMode =
+ NSMakeRange(aIMENotification.mSelectionChangeData.mOffset,
+ aIMENotification.mSelectionChangeData.Length());
+ if (mIMEHasFocus) {
+ mSelectedRange = mRangeForWritingMode;
+ }
+}
+
+bool
+IMEInputHandler::OnHandleEvent(NSEvent* aEvent)
+{
+ if (!IsFocused()) {
+ return false;
+ }
+ NSTextInputContext* inputContext = [mView inputContext];
+ return [inputContext handleEvent:aEvent];
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase implementation
+ *
+ ******************************************************************************/
+
+int32_t TextInputHandlerBase::sSecureEventInputCount = 0;
+
+NS_IMPL_ISUPPORTS(TextInputHandlerBase,
+ TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+TextInputHandlerBase::TextInputHandlerBase(nsChildView* aWidget,
+ NSView<mozView> *aNativeView)
+ : mWidget(aWidget)
+ , mDispatcher(aWidget->GetTextEventDispatcher())
+{
+ gHandlerInstanceCount++;
+ mView = [aNativeView retain];
+}
+
+TextInputHandlerBase::~TextInputHandlerBase()
+{
+ [mView release];
+ if (--gHandlerInstanceCount == 0) {
+ TISInputSourceWrapper::Shutdown();
+ }
+}
+
+bool
+TextInputHandlerBase::OnDestroyWidget(nsChildView* aDestroyingWidget)
+{
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::OnDestroyWidget, "
+ "aDestroyingWidget=%p, mWidget=%p",
+ this, aDestroyingWidget, mWidget));
+
+ if (aDestroyingWidget != mWidget) {
+ return false;
+ }
+
+ mWidget = nullptr;
+ mDispatcher = nullptr;
+ return true;
+}
+
+bool
+TextInputHandlerBase::DispatchEvent(WidgetGUIEvent& aEvent)
+{
+ return mWidget->DispatchWindowEvent(aEvent);
+}
+
+void
+TextInputHandlerBase::InitKeyEvent(NSEvent *aNativeKeyEvent,
+ WidgetKeyboardEvent& aKeyEvent,
+ const nsAString* aInsertString)
+{
+ NS_ASSERTION(aNativeKeyEvent, "aNativeKeyEvent must not be NULL");
+
+ if (mKeyboardOverride.mOverrideEnabled) {
+ TISInputSourceWrapper tis;
+ tis.InitByLayoutID(mKeyboardOverride.mKeyboardLayout, true);
+ tis.InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
+ return;
+ }
+ TISInputSourceWrapper::CurrentInputSource().
+ InitKeyEvent(aNativeKeyEvent, aKeyEvent, aInsertString);
+}
+
+nsresult
+TextInputHandlerBase::SynthesizeNativeKeyEvent(
+ int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ static const uint32_t sModifierFlagMap[][2] = {
+ { nsIWidget::CAPS_LOCK, NSAlphaShiftKeyMask },
+ { nsIWidget::SHIFT_L, NSShiftKeyMask | 0x0002 },
+ { nsIWidget::SHIFT_R, NSShiftKeyMask | 0x0004 },
+ { nsIWidget::CTRL_L, NSControlKeyMask | 0x0001 },
+ { nsIWidget::CTRL_R, NSControlKeyMask | 0x2000 },
+ { nsIWidget::ALT_L, NSAlternateKeyMask | 0x0020 },
+ { nsIWidget::ALT_R, NSAlternateKeyMask | 0x0040 },
+ { nsIWidget::COMMAND_L, NSCommandKeyMask | 0x0008 },
+ { nsIWidget::COMMAND_R, NSCommandKeyMask | 0x0010 },
+ { nsIWidget::NUMERIC_KEY_PAD, NSNumericPadKeyMask },
+ { nsIWidget::HELP, NSHelpKeyMask },
+ { nsIWidget::FUNCTION, NSFunctionKeyMask }
+ };
+
+ uint32_t modifierFlags = 0;
+ for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
+ if (aModifierFlags & sModifierFlagMap[i][0]) {
+ modifierFlags |= sModifierFlagMap[i][1];
+ }
+ }
+
+ NSInteger windowNumber = [[mView window] windowNumber];
+ bool sendFlagsChangedEvent = IsModifierKey(aNativeKeyCode);
+ NSEventType eventType = sendFlagsChangedEvent ? NSFlagsChanged : NSKeyDown;
+ NSEvent* downEvent =
+ [NSEvent keyEventWithType:eventType
+ location:NSMakePoint(0,0)
+ modifierFlags:modifierFlags
+ timestamp:0
+ windowNumber:windowNumber
+ context:[NSGraphicsContext currentContext]
+ characters:nsCocoaUtils::ToNSString(aCharacters)
+ charactersIgnoringModifiers:nsCocoaUtils::ToNSString(aUnmodifiedCharacters)
+ isARepeat:NO
+ keyCode:aNativeKeyCode];
+
+ NSEvent* upEvent = sendFlagsChangedEvent ?
+ nil : nsCocoaUtils::MakeNewCocoaEventWithType(NSKeyUp, downEvent);
+
+ if (downEvent && (sendFlagsChangedEvent || upEvent)) {
+ KeyboardLayoutOverride currentLayout = mKeyboardOverride;
+ mKeyboardOverride.mKeyboardLayout = aNativeKeyboardLayout;
+ mKeyboardOverride.mOverrideEnabled = true;
+ [NSApp sendEvent:downEvent];
+ if (upEvent) {
+ [NSApp sendEvent:upEvent];
+ }
+ // processKeyDownEvent and keyUp block exceptions so we're sure to
+ // reach here to restore mKeyboardOverride
+ mKeyboardOverride = currentLayout;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NSInteger
+TextInputHandlerBase::GetWindowLevel()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::GetWindowLevel, Destryoed()=%s",
+ this, TrueOrFalse(Destroyed())));
+
+ if (Destroyed()) {
+ return NSNormalWindowLevel;
+ }
+
+ // When an <input> element on a XUL <panel> is focused, the actual focused view
+ // is the panel's parent view (mView). But the editor is displayed on the
+ // popped-up widget's view (editorView). We want the latter's window level.
+ NSView<mozView>* editorView = mWidget->GetEditorView();
+ NS_ENSURE_TRUE(editorView, NSNormalWindowLevel);
+ NSInteger windowLevel = [[editorView window] level];
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::GetWindowLevel, windowLevel=%s (%X)",
+ this, GetWindowLevelName(windowLevel), windowLevel));
+
+ return windowLevel;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
+}
+
+NS_IMETHODIMP
+TextInputHandlerBase::AttachNativeKeyEvent(WidgetKeyboardEvent& aKeyEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Don't try to replace a native event if one already exists.
+ // OS X doesn't have an OS modifier, can't make a native event.
+ if (aKeyEvent.mNativeKeyEvent || aKeyEvent.mModifiers & MODIFIER_OS) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Info,
+ ("%p TextInputHandlerBase::AttachNativeKeyEvent, key=0x%X, char=0x%X, "
+ "mod=0x%X", this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode,
+ aKeyEvent.mModifiers));
+
+ NSEventType eventType;
+ if (aKeyEvent.mMessage == eKeyUp) {
+ eventType = NSKeyUp;
+ } else {
+ eventType = NSKeyDown;
+ }
+
+ static const uint32_t sModifierFlagMap[][2] = {
+ { MODIFIER_SHIFT, NSShiftKeyMask },
+ { MODIFIER_CONTROL, NSControlKeyMask },
+ { MODIFIER_ALT, NSAlternateKeyMask },
+ { MODIFIER_ALTGRAPH, NSAlternateKeyMask },
+ { MODIFIER_META, NSCommandKeyMask },
+ { MODIFIER_CAPSLOCK, NSAlphaShiftKeyMask },
+ { MODIFIER_NUMLOCK, NSNumericPadKeyMask }
+ };
+
+ NSUInteger modifierFlags = 0;
+ for (uint32_t i = 0; i < ArrayLength(sModifierFlagMap); ++i) {
+ if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) {
+ modifierFlags |= sModifierFlagMap[i][1];
+ }
+ }
+
+ NSInteger windowNumber = [[mView window] windowNumber];
+
+ NSString* characters;
+ if (aKeyEvent.mCharCode) {
+ characters = [NSString stringWithCharacters:
+ reinterpret_cast<const unichar*>(&(aKeyEvent.mCharCode)) length:1];
+ } else {
+ uint32_t cocoaCharCode =
+ nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode);
+ characters = [NSString stringWithCharacters:
+ reinterpret_cast<const unichar*>(&cocoaCharCode) length:1];
+ }
+
+ aKeyEvent.mNativeKeyEvent =
+ [NSEvent keyEventWithType:eventType
+ location:NSMakePoint(0,0)
+ modifierFlags:modifierFlags
+ timestamp:0
+ windowNumber:windowNumber
+ context:[NSGraphicsContext currentContext]
+ characters:characters
+ charactersIgnoringModifiers:characters
+ isARepeat:NO
+ keyCode:0]; // Native key code not currently needed
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+TextInputHandlerBase::SetSelection(NSRange& aRange)
+{
+ MOZ_ASSERT(!Destroyed());
+
+ RefPtr<TextInputHandlerBase> kungFuDeathGrip(this);
+ WidgetSelectionEvent selectionEvent(true, eSetSelection, mWidget);
+ selectionEvent.mOffset = aRange.location;
+ selectionEvent.mLength = aRange.length;
+ selectionEvent.mReversed = false;
+ selectionEvent.mExpandToClusterBoundary = false;
+ DispatchEvent(selectionEvent);
+ NS_ENSURE_TRUE(selectionEvent.mSucceeded, false);
+ return !Destroyed();
+}
+
+/* static */ bool
+TextInputHandlerBase::IsPrintableChar(char16_t aChar)
+{
+ return (aChar >= 0x20 && aChar <= 0x7E) || aChar >= 0xA0;
+}
+
+
+/* static */ bool
+TextInputHandlerBase::IsSpecialGeckoKey(UInt32 aNativeKeyCode)
+{
+ // this table is used to determine which keys are special and should not
+ // generate a charCode
+ switch (aNativeKeyCode) {
+ // modifiers - we don't get separate events for these yet
+ case kVK_Escape:
+ case kVK_Shift:
+ case kVK_RightShift:
+ case kVK_Command:
+ case kVK_RightCommand:
+ case kVK_CapsLock:
+ case kVK_Control:
+ case kVK_RightControl:
+ case kVK_Option:
+ case kVK_RightOption:
+ case kVK_ANSI_KeypadClear:
+ case kVK_Function:
+
+ // function keys
+ case kVK_F1:
+ case kVK_F2:
+ case kVK_F3:
+ case kVK_F4:
+ case kVK_F5:
+ case kVK_F6:
+ case kVK_F7:
+ case kVK_F8:
+ case kVK_F9:
+ case kVK_F10:
+ case kVK_F11:
+ case kVK_F12:
+ case kVK_PC_Pause:
+ case kVK_PC_ScrollLock:
+ case kVK_PC_PrintScreen:
+ case kVK_F16:
+ case kVK_F17:
+ case kVK_F18:
+ case kVK_F19:
+
+ case kVK_PC_Insert:
+ case kVK_PC_Delete:
+ case kVK_Tab:
+ case kVK_PC_Backspace:
+ case kVK_PC_ContextMenu:
+
+ case kVK_JIS_Eisu:
+ case kVK_JIS_Kana:
+
+ case kVK_Home:
+ case kVK_End:
+ case kVK_PageUp:
+ case kVK_PageDown:
+ case kVK_LeftArrow:
+ case kVK_RightArrow:
+ case kVK_UpArrow:
+ case kVK_DownArrow:
+ case kVK_Return:
+ case kVK_ANSI_KeypadEnter:
+ case kVK_Powerbook_KeypadEnter:
+ return true;
+ }
+ return false;
+}
+
+/* static */ bool
+TextInputHandlerBase::IsNormalCharInputtingEvent(
+ const WidgetKeyboardEvent& aKeyEvent)
+{
+ // this is not character inputting event, simply.
+ if (aKeyEvent.mNativeCharacters.IsEmpty() ||
+ aKeyEvent.IsMeta()) {
+ return false;
+ }
+ return !IsControlChar(aKeyEvent.mNativeCharacters[0]);
+}
+
+/* static */ bool
+TextInputHandlerBase::IsModifierKey(UInt32 aNativeKeyCode)
+{
+ switch (aNativeKeyCode) {
+ case kVK_CapsLock:
+ case kVK_RightCommand:
+ case kVK_Command:
+ case kVK_Shift:
+ case kVK_Option:
+ case kVK_Control:
+ case kVK_RightShift:
+ case kVK_RightOption:
+ case kVK_RightControl:
+ case kVK_Function:
+ return true;
+ }
+ return false;
+}
+
+/* static */ void
+TextInputHandlerBase::EnableSecureEventInput()
+{
+ sSecureEventInputCount++;
+ ::EnableSecureEventInput();
+}
+
+/* static */ void
+TextInputHandlerBase::DisableSecureEventInput()
+{
+ if (!sSecureEventInputCount) {
+ return;
+ }
+ sSecureEventInputCount--;
+ ::DisableSecureEventInput();
+}
+
+/* static */ bool
+TextInputHandlerBase::IsSecureEventInputEnabled()
+{
+ NS_ASSERTION(!!sSecureEventInputCount == !!::IsSecureEventInputEnabled(),
+ "Some other process has enabled secure event input");
+ return !!sSecureEventInputCount;
+}
+
+/* static */ void
+TextInputHandlerBase::EnsureSecureEventInputDisabled()
+{
+ while (sSecureEventInputCount) {
+ TextInputHandlerBase::DisableSecureEventInput();
+ }
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase::KeyEventState implementation
+ *
+ ******************************************************************************/
+
+void
+TextInputHandlerBase::KeyEventState::InitKeyEvent(
+ TextInputHandlerBase* aHandler,
+ WidgetKeyboardEvent& aKeyEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aHandler);
+ MOZ_RELEASE_ASSERT(mKeyEvent);
+
+ NSEvent* nativeEvent = mKeyEvent;
+ if (!mInsertedString.IsEmpty()) {
+ nsAutoString unhandledString;
+ GetUnhandledString(unhandledString);
+ NSString* unhandledNSString =
+ nsCocoaUtils::ToNSString(unhandledString);
+ // If the key event's some characters were already handled by
+ // InsertString() calls, we need to create a dummy event which doesn't
+ // include the handled characters.
+ nativeEvent =
+ [NSEvent keyEventWithType:[mKeyEvent type]
+ location:[mKeyEvent locationInWindow]
+ modifierFlags:[mKeyEvent modifierFlags]
+ timestamp:[mKeyEvent timestamp]
+ windowNumber:[mKeyEvent windowNumber]
+ context:[mKeyEvent context]
+ characters:unhandledNSString
+ charactersIgnoringModifiers:[mKeyEvent charactersIgnoringModifiers]
+ isARepeat:[mKeyEvent isARepeat]
+ keyCode:[mKeyEvent keyCode]];
+ }
+
+ aHandler->InitKeyEvent(nativeEvent, aKeyEvent, mInsertString);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+TextInputHandlerBase::KeyEventState::GetUnhandledString(
+ nsAString& aUnhandledString) const
+{
+ aUnhandledString.Truncate();
+ if (NS_WARN_IF(!mKeyEvent)) {
+ return;
+ }
+ nsAutoString characters;
+ nsCocoaUtils::GetStringForNSString([mKeyEvent characters],
+ characters);
+ if (characters.IsEmpty()) {
+ return;
+ }
+ if (mInsertedString.IsEmpty()) {
+ aUnhandledString = characters;
+ return;
+ }
+
+ // The insertes string must match with the start of characters.
+ MOZ_ASSERT(StringBeginsWith(characters, mInsertedString));
+
+ aUnhandledString = nsDependentSubstring(characters, mInsertedString.Length());
+}
+
+#pragma mark -
+
+
+/******************************************************************************
+ *
+ * TextInputHandlerBase::AutoInsertStringClearer implementation
+ *
+ ******************************************************************************/
+
+TextInputHandlerBase::AutoInsertStringClearer::~AutoInsertStringClearer()
+{
+ if (mState && mState->mInsertString) {
+ // If inserting string is a part of characters of the event,
+ // we should record it as inserted string.
+ nsAutoString characters;
+ nsCocoaUtils::GetStringForNSString([mState->mKeyEvent characters],
+ characters);
+ nsAutoString insertedString(mState->mInsertedString);
+ insertedString += *mState->mInsertString;
+ if (StringBeginsWith(characters, insertedString)) {
+ mState->mInsertedString = insertedString;
+ }
+ }
+ if (mState) {
+ mState->mInsertString = nullptr;
+ }
+}
diff --git a/widget/cocoa/VibrancyManager.h b/widget/cocoa/VibrancyManager.h
new file mode 100644
index 0000000000..04b1ad1cda
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef VibrancyManager_h
+#define VibrancyManager_h
+
+#include "mozilla/Assertions.h"
+#include "nsClassHashtable.h"
+#include "nsRegion.h"
+#include "nsTArray.h"
+#include "ViewRegion.h"
+
+#import <Foundation/NSGeometry.h>
+
+@class NSColor;
+@class NSView;
+class nsChildView;
+
+namespace mozilla {
+
+enum class VibrancyType {
+ LIGHT,
+ DARK,
+ TOOLTIP,
+ MENU,
+ HIGHLIGHTED_MENUITEM,
+ SHEET,
+ SOURCE_LIST,
+ SOURCE_LIST_SELECTION,
+ ACTIVE_SOURCE_LIST_SELECTION
+};
+
+/**
+ * VibrancyManager takes care of updating the vibrant regions of a window.
+ * Vibrancy is a visual look that was introduced on OS X starting with 10.10.
+ * An app declares vibrant window regions to the window server, and the window
+ * server will display a blurred rendering of the screen contents from behind
+ * the window in these areas, behind the actual window contents. Consequently,
+ * the effect is only visible in areas where the window contents are not
+ * completely opaque. Usually this is achieved by clearing the background of
+ * the window prior to drawing in the vibrant areas. This is possible even if
+ * the window is declared as opaque.
+ */
+class VibrancyManager {
+public:
+ /**
+ * Create a new VibrancyManager instance and provide it with an NSView
+ * to attach NSVisualEffectViews to.
+ *
+ * @param aCoordinateConverter The nsChildView to use for converting
+ * nsIntRect device pixel coordinates into Cocoa NSRect coordinates. Must
+ * outlive this VibrancyManager instance.
+ * @param aContainerView The view that's going to be the superview of the
+ * NSVisualEffectViews which will be created for vibrant regions.
+ */
+ VibrancyManager(const nsChildView& aCoordinateConverter,
+ NSView* aContainerView)
+ : mCoordinateConverter(aCoordinateConverter)
+ , mContainerView(aContainerView)
+ {
+ MOZ_ASSERT(SystemSupportsVibrancy(),
+ "Don't instantiate this if !SystemSupportsVibrancy()");
+ }
+
+ /**
+ * Update the placement of the NSVisualEffectViews inside the container
+ * NSView so that they cover aRegion, and create new NSVisualEffectViews
+ * or remove existing ones as needed.
+ * @param aType The vibrancy type to use in the region.
+ * @param aRegion The vibrant area, in device pixels.
+ */
+ void UpdateVibrantRegion(VibrancyType aType,
+ const LayoutDeviceIntRegion& aRegion);
+
+ bool HasVibrantRegions() { return !mVibrantRegions.IsEmpty(); }
+
+ /**
+ * Return the fill color that should be drawn on top of the cleared window
+ * parts. Usually this would be drawn by -[NSVisualEffectView drawRect:].
+ * The returned color is opaque if the system-wide "Reduce transparency"
+ * preference is set.
+ */
+ NSColor* VibrancyFillColorForType(VibrancyType aType);
+
+ /**
+ * Return the font smoothing background color that should be used for text
+ * drawn on top of the vibrant window parts.
+ */
+ NSColor* VibrancyFontSmoothingBackgroundColorForType(VibrancyType aType);
+
+ /**
+ * Check whether the operating system supports vibrancy at all.
+ * You may only create a VibrancyManager instance if this returns true.
+ * @return Whether VibrancyManager can be used on this OS.
+ */
+ static bool SystemSupportsVibrancy();
+
+ /**
+ * Create an NSVisualEffectView for the specified vibrancy type. The return
+ * value is not autoreleased. We return an object of type NSView* because we
+ * compile with an SDK that does not contain a definition for
+ * NSVisualEffectView.
+ * @param aIsContainer Whether this NSView will have child views. This value
+ * affects hit testing: Container views will pass through
+ * hit testing requests to their children, and leaf views
+ * will be transparent to hit testing.
+ */
+ static NSView* CreateEffectView(VibrancyType aType, BOOL aIsContainer = NO);
+
+protected:
+ const nsChildView& mCoordinateConverter;
+ NSView* mContainerView;
+ nsClassHashtable<nsUint32HashKey, ViewRegion> mVibrantRegions;
+};
+
+} // namespace mozilla
+
+#endif // VibrancyManager_h
diff --git a/widget/cocoa/VibrancyManager.mm b/widget/cocoa/VibrancyManager.mm
new file mode 100644
index 0000000000..d02338eb6b
--- /dev/null
+++ b/widget/cocoa/VibrancyManager.mm
@@ -0,0 +1,244 @@
+/* -*- Mode: C++; tab-width: 8; 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 "VibrancyManager.h"
+#include "nsChildView.h"
+#import <objc/message.h>
+
+using namespace mozilla;
+
+void
+VibrancyManager::UpdateVibrantRegion(VibrancyType aType,
+ const LayoutDeviceIntRegion& aRegion)
+{
+ if (aRegion.IsEmpty()) {
+ mVibrantRegions.Remove(uint32_t(aType));
+ return;
+ }
+ auto& vr = *mVibrantRegions.LookupOrAdd(uint32_t(aType));
+ vr.UpdateRegion(aRegion, mCoordinateConverter, mContainerView, ^() {
+ return this->CreateEffectView(aType);
+ });
+}
+
+@interface NSView(CurrentFillColor)
+- (NSColor*)_currentFillColor;
+@end
+
+static NSColor*
+AdjustedColor(NSColor* aFillColor, VibrancyType aType)
+{
+ if (aType == VibrancyType::MENU && [aFillColor alphaComponent] == 1.0) {
+ // The opaque fill color that's used for the menu background when "Reduce
+ // vibrancy" is checked in the system accessibility prefs is too dark.
+ // This is probably because we're not using the right material for menus,
+ // see VibrancyManager::CreateEffectView.
+ return [NSColor colorWithDeviceWhite:0.96 alpha:1.0];
+ }
+ return aFillColor;
+}
+
+NSColor*
+VibrancyManager::VibrancyFillColorForType(VibrancyType aType)
+{
+ NSView* view = mVibrantRegions.LookupOrAdd(uint32_t(aType))->GetAnyView();
+
+ if (view && [view respondsToSelector:@selector(_currentFillColor)]) {
+ // -[NSVisualEffectView _currentFillColor] is the color that the view
+ // draws in its drawRect implementation.
+ return AdjustedColor([view _currentFillColor], aType);
+ }
+ return [NSColor whiteColor];
+}
+
+@interface NSView(FontSmoothingBackgroundColor)
+- (NSColor*)fontSmoothingBackgroundColor;
+@end
+
+NSColor*
+VibrancyManager::VibrancyFontSmoothingBackgroundColorForType(VibrancyType aType)
+{
+ NSView* view = mVibrantRegions.LookupOrAdd(uint32_t(aType))->GetAnyView();
+
+ if (view && [view respondsToSelector:@selector(fontSmoothingBackgroundColor)]) {
+ return [view fontSmoothingBackgroundColor];
+ }
+ return [NSColor clearColor];
+}
+
+static NSView*
+HitTestNil(id self, SEL _cmd, NSPoint aPoint)
+{
+ // This view must be transparent to mouse events.
+ return nil;
+}
+
+static BOOL
+AllowsVibrancyYes(id self, SEL _cmd)
+{
+ // Means that the foreground is blended using a vibrant blend mode.
+ return YES;
+}
+
+static Class
+CreateEffectViewClass(BOOL aForegroundVibrancy, BOOL aIsContainer)
+{
+ // Create a class that inherits from NSVisualEffectView and overrides the
+ // methods -[NSView hitTest:] and -[NSVisualEffectView allowsVibrancy].
+ Class NSVisualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
+ const char* className = aForegroundVibrancy
+ ? "EffectViewWithForegroundVibrancy" : "EffectViewWithoutForegroundVibrancy";
+ Class EffectViewClass = objc_allocateClassPair(NSVisualEffectViewClass, className, 0);
+ if (!aIsContainer) {
+ class_addMethod(EffectViewClass, @selector(hitTest:), (IMP)HitTestNil,
+ "@@:{CGPoint=dd}");
+ }
+ if (aForegroundVibrancy) {
+ // Override the -[NSView allowsVibrancy] method to return YES.
+ class_addMethod(EffectViewClass, @selector(allowsVibrancy), (IMP)AllowsVibrancyYes, "I@:");
+ }
+ return EffectViewClass;
+}
+
+static id
+AppearanceForVibrancyType(VibrancyType aType)
+{
+ Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
+ switch (aType) {
+ case VibrancyType::LIGHT:
+ case VibrancyType::TOOLTIP:
+ case VibrancyType::MENU:
+ case VibrancyType::HIGHLIGHTED_MENUITEM:
+ case VibrancyType::SHEET:
+ case VibrancyType::SOURCE_LIST:
+ case VibrancyType::SOURCE_LIST_SELECTION:
+ case VibrancyType::ACTIVE_SOURCE_LIST_SELECTION:
+ return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
+ withObject:@"NSAppearanceNameVibrantLight"];
+ case VibrancyType::DARK:
+ return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
+ withObject:@"NSAppearanceNameVibrantDark"];
+ }
+}
+
+#if !defined(MAC_OS_X_VERSION_10_10) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
+enum {
+ NSVisualEffectStateFollowsWindowActiveState,
+ NSVisualEffectStateActive,
+ NSVisualEffectStateInactive
+};
+
+enum {
+ NSVisualEffectMaterialTitlebar = 3
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_11) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11
+enum {
+ NSVisualEffectMaterialMenu = 5,
+ NSVisualEffectMaterialSidebar = 7
+};
+#endif
+
+static NSUInteger
+VisualEffectStateForVibrancyType(VibrancyType aType)
+{
+ switch (aType) {
+ case VibrancyType::TOOLTIP:
+ case VibrancyType::MENU:
+ case VibrancyType::HIGHLIGHTED_MENUITEM:
+ case VibrancyType::SHEET:
+ // Tooltip and menu windows are never "key" and sheets always looks
+ // active, so we need to tell the vibrancy effect to look active
+ // regardless of window state.
+ return NSVisualEffectStateActive;
+ default:
+ return NSVisualEffectStateFollowsWindowActiveState;
+ }
+}
+
+static BOOL
+HasVibrantForeground(VibrancyType aType)
+{
+ switch (aType) {
+ case VibrancyType::MENU:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+#if !defined(MAC_OS_X_VERSION_10_12) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+enum {
+ NSVisualEffectMaterialSelection = 4
+};
+#endif
+
+@interface NSView(NSVisualEffectViewMethods)
+- (void)setState:(NSUInteger)state;
+- (void)setMaterial:(NSUInteger)material;
+- (void)setEmphasized:(BOOL)emphasized;
+@end
+
+NSView*
+VibrancyManager::CreateEffectView(VibrancyType aType, BOOL aIsContainer)
+{
+ static Class EffectViewWithoutForegroundVibrancy = CreateEffectViewClass(NO, NO);
+ static Class EffectViewWithForegroundVibrancy = CreateEffectViewClass(YES, NO);
+ static Class EffectViewContainer = CreateEffectViewClass(NO, YES);
+
+ // Pick the right NSVisualEffectView subclass for the desired vibrancy mode.
+ // For "container" views, never use foreground vibrancy, because returning
+ // YES from allowsVibrancy forces on foreground vibrancy for all descendant
+ // views which can have unintended effects.
+ Class EffectViewClass = aIsContainer
+ ? EffectViewContainer
+ : (HasVibrantForeground(aType) ? EffectViewWithForegroundVibrancy
+ : EffectViewWithoutForegroundVibrancy);
+ NSView* effectView = [[EffectViewClass alloc] initWithFrame:NSZeroRect];
+ [effectView performSelector:@selector(setAppearance:)
+ withObject:AppearanceForVibrancyType(aType)];
+ [effectView setState:VisualEffectStateForVibrancyType(aType)];
+
+ BOOL canUseElCapitanMaterials = nsCocoaFeatures::OnElCapitanOrLater();
+ if (aType == VibrancyType::MENU) {
+ // Before 10.11 there is no material that perfectly matches the menu
+ // look. Of all available material types, NSVisualEffectMaterialTitlebar
+ // is the one that comes closest.
+ [effectView setMaterial:canUseElCapitanMaterials ? NSVisualEffectMaterialMenu
+ : NSVisualEffectMaterialTitlebar];
+ } else if (aType == VibrancyType::SOURCE_LIST && canUseElCapitanMaterials) {
+ [effectView setMaterial:NSVisualEffectMaterialSidebar];
+ } else if (aType == VibrancyType::HIGHLIGHTED_MENUITEM ||
+ aType == VibrancyType::SOURCE_LIST_SELECTION ||
+ aType == VibrancyType::ACTIVE_SOURCE_LIST_SELECTION) {
+ [effectView setMaterial:NSVisualEffectMaterialSelection];
+ if ([effectView respondsToSelector:@selector(setEmphasized:)] &&
+ aType != VibrancyType::SOURCE_LIST_SELECTION) {
+ [effectView setEmphasized:YES];
+ }
+ }
+
+ return effectView;
+}
+
+static bool
+ComputeSystemSupportsVibrancy()
+{
+#ifdef __x86_64__
+ return NSClassFromString(@"NSAppearance") &&
+ NSClassFromString(@"NSVisualEffectView");
+#else
+ // objc_allocateClassPair doesn't work in 32 bit mode, so turn off vibrancy.
+ return false;
+#endif
+}
+
+/* static */ bool
+VibrancyManager::SystemSupportsVibrancy()
+{
+ static bool supportsVibrancy = ComputeSystemSupportsVibrancy();
+ return supportsVibrancy;
+}
diff --git a/widget/cocoa/ViewRegion.h b/widget/cocoa/ViewRegion.h
new file mode 100644
index 0000000000..9ff7e6d091
--- /dev/null
+++ b/widget/cocoa/ViewRegion.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef ViewRegion_h
+#define ViewRegion_h
+
+#include "Units.h"
+#include "nsTArray.h"
+
+@class NSView;
+
+namespace mozilla {
+
+/**
+ * Manages a set of NSViews to cover a LayoutDeviceIntRegion.
+ */
+class ViewRegion {
+public:
+ ~ViewRegion();
+
+ mozilla::LayoutDeviceIntRegion Region() { return mRegion; }
+
+ /**
+ * Update the region.
+ * @param aRegion The new region.
+ * @param aCoordinateConverter The nsChildView to use for converting
+ * LayoutDeviceIntRect device pixel coordinates into Cocoa NSRect coordinates.
+ * @param aContainerView The view that's going to be the superview of the
+ * NSViews which will be created for this region.
+ * @param aViewCreationCallback A block that instantiates new NSViews.
+ * @return Whether or not the region changed.
+ */
+ bool UpdateRegion(const mozilla::LayoutDeviceIntRegion& aRegion,
+ const nsChildView& aCoordinateConverter,
+ NSView* aContainerView,
+ NSView* (^aViewCreationCallback)());
+
+ /**
+ * Return an NSView from the region, if there is any.
+ */
+ NSView* GetAnyView() { return mViews.Length() > 0 ? mViews[0] : nil; }
+
+private:
+ mozilla::LayoutDeviceIntRegion mRegion;
+ nsTArray<NSView*> mViews;
+};
+
+} // namespace mozilla
+
+#endif // ViewRegion_h
diff --git a/widget/cocoa/ViewRegion.mm b/widget/cocoa/ViewRegion.mm
new file mode 100644
index 0000000000..455d54fdad
--- /dev/null
+++ b/widget/cocoa/ViewRegion.mm
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 8; 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 "ViewRegion.h"
+#import <Cocoa/Cocoa.h>
+
+using namespace mozilla;
+
+ViewRegion::~ViewRegion()
+{
+ for (size_t i = 0; i < mViews.Length(); i++) {
+ [mViews[i] removeFromSuperview];
+ }
+}
+
+bool
+ViewRegion::UpdateRegion(const LayoutDeviceIntRegion& aRegion,
+ const nsChildView& aCoordinateConverter,
+ NSView* aContainerView,
+ NSView* (^aViewCreationCallback)())
+{
+ if (mRegion == aRegion) {
+ return false;
+ }
+
+ // We need to construct the required region using as many EffectViews
+ // as necessary. We try to update the geometry of existing views if
+ // possible, or create new ones or remove old ones if the number of
+ // rects in the region has changed.
+
+ nsTArray<NSView*> viewsToRecycle;
+ mViews.SwapElements(viewsToRecycle);
+ // The mViews array is now empty.
+
+ size_t i = 0;
+ for (auto iter = aRegion.RectIter();
+ !iter.Done() || i < viewsToRecycle.Length();
+ i++) {
+ if (!iter.Done()) {
+ NSView* view = nil;
+ NSRect rect = aCoordinateConverter.DevPixelsToCocoaPoints(iter.Get());
+ if (i < viewsToRecycle.Length()) {
+ view = viewsToRecycle[i];
+ } else {
+ view = aViewCreationCallback();
+ [aContainerView addSubview:view];
+
+ // Now that the view is in the view hierarchy, it'll be kept alive by
+ // its superview, so we can drop our reference.
+ [view release];
+ }
+ if (!NSEqualRects(rect, [view frame])) {
+ [view setFrame:rect];
+ }
+ [view setNeedsDisplay:YES];
+ mViews.AppendElement(view);
+ iter.Next();
+ } else {
+ // Our new region is made of fewer rects than the old region, so we can
+ // remove this view. We only have a weak reference to it, so removing it
+ // from the view hierarchy will release it.
+ [viewsToRecycle[i] removeFromSuperview];
+ }
+ }
+
+ mRegion = aRegion;
+ return true;
+}
diff --git a/widget/cocoa/WidgetTraceEvent.mm b/widget/cocoa/WidgetTraceEvent.mm
new file mode 100644
index 0000000000..7023a17ba0
--- /dev/null
+++ b/widget/cocoa/WidgetTraceEvent.mm
@@ -0,0 +1,85 @@
+/* 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 <Cocoa/Cocoa.h>
+#include "CustomCocoaEvents.h"
+#include <Foundation/NSAutoreleasePool.h>
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+#include "mozilla/WidgetTraceEvent.h"
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace {
+
+Mutex* sMutex = NULL;
+CondVar* sCondVar = NULL;
+bool sTracerProcessed = false;
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing()
+{
+ sMutex = new Mutex("Event tracer thread mutex");
+ sCondVar = new CondVar(*sMutex, "Event tracer thread condvar");
+ return sMutex && sCondVar;
+}
+
+void CleanUpWidgetTracing()
+{
+ delete sMutex;
+ delete sCondVar;
+ sMutex = NULL;
+ sCondVar = NULL;
+}
+
+// This function is called from the main (UI) thread.
+void SignalTracerThread()
+{
+ if (!sMutex || !sCondVar)
+ return;
+ MutexAutoLock lock(*sMutex);
+ if (!sTracerProcessed) {
+ sTracerProcessed = true;
+ sCondVar->Notify();
+ }
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent()
+{
+ MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!");
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ MutexAutoLock lock(*sMutex);
+ if (sTracerProcessed) {
+ // Things are out of sync. This is likely because we're in
+ // the middle of shutting down. Just return false and hope the
+ // tracer thread is quitting anyway.
+ return false;
+ }
+
+ // Post an application-defined event to the main thread's event queue
+ // and wait for it to get processed.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeTrace
+ data1:0
+ data2:0]
+ atStart:NO];
+ while (!sTracerProcessed)
+ sCondVar->Wait();
+ sTracerProcessed = false;
+ [pool release];
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/cocoa/crashtests/373122-1-inner.html b/widget/cocoa/crashtests/373122-1-inner.html
new file mode 100644
index 0000000000..5c14166b75
--- /dev/null
+++ b/widget/cocoa/crashtests/373122-1-inner.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+
+<script>
+function boom()
+{
+ document.body.style.position = "fixed"
+
+ setTimeout(boom2, 1);
+}
+
+function boom2()
+{
+ lappy = document.getElementById("lappy");
+ lappy.style.display = "none"
+
+ setTimeout(boom3, 200);
+}
+
+function boom3()
+{
+ dump("Reloading\n");
+ location.reload();
+}
+
+</script>
+
+
+</head>
+
+
+<body bgcolor="black" onload="boom()">
+
+ <span style="overflow: scroll; display: -moz-box;"></span>
+
+ <embed id="lappy" src="" width=550 height=400 TYPE="application/x-shockwave-flash" ></embed>
+
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/373122-1.html b/widget/cocoa/crashtests/373122-1.html
new file mode 100644
index 0000000000..a57e5f4249
--- /dev/null
+++ b/widget/cocoa/crashtests/373122-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="373122-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/397209-1.html b/widget/cocoa/crashtests/397209-1.html
new file mode 100644
index 0000000000..554b2dac72
--- /dev/null
+++ b/widget/cocoa/crashtests/397209-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<button style="width: 8205em;"></button>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/403296-1.xhtml b/widget/cocoa/crashtests/403296-1.xhtml
new file mode 100644
index 0000000000..800eaa3558
--- /dev/null
+++ b/widget/cocoa/crashtests/403296-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ style="margin: 12em; padding: 20px 10em; opacity: 0.2; font-size: 11.2px; -moz-appearance: toolbar; white-space: nowrap;"><body
+ style="position: absolute;"
+ onload="setTimeout(function() { document.body.removeChild(document.getElementById('tr')); document.documentElement.removeAttribute('class'); }, 30);">
+
+xxx
+yyy
+
+<tr id="tr">300</tr></body></html>
diff --git a/widget/cocoa/crashtests/419737-1.html b/widget/cocoa/crashtests/419737-1.html
new file mode 100644
index 0000000000..fe6e4532b4
--- /dev/null
+++ b/widget/cocoa/crashtests/419737-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div><span style="-moz-appearance: radio; padding: 15000px;"></span></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/435223-1.html b/widget/cocoa/crashtests/435223-1.html
new file mode 100644
index 0000000000..1bbc27ba01
--- /dev/null
+++ b/widget/cocoa/crashtests/435223-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+<div style="min-width: -moz-max-content;"><div style="-moz-appearance: button;"><div style="margin: 0 100%;"></div></div></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/444260-1.xul b/widget/cocoa/crashtests/444260-1.xul
new file mode 100644
index 0000000000..f1a84023df
--- /dev/null
+++ b/widget/cocoa/crashtests/444260-1.xul
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<hbox><button width="7788025414616">S</button></hbox>
+</window>
diff --git a/widget/cocoa/crashtests/444864-1.html b/widget/cocoa/crashtests/444864-1.html
new file mode 100644
index 0000000000..f8bac76e6a
--- /dev/null
+++ b/widget/cocoa/crashtests/444864-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="padding: 10px;"><input type="button" value="Go" style="letter-spacing: 331989pt;"></div>
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/449111-1.html b/widget/cocoa/crashtests/449111-1.html
new file mode 100644
index 0000000000..4494591803
--- /dev/null
+++ b/widget/cocoa/crashtests/449111-1.html
@@ -0,0 +1,4 @@
+<html>
+<head></head>
+<body><div style="display: -moz-box; word-spacing: 549755813889px;"><button>T </button></div></body>
+</html>
diff --git a/widget/cocoa/crashtests/460349-1.xhtml b/widget/cocoa/crashtests/460349-1.xhtml
new file mode 100644
index 0000000000..cc9b9700c7
--- /dev/null
+++ b/widget/cocoa/crashtests/460349-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+<body><div><mstyle xmlns="http://www.w3.org/1998/Math/MathML" style="-moz-appearance: button;"/></div></body>
+</html>
diff --git a/widget/cocoa/crashtests/460387-1.html b/widget/cocoa/crashtests/460387-1.html
new file mode 100644
index 0000000000..cab7e7eb32
--- /dev/null
+++ b/widget/cocoa/crashtests/460387-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head></head><body><div style="display: table; padding: 625203mm; -moz-appearance: menulist;"></div></body></html>
diff --git a/widget/cocoa/crashtests/464589-1.html b/widget/cocoa/crashtests/464589-1.html
new file mode 100644
index 0000000000..d25d92315d
--- /dev/null
+++ b/widget/cocoa/crashtests/464589-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var o2 = document.createElement("option");
+ document.getElementById("o1").appendChild(o2);
+ o2.style.padding = "131072cm";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<select><option id="o1" style="height: 0cm;"></option></select>
+
+</body>
+</html>
diff --git a/widget/cocoa/crashtests/crashtests.list b/widget/cocoa/crashtests/crashtests.list
new file mode 100644
index 0000000000..b65fe01394
--- /dev/null
+++ b/widget/cocoa/crashtests/crashtests.list
@@ -0,0 +1,11 @@
+skip-if(!cocoaWidget) load 373122-1.html # bug 1300017
+load 397209-1.html
+load 403296-1.xhtml
+load 419737-1.html
+load 435223-1.html
+load 444260-1.xul
+load 444864-1.html
+load 449111-1.html
+load 460349-1.xhtml
+load 460387-1.html
+load 464589-1.html
diff --git a/widget/cocoa/cursors/arrowN.png b/widget/cocoa/cursors/arrowN.png
new file mode 100644
index 0000000000..5ca8ec5ac6
--- /dev/null
+++ b/widget/cocoa/cursors/arrowN.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowN@2x.png b/widget/cocoa/cursors/arrowN@2x.png
new file mode 100644
index 0000000000..d00e87636c
--- /dev/null
+++ b/widget/cocoa/cursors/arrowN@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowS.png b/widget/cocoa/cursors/arrowS.png
new file mode 100644
index 0000000000..9b2d19e0fd
--- /dev/null
+++ b/widget/cocoa/cursors/arrowS.png
Binary files differ
diff --git a/widget/cocoa/cursors/arrowS@2x.png b/widget/cocoa/cursors/arrowS@2x.png
new file mode 100644
index 0000000000..5d011c1fd1
--- /dev/null
+++ b/widget/cocoa/cursors/arrowS@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/cell.png b/widget/cocoa/cursors/cell.png
new file mode 100644
index 0000000000..5284eaec57
--- /dev/null
+++ b/widget/cocoa/cursors/cell.png
Binary files differ
diff --git a/widget/cocoa/cursors/cell@2x.png b/widget/cocoa/cursors/cell@2x.png
new file mode 100644
index 0000000000..5e6738cff7
--- /dev/null
+++ b/widget/cocoa/cursors/cell@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/colResize.png b/widget/cocoa/cursors/colResize.png
new file mode 100644
index 0000000000..4e3e19e223
--- /dev/null
+++ b/widget/cocoa/cursors/colResize.png
Binary files differ
diff --git a/widget/cocoa/cursors/colResize@2x.png b/widget/cocoa/cursors/colResize@2x.png
new file mode 100644
index 0000000000..6a92cf6806
--- /dev/null
+++ b/widget/cocoa/cursors/colResize@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/help.png b/widget/cocoa/cursors/help.png
new file mode 100644
index 0000000000..5e5416b4e3
--- /dev/null
+++ b/widget/cocoa/cursors/help.png
Binary files differ
diff --git a/widget/cocoa/cursors/help@2x.png b/widget/cocoa/cursors/help@2x.png
new file mode 100644
index 0000000000..0ac53a9733
--- /dev/null
+++ b/widget/cocoa/cursors/help@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/move.png b/widget/cocoa/cursors/move.png
new file mode 100644
index 0000000000..1360f82277
--- /dev/null
+++ b/widget/cocoa/cursors/move.png
Binary files differ
diff --git a/widget/cocoa/cursors/move@2x.png b/widget/cocoa/cursors/move@2x.png
new file mode 100644
index 0000000000..ad146e4863
--- /dev/null
+++ b/widget/cocoa/cursors/move@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/rowResize.png b/widget/cocoa/cursors/rowResize.png
new file mode 100644
index 0000000000..4c16bb8bd6
--- /dev/null
+++ b/widget/cocoa/cursors/rowResize.png
Binary files differ
diff --git a/widget/cocoa/cursors/rowResize@2x.png b/widget/cocoa/cursors/rowResize@2x.png
new file mode 100644
index 0000000000..b48f03ae0d
--- /dev/null
+++ b/widget/cocoa/cursors/rowResize@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNE.png b/widget/cocoa/cursors/sizeNE.png
new file mode 100644
index 0000000000..f62c046575
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNE@2x.png b/widget/cocoa/cursors/sizeNE@2x.png
new file mode 100644
index 0000000000..98d19e9efa
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNESW.png b/widget/cocoa/cursors/sizeNESW.png
new file mode 100644
index 0000000000..0a077fa674
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNESW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNESW@2x.png b/widget/cocoa/cursors/sizeNESW@2x.png
new file mode 100644
index 0000000000..31bca3c901
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNESW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNS.png b/widget/cocoa/cursors/sizeNS.png
new file mode 100644
index 0000000000..0419be0af7
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNS.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNS@2x.png b/widget/cocoa/cursors/sizeNS@2x.png
new file mode 100644
index 0000000000..e48fd0cb3a
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNS@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNW.png b/widget/cocoa/cursors/sizeNW.png
new file mode 100644
index 0000000000..8f5faee5f7
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNW@2x.png b/widget/cocoa/cursors/sizeNW@2x.png
new file mode 100644
index 0000000000..3a80e7ce91
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNWSE.png b/widget/cocoa/cursors/sizeNWSE.png
new file mode 100644
index 0000000000..0574a584c1
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNWSE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeNWSE@2x.png b/widget/cocoa/cursors/sizeNWSE@2x.png
new file mode 100644
index 0000000000..9a0a276c34
--- /dev/null
+++ b/widget/cocoa/cursors/sizeNWSE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSE.png b/widget/cocoa/cursors/sizeSE.png
new file mode 100644
index 0000000000..6a1948f521
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSE.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSE@2x.png b/widget/cocoa/cursors/sizeSE@2x.png
new file mode 100644
index 0000000000..7d637f4be6
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSE@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSW.png b/widget/cocoa/cursors/sizeSW.png
new file mode 100644
index 0000000000..5dd054dd46
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSW.png
Binary files differ
diff --git a/widget/cocoa/cursors/sizeSW@2x.png b/widget/cocoa/cursors/sizeSW@2x.png
new file mode 100644
index 0000000000..5ac63c25c6
--- /dev/null
+++ b/widget/cocoa/cursors/sizeSW@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/vtIBeam.png b/widget/cocoa/cursors/vtIBeam.png
new file mode 100644
index 0000000000..ee7528c595
--- /dev/null
+++ b/widget/cocoa/cursors/vtIBeam.png
Binary files differ
diff --git a/widget/cocoa/cursors/vtIBeam@2x.png b/widget/cocoa/cursors/vtIBeam@2x.png
new file mode 100644
index 0000000000..41c47af116
--- /dev/null
+++ b/widget/cocoa/cursors/vtIBeam@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomIn.png b/widget/cocoa/cursors/zoomIn.png
new file mode 100644
index 0000000000..275bf1c69d
--- /dev/null
+++ b/widget/cocoa/cursors/zoomIn.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomIn@2x.png b/widget/cocoa/cursors/zoomIn@2x.png
new file mode 100644
index 0000000000..fdd3f8e71d
--- /dev/null
+++ b/widget/cocoa/cursors/zoomIn@2x.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomOut.png b/widget/cocoa/cursors/zoomOut.png
new file mode 100644
index 0000000000..19d2d89125
--- /dev/null
+++ b/widget/cocoa/cursors/zoomOut.png
Binary files differ
diff --git a/widget/cocoa/cursors/zoomOut@2x.png b/widget/cocoa/cursors/zoomOut@2x.png
new file mode 100644
index 0000000000..0ed46ce75e
--- /dev/null
+++ b/widget/cocoa/cursors/zoomOut@2x.png
Binary files differ
diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build
new file mode 100644
index 0000000000..c4f887297d
--- /dev/null
+++ b/widget/cocoa/moz.build
@@ -0,0 +1,140 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+XPIDL_SOURCES += [
+ 'nsPIWidgetCocoa.idl',
+]
+
+XPIDL_MODULE = 'widget_cocoa'
+
+EXPORTS += [
+ 'mozView.h',
+ 'nsBidiKeyboard.h',
+ 'nsChangeObserver.h',
+ 'nsCocoaDebugUtils.h',
+ 'nsCocoaFeatures.h',
+ 'nsCocoaUtils.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ComplexTextInputPanel.mm',
+ 'GfxInfo.mm',
+ 'NativeKeyBindings.mm',
+ 'nsAppShell.mm',
+ 'nsBidiKeyboard.mm',
+ 'nsCocoaFeatures.mm',
+ 'nsCocoaUtils.mm',
+ 'nsCocoaWindow.mm',
+ 'nsColorPicker.mm',
+ 'nsCursorManager.mm',
+ 'nsDeviceContextSpecX.mm',
+ 'nsFilePicker.mm',
+ 'nsIdleServiceX.mm',
+ 'nsLookAndFeel.mm',
+ 'nsMacCursor.mm',
+ 'nsMacDockSupport.mm',
+ 'nsMacWebAppUtils.mm',
+ 'nsMenuBarX.mm',
+ 'nsMenuGroupOwnerX.mm',
+ 'nsMenuItemIconX.mm',
+ 'nsMenuItemX.mm',
+ 'nsMenuUtilsX.mm',
+ 'nsMenuX.mm',
+ 'nsPrintDialogX.mm',
+ 'nsPrintOptionsX.mm',
+ 'nsPrintSettingsX.mm',
+ 'nsScreenCocoa.mm',
+ 'nsScreenManagerCocoa.mm',
+ 'nsSound.mm',
+ 'nsStandaloneNativeMenu.mm',
+ 'nsSystemStatusBarCocoa.mm',
+ 'nsToolkit.mm',
+ 'nsWidgetFactory.mm',
+ 'nsWindowMap.mm',
+ 'OSXNotificationCenter.mm',
+ 'RectTextureImage.mm',
+ 'SwipeTracker.mm',
+ 'TextInputHandler.mm',
+ 'VibrancyManager.mm',
+ 'ViewRegion.mm',
+ 'WidgetTraceEvent.mm',
+]
+
+# These files cannot be built in unified mode because they cause symbol conflicts
+SOURCES += [
+ 'nsChildView.mm',
+ 'nsClipboard.mm',
+ 'nsCocoaDebugUtils.mm',
+ 'nsDragService.mm',
+ 'nsNativeThemeCocoa.mm',
+]
+
+if not CONFIG['RELEASE_OR_BETA'] or CONFIG['MOZ_DEBUG']:
+ SOURCES += [
+ 'nsSandboxViolationSink.mm',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+# XXX: We should fix these warnings.
+ALLOW_COMPILER_WARNINGS = True
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/layout/forms',
+ '/layout/generic',
+ '/layout/style',
+ '/layout/xul',
+ '/widget',
+]
+
+RESOURCE_FILES.cursors += [
+ 'cursors/arrowN.png',
+ 'cursors/arrowN@2x.png',
+ 'cursors/arrowS.png',
+ 'cursors/arrowS@2x.png',
+ 'cursors/cell.png',
+ 'cursors/cell@2x.png',
+ 'cursors/colResize.png',
+ 'cursors/colResize@2x.png',
+ 'cursors/help.png',
+ 'cursors/help@2x.png',
+ 'cursors/move.png',
+ 'cursors/move@2x.png',
+ 'cursors/rowResize.png',
+ 'cursors/rowResize@2x.png',
+ 'cursors/sizeNE.png',
+ 'cursors/sizeNE@2x.png',
+ 'cursors/sizeNESW.png',
+ 'cursors/sizeNESW@2x.png',
+ 'cursors/sizeNS.png',
+ 'cursors/sizeNS@2x.png',
+ 'cursors/sizeNW.png',
+ 'cursors/sizeNW@2x.png',
+ 'cursors/sizeNWSE.png',
+ 'cursors/sizeNWSE@2x.png',
+ 'cursors/sizeSE.png',
+ 'cursors/sizeSE@2x.png',
+ 'cursors/sizeSW.png',
+ 'cursors/sizeSW@2x.png',
+ 'cursors/vtIBeam.png',
+ 'cursors/vtIBeam@2x.png',
+ 'cursors/zoomIn.png',
+ 'cursors/zoomIn@2x.png',
+ 'cursors/zoomOut.png',
+ 'cursors/zoomOut@2x.png',
+]
+
+# These resources go in $(DIST)/bin/res/MainMenu.nib, but we can't use a magic
+# RESOURCE_FILES.MainMenu.nib attribute, since that would put the files in
+# $(DIST)/bin/res/MainMenu/nib. Instead, we call __setattr__ directly to create
+# an attribute with the correct name.
+RESOURCE_FILES.__setattr__('MainMenu.nib', [
+ 'resources/MainMenu.nib/classes.nib',
+ 'resources/MainMenu.nib/info.nib',
+ 'resources/MainMenu.nib/keyedobjects.nib',
+])
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/widget/cocoa/mozView.h b/widget/cocoa/mozView.h
new file mode 100644
index 0000000000..bd054893ba
--- /dev/null
+++ b/widget/cocoa/mozView.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef mozView_h_
+#define mozView_h_
+
+#undef DARWIN
+#import <Cocoa/Cocoa.h>
+class nsIWidget;
+
+namespace mozilla {
+namespace widget{
+class TextInputHandler;
+} // namespace widget
+} // namespace mozilla
+
+// A protocol listing all the methods that an object which wants
+// to live in gecko's widget hierarchy must implement. |nsChildView|
+// makes assumptions that any NSView with which it comes in contact will
+// implement this protocol.
+@protocol mozView
+
+ // aHandler is Gecko's default text input handler: It implements the
+ // NSTextInput protocol to handle key events. Don't make aHandler a
+ // strong reference -- that causes a memory leak.
+- (void)installTextInputHandler:(mozilla::widget::TextInputHandler*)aHandler;
+- (void)uninstallTextInputHandler;
+
+ // access the nsIWidget associated with this view. DOES NOT ADDREF.
+- (nsIWidget*)widget;
+
+ // return a context menu for this view
+- (NSMenu*)contextMenu;
+
+ // called when our corresponding Gecko view goes away
+- (void)widgetDestroyed;
+
+- (BOOL)isDragInProgress;
+
+ // Checks whether the view is first responder or not
+- (BOOL)isFirstResponder;
+
+ // Call when you dispatch an event which may cause to open context menu.
+- (void)maybeInitContextMenuTracking;
+
+@end
+
+// An informal protocol implemented by the NSWindow of the host application.
+//
+// It's used to prevent re-entrant calls to -makeKeyAndOrderFront: when gecko
+// focus/activate events propagate out to the embedder's
+// nsIEmbeddingSiteWindow::SetFocus implementation.
+@interface NSObject(mozWindow)
+
+- (BOOL)suppressMakeKeyFront;
+- (void)setSuppressMakeKeyFront:(BOOL)inSuppress;
+
+@end
+
+#endif // mozView_h_
diff --git a/widget/cocoa/nsAppShell.h b/widget/cocoa/nsAppShell.h
new file mode 100644
index 0000000000..b7836b6391
--- /dev/null
+++ b/widget/cocoa/nsAppShell.h
@@ -0,0 +1,71 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+/*
+ * Runs the main native Cocoa run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#ifndef nsAppShell_h_
+#define nsAppShell_h_
+
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+// GeckoNSApplication
+//
+// Subclass of NSApplication for filtering out certain events.
+@interface GeckoNSApplication : NSApplication
+{
+}
+@end
+
+@class AppShellDelegate;
+
+class nsAppShell : public nsBaseAppShell
+{
+public:
+ NS_IMETHOD ResumeNative(void);
+
+ nsAppShell();
+
+ nsresult Init();
+
+ NS_IMETHOD Run(void);
+ NS_IMETHOD Exit(void);
+ NS_IMETHOD OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait);
+ NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal *aThread,
+ bool aEventWasProcessed);
+
+ // public only to be visible to Objective-C code that must call it
+ void WillTerminate();
+
+protected:
+ virtual ~nsAppShell();
+
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool aMayWait);
+
+ static void ProcessGeckoEvents(void* aInfo);
+
+protected:
+ CFMutableArrayRef mAutoreleasePools;
+
+ AppShellDelegate* mDelegate;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mCFRunLoopSource;
+
+ bool mRunningEventLoop;
+ bool mStarted;
+ bool mTerminated;
+ bool mSkippedNativeCallback;
+ bool mRunningCocoaEmbedded;
+
+ int32_t mNativeEventCallbackDepth;
+ // Can be set from different threads, so must be modified atomically
+ int32_t mNativeEventScheduledDepth;
+};
+
+#endif // nsAppShell_h_
diff --git a/widget/cocoa/nsAppShell.mm b/widget/cocoa/nsAppShell.mm
new file mode 100644
index 0000000000..33ce8e742a
--- /dev/null
+++ b/widget/cocoa/nsAppShell.mm
@@ -0,0 +1,907 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+/*
+ * Runs the main native Cocoa run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#include "CustomCocoaEvents.h"
+#include "mozilla/WidgetTraceEvent.h"
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "nsIWindowMediator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsToolkit.h"
+#include "TextInputHandler.h"
+#include "mozilla/HangMonitor.h"
+#include "GeckoProfiler.h"
+#include "pratom.h"
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+#include "nsSandboxViolationSink.h"
+#endif
+
+#include <IOKit/pwr_mgt/IOPMLib.h>
+#include "nsIDOMWakeLockListener.h"
+#include "nsIPowerManagerService.h"
+
+using namespace mozilla::widget;
+
+// A wake lock listener that disables screen saver when requested by
+// Gecko. For example when we're playing video in a foreground tab we
+// don't want the screen saver to turn on.
+
+class MacWakeLockListener final : public nsIDOMMozWakeLockListener {
+public:
+ NS_DECL_ISUPPORTS;
+
+private:
+ ~MacWakeLockListener() {}
+
+ IOPMAssertionID mAssertionID = kIOPMNullAssertionID;
+
+ NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) override {
+ if (!aTopic.EqualsASCII("screen")) {
+ return NS_OK;
+ }
+ // Note the wake lock code ensures that we're not sent duplicate
+ // "locked-foreground" notifications when multiple wake locks are held.
+ if (aState.EqualsASCII("locked-foreground")) {
+ // Prevent screen saver.
+ CFStringRef cf_topic =
+ ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+ reinterpret_cast<const UniChar*>
+ (aTopic.Data()),
+ aTopic.Length());
+ IOReturn success =
+ ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn,
+ cf_topic,
+ &mAssertionID);
+ CFRelease(cf_topic);
+ if (success != kIOReturnSuccess) {
+ NS_WARNING("failed to disable screensaver");
+ }
+ } else {
+ // Re-enable screen saver.
+ NS_WARNING("Releasing screensaver");
+ if (mAssertionID != kIOPMNullAssertionID) {
+ IOReturn result = ::IOPMAssertionRelease(mAssertionID);
+ if (result != kIOReturnSuccess) {
+ NS_WARNING("failed to release screensaver");
+ }
+ }
+ }
+ return NS_OK;
+ }
+}; // MacWakeLockListener
+
+// defined in nsCocoaWindow.mm
+extern int32_t gXULModalLevel;
+
+static bool gAppShellMethodsSwizzled = false;
+
+@implementation GeckoNSApplication
+
+- (void)sendEvent:(NSEvent *)anEvent
+{
+ mozilla::HangMonitor::NotifyActivity();
+ if ([anEvent type] == NSApplicationDefined &&
+ [anEvent subtype] == kEventSubtypeTrace) {
+ mozilla::SignalTracerThread();
+ return;
+ }
+ [super sendEvent:anEvent];
+}
+
+#if defined(MAC_OS_X_VERSION_10_12) && \
+ MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 && \
+ __LP64__
+// 10.12 changed `mask` to NSEventMask (unsigned long long) for x86_64 builds.
+- (NSEvent*)nextEventMatchingMask:(NSEventMask)mask
+#else
+- (NSEvent*)nextEventMatchingMask:(NSUInteger)mask
+#endif
+ untilDate:(NSDate*)expiration
+ inMode:(NSString*)mode
+ dequeue:(BOOL)flag
+{
+ if (expiration) {
+ mozilla::HangMonitor::Suspend();
+ }
+ NSEvent* nextEvent = [super nextEventMatchingMask:mask
+ untilDate:expiration inMode:mode dequeue:flag];
+ if (expiration) {
+ mozilla::HangMonitor::NotifyActivity();
+ }
+ return nextEvent;
+}
+
+@end
+
+// AppShellDelegate
+//
+// Cocoa bridge class. An object of this class is registered to receive
+// notifications.
+//
+@interface AppShellDelegate : NSObject
+{
+ @private
+ nsAppShell* mAppShell;
+}
+
+- (id)initWithAppShell:(nsAppShell*)aAppShell;
+- (void)applicationWillTerminate:(NSNotification*)aNotification;
+- (void)beginMenuTracking:(NSNotification*)aNotification;
+@end
+
+// nsAppShell implementation
+
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void)
+{
+ nsresult retval = nsBaseAppShell::ResumeNative();
+ if (NS_SUCCEEDED(retval) && (mSuspendNativeCount == 0) &&
+ mSkippedNativeCallback)
+ {
+ mSkippedNativeCallback = false;
+ ScheduleNativeEventCallback();
+ }
+ return retval;
+}
+
+nsAppShell::nsAppShell()
+: mAutoreleasePools(nullptr)
+, mDelegate(nullptr)
+, mCFRunLoop(NULL)
+, mCFRunLoopSource(NULL)
+, mRunningEventLoop(false)
+, mStarted(false)
+, mTerminated(false)
+, mSkippedNativeCallback(false)
+, mNativeEventCallbackDepth(0)
+, mNativeEventScheduledDepth(0)
+{
+ // A Cocoa event loop is running here if (and only if) we've been embedded
+ // by a Cocoa app.
+ mRunningCocoaEmbedded = [NSApp isRunning] ? true : false;
+}
+
+nsAppShell::~nsAppShell()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mCFRunLoop) {
+ if (mCFRunLoopSource) {
+ ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
+ kCFRunLoopCommonModes);
+ ::CFRelease(mCFRunLoopSource);
+ }
+ ::CFRelease(mCFRunLoop);
+ }
+
+ if (mAutoreleasePools) {
+ NS_ASSERTION(::CFArrayGetCount(mAutoreleasePools) == 0,
+ "nsAppShell destroyed without popping all autorelease pools");
+ ::CFRelease(mAutoreleasePools);
+ }
+
+ [mDelegate release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+NS_IMPL_ISUPPORTS(MacWakeLockListener, nsIDOMMozWakeLockListener)
+mozilla::StaticRefPtr<MacWakeLockListener> sWakeLockListener;
+
+static void
+AddScreenWakeLockListener()
+{
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
+ POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sWakeLockListener = new MacWakeLockListener();
+ sPowerManagerService->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void
+RemoveScreenWakeLockListener()
+{
+ nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(
+ POWERMANAGERSERVICE_CONTRACTID);
+ if (sPowerManagerService) {
+ sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
+ sPowerManagerService = nullptr;
+ sWakeLockListener = nullptr;
+ }
+}
+
+// An undocumented CoreGraphics framework method, present in the same form
+// since at least OS X 10.5.
+extern "C" CGError CGSSetDebugOptions(int options);
+
+// Init
+//
+// Loads the nib (see bug 316076c21) and sets up the CFRunLoopSource used to
+// interrupt the main native run loop.
+//
+// public
+nsresult
+nsAppShell::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // No event loop is running yet (unless an embedding app that uses
+ // NSApplicationMain() is running).
+ NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
+
+ // mAutoreleasePools is used as a stack of NSAutoreleasePool objects created
+ // by |this|. CFArray is used instead of NSArray because NSArray wants to
+ // retain each object you add to it, and you can't retain an
+ // NSAutoreleasePool.
+ mAutoreleasePools = ::CFArrayCreateMutable(nullptr, 0, nullptr);
+ NS_ENSURE_STATE(mAutoreleasePools);
+
+ // Get the path of the nib file, which lives in the GRE location
+ nsCOMPtr<nsIFile> nibFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(nibFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nibFile->AppendNative(NS_LITERAL_CSTRING("res"));
+ nibFile->AppendNative(NS_LITERAL_CSTRING("MainMenu.nib"));
+
+ nsAutoCString nibPath;
+ rv = nibFile->GetNativePath(nibPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This call initializes NSApplication unless:
+ // 1) we're using xre -- NSApp's already been initialized by
+ // MacApplicationDelegate.mm's EnsureUseCocoaDockAPI().
+ // 2) an embedding app that uses NSApplicationMain() is running -- NSApp's
+ // already been initialized and its main run loop is already running.
+ [NSBundle loadNibFile:
+ [NSString stringWithUTF8String:(const char*)nibPath.get()]
+ externalNameTable:
+ [NSDictionary dictionaryWithObject:[GeckoNSApplication sharedApplication]
+ forKey:@"NSOwner"]
+ withZone:NSDefaultMallocZone()];
+
+ mDelegate = [[AppShellDelegate alloc] initWithAppShell:this];
+ NS_ENSURE_STATE(mDelegate);
+
+ // Add a CFRunLoopSource to the main native run loop. The source is
+ // responsible for interrupting the run loop when Gecko events are ready.
+
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ NS_ENSURE_STATE(mCFRunLoop);
+ ::CFRetain(mCFRunLoop);
+
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ // context.version = 0;
+ context.info = this;
+ context.perform = ProcessGeckoEvents;
+
+ mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
+ NS_ENSURE_STATE(mCFRunLoopSource);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+
+ rv = nsBaseAppShell::Init();
+
+ if (!gAppShellMethodsSwizzled) {
+ // We should only replace the original terminate: method if we're not
+ // running in a Cocoa embedder. See bug 604901.
+ if (!mRunningCocoaEmbedded) {
+ nsToolkit::SwizzleMethods([NSApplication class], @selector(terminate:),
+ @selector(nsAppShell_NSApplication_terminate:));
+ }
+ gAppShellMethodsSwizzled = true;
+ }
+
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ // Explicitly turn off CGEvent logging. This works around bug 1092855.
+ // If there are already CGEvents in the log, turning off logging also
+ // causes those events to be written to disk. But at this point no
+ // CGEvents have yet been processed. CGEvents are events (usually
+ // input events) pulled from the WindowServer. An option of 0x80000008
+ // turns on CGEvent logging.
+ CGSSetDebugOptions(0x80000007);
+ }
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ if (Preferences::GetBool("security.sandbox.mac.track.violations", false)) {
+ nsSandboxViolationSink::Start();
+ }
+#endif
+
+ [localPool release];
+
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// ProcessGeckoEvents
+//
+// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
+// signalled from ScheduleNativeEventCallback.
+//
+// Arrange for Gecko events to be processed on demand (in response to a call
+// to ScheduleNativeEventCallback(), if processing of Gecko events via "native
+// methods" hasn't been suspended). This happens in NativeEventCallback().
+//
+// protected static
+void
+nsAppShell::ProcessGeckoEvents(void* aInfo)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ PROFILER_LABEL("Events", "ProcessGeckoEvents",
+ js::ProfileEntry::Category::EVENTS);
+
+ nsAppShell* self = static_cast<nsAppShell*> (aInfo);
+
+ if (self->mRunningEventLoop) {
+ self->mRunningEventLoop = false;
+
+ // The run loop may be sleeping -- [NSRunLoop runMode:...]
+ // won't return until it's given a reason to wake up. Awaken it by
+ // posting a bogus event. There's no need to make the event
+ // presentable.
+ //
+ // But _don't_ set windowNumber to '-1' -- that can lead to nasty
+ // weirdness like bmo bug 397039 (a crash in [NSApp sendEvent:] on one of
+ // these fake events, because the -1 has gotten changed into the number
+ // of an actual NSWindow object, and that NSWindow object has just been
+ // destroyed). Setting windowNumber to '0' seems to work fine -- this
+ // seems to prevent the OS from ever trying to associate our bogus event
+ // with a particular NSWindow object.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeNone
+ data1:0
+ data2:0]
+ atStart:NO];
+ }
+
+ if (self->mSuspendNativeCount <= 0) {
+ ++self->mNativeEventCallbackDepth;
+ self->NativeEventCallback();
+ --self->mNativeEventCallbackDepth;
+ } else {
+ self->mSkippedNativeCallback = true;
+ }
+
+ // Still needed to avoid crashes on quit in most Mochitests.
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+ location:NSMakePoint(0,0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:NULL
+ subtype:kEventSubtypeNone
+ data1:0
+ data2:0]
+ atStart:NO];
+
+ // Normally every call to ScheduleNativeEventCallback() results in
+ // exactly one call to ProcessGeckoEvents(). So each Release() here
+ // normally balances exactly one AddRef() in ScheduleNativeEventCallback().
+ // But if Exit() is called just after ScheduleNativeEventCallback(), the
+ // corresponding call to ProcessGeckoEvents() will never happen. We check
+ // for this possibility in two different places -- here and in Exit()
+ // itself. If we find here that Exit() has been called (that mTerminated
+ // is true), it's because we've been called recursively, that Exit() was
+ // called from self->NativeEventCallback() above, and that we're unwinding
+ // the recursion. In this case we'll never be called again, and we balance
+ // here any extra calls to ScheduleNativeEventCallback().
+ //
+ // When ProcessGeckoEvents() is called recursively, it's because of a
+ // call to ScheduleNativeEventCallback() from NativeEventCallback(). We
+ // balance the "extra" AddRefs here (rather than always in Exit()) in order
+ // to ensure that 'self' stays alive until the end of this method. We also
+ // make sure not to finish the balancing until all the recursion has been
+ // unwound.
+ if (self->mTerminated) {
+ int32_t releaseCount = 0;
+ if (self->mNativeEventScheduledDepth > self->mNativeEventCallbackDepth) {
+ releaseCount = PR_ATOMIC_SET(&self->mNativeEventScheduledDepth,
+ self->mNativeEventCallbackDepth);
+ }
+ while (releaseCount-- > self->mNativeEventCallbackDepth)
+ self->Release();
+ } else {
+ // As best we can tell, every call to ProcessGeckoEvents() is triggered
+ // by a call to ScheduleNativeEventCallback(). But we've seen a few
+ // (non-reproducible) cases of double-frees that *might* have been caused
+ // by spontaneous calls (from the OS) to ProcessGeckoEvents(). So we
+ // deal with that possibility here.
+ if (PR_ATOMIC_DECREMENT(&self->mNativeEventScheduledDepth) < 0) {
+ PR_ATOMIC_SET(&self->mNativeEventScheduledDepth, 0);
+ NS_WARNING("Spontaneous call to ProcessGeckoEvents()!");
+ } else {
+ self->Release();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// WillTerminate
+//
+// Called by the AppShellDelegate when an NSApplicationWillTerminate
+// notification is posted. After this method is called, native events should
+// no longer be processed. The NSApplicationWillTerminate notification is
+// only posted when [NSApp terminate:] is called, which doesn't happen on a
+// "normal" application quit.
+//
+// public
+void
+nsAppShell::WillTerminate()
+{
+ if (mTerminated)
+ return;
+
+ // Make sure that the nsAppExitEvent posted by nsAppStartup::Quit() (called
+ // from [MacApplicationDelegate applicationShouldTerminate:]) gets run.
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+
+ mTerminated = true;
+}
+
+// ScheduleNativeEventCallback
+//
+// Called (possibly on a non-main thread) when Gecko has an event that
+// needs to be processed. The Gecko event needs to be processed on the
+// main thread, so the native run loop must be interrupted.
+//
+// In nsBaseAppShell.cpp, the mNativeEventPending variable is used to
+// ensure that ScheduleNativeEventCallback() is called no more than once
+// per call to NativeEventCallback(). ProcessGeckoEvents() can skip its
+// call to NativeEventCallback() if processing of Gecko events by native
+// means is suspended (using nsIAppShell::SuspendNative()), which will
+// suspend calls from nsBaseAppShell::OnDispatchedEvent() to
+// ScheduleNativeEventCallback(). But when Gecko event processing by
+// native means is resumed (in ResumeNative()), an extra call is made to
+// ScheduleNativeEventCallback() (from ResumeNative()). This triggers
+// another call to ProcessGeckoEvents(), which calls NativeEventCallback(),
+// and nsBaseAppShell::OnDispatchedEvent() resumes calling
+// ScheduleNativeEventCallback().
+//
+// protected virtual
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTerminated)
+ return;
+
+ // Each AddRef() here is normally balanced by exactly one Release() in
+ // ProcessGeckoEvents(). But there are exceptions, for which see
+ // ProcessGeckoEvents() and Exit().
+ NS_ADDREF_THIS();
+ PR_ATOMIC_INCREMENT(&mNativeEventScheduledDepth);
+
+ // This will invoke ProcessGeckoEvents on the main thread.
+ ::CFRunLoopSourceSignal(mCFRunLoopSource);
+ ::CFRunLoopWakeUp(mCFRunLoop);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Undocumented Cocoa Event Manager function, present in the same form since
+// at least OS X 10.6.
+extern "C" EventAttributes GetEventAttributes(EventRef inEvent);
+
+// ProcessNextNativeEvent
+//
+// If aMayWait is false, process a single native event. If it is true, run
+// the native run loop until stopped by ProcessGeckoEvents.
+//
+// Returns true if more events are waiting in the native event queue.
+//
+// protected virtual
+bool
+nsAppShell::ProcessNextNativeEvent(bool aMayWait)
+{
+ bool moreEvents = false;
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool eventProcessed = false;
+ NSString* currentMode = nil;
+
+ if (mTerminated)
+ return false;
+
+ bool wasRunningEventLoop = mRunningEventLoop;
+ mRunningEventLoop = aMayWait;
+ NSDate* waitUntil = nil;
+ if (aMayWait)
+ waitUntil = [NSDate distantFuture];
+
+ NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
+
+ EventQueueRef currentEventQueue = GetCurrentEventQueue();
+ EventTargetRef eventDispatcherTarget = GetEventDispatcherTarget();
+
+ if (aMayWait) {
+ mozilla::HangMonitor::Suspend();
+ }
+
+ // Only call -[NSApp sendEvent:] (and indirectly send user-input events to
+ // Gecko) if aMayWait is true. Tbis ensures most calls to -[NSApp
+ // sendEvent:] happen under nsAppShell::Run(), at the lowest level of
+ // recursion -- thereby making it less likely Gecko will process user-input
+ // events in the wrong order or skip some of them. It also avoids eating
+ // too much CPU in nsBaseAppShell::OnProcessNextEvent() (which calls
+ // us) -- thereby avoiding the starvation of nsIRunnable events in
+ // nsThread::ProcessNextEvent(). For more information see bug 996848.
+ do {
+ // No autorelease pool is provided here, because OnProcessNextEvent
+ // and AfterProcessNextEvent are responsible for maintaining it.
+ NS_ASSERTION(mAutoreleasePools && ::CFArrayGetCount(mAutoreleasePools),
+ "No autorelease pool for native event");
+
+ if (aMayWait) {
+ currentMode = [currentRunLoop currentMode];
+ if (!currentMode)
+ currentMode = NSDefaultRunLoopMode;
+ NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:waitUntil
+ inMode:currentMode
+ dequeue:YES];
+ if (nextEvent) {
+ mozilla::HangMonitor::NotifyActivity();
+ [NSApp sendEvent:nextEvent];
+ eventProcessed = true;
+ }
+ } else {
+ // AcquireFirstMatchingEventInQueue() doesn't spin the (native) event
+ // loop, though it does queue up any newly available events from the
+ // window server.
+ EventRef currentEvent = AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
+ kEventQueueOptionsNone);
+ if (!currentEvent) {
+ continue;
+ }
+ EventAttributes attrs = GetEventAttributes(currentEvent);
+ UInt32 eventKind = GetEventKind(currentEvent);
+ UInt32 eventClass = GetEventClass(currentEvent);
+ bool osCocoaEvent =
+ ((eventClass == 'appl') || (eventClass == kEventClassAppleEvent) ||
+ ((eventClass == 'cgs ') && (eventKind != NSApplicationDefined)));
+ // If attrs is kEventAttributeUserEvent or kEventAttributeMonitored
+ // (i.e. a user input event), we shouldn't process it here while
+ // aMayWait is false. Likewise if currentEvent will eventually be
+ // turned into an OS-defined Cocoa event, or otherwise needs AppKit
+ // processing. Doing otherwise risks doing too much work here, and
+ // preventing the event from being properly processed by the AppKit
+ // framework.
+ if ((attrs != kEventAttributeNone) || osCocoaEvent) {
+ // Since we can't process the next event here (while aMayWait is false),
+ // we want moreEvents to be false on return.
+ eventProcessed = false;
+ // This call to ReleaseEvent() matches a call to RetainEvent() in
+ // AcquireFirstMatchingEventInQueue() above.
+ ReleaseEvent(currentEvent);
+ break;
+ }
+ // This call to RetainEvent() matches a call to ReleaseEvent() in
+ // RemoveEventFromQueue() below.
+ RetainEvent(currentEvent);
+ RemoveEventFromQueue(currentEventQueue, currentEvent);
+ SendEventToEventTarget(currentEvent, eventDispatcherTarget);
+ // This call to ReleaseEvent() matches a call to RetainEvent() in
+ // AcquireFirstMatchingEventInQueue() above.
+ ReleaseEvent(currentEvent);
+ eventProcessed = true;
+ }
+ } while (mRunningEventLoop);
+
+ if (eventProcessed) {
+ moreEvents =
+ (AcquireFirstMatchingEventInQueue(currentEventQueue, 0, NULL,
+ kEventQueueOptionsNone) != NULL);
+ }
+
+ mRunningEventLoop = wasRunningEventLoop;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+
+ if (!moreEvents) {
+ nsChildView::UpdateCurrentInputEventCount();
+ }
+
+ return moreEvents;
+}
+
+// Run
+//
+// Overrides the base class's Run() method to call [NSApp run] (which spins
+// the native run loop until the application quits). Since (unlike the base
+// class's Run() method) we don't process any Gecko events here, they need
+// to be processed elsewhere (in NativeEventCallback(), called from
+// ProcessGeckoEvents()).
+//
+// Camino called [NSApp run] on its own (via NSApplicationMain()), and so
+// didn't call nsAppShell::Run().
+//
+// public
+NS_IMETHODIMP
+nsAppShell::Run(void)
+{
+ NS_ASSERTION(!mStarted, "nsAppShell::Run() called multiple times");
+ if (mStarted || mTerminated)
+ return NS_OK;
+
+ mStarted = true;
+
+ AddScreenWakeLockListener();
+
+ NS_OBJC_TRY_ABORT([NSApp run]);
+
+ RemoveScreenWakeLockListener();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // This method is currently called more than once -- from (according to
+ // mento) an nsAppExitEvent dispatched by nsAppStartup::Quit() and from an
+ // XPCOM shutdown notification that nsBaseAppShell has registered to
+ // receive. So we need to ensure that multiple calls won't break anything.
+ // But we should also complain about it (since it isn't quite kosher).
+ if (mTerminated) {
+ NS_WARNING("nsAppShell::Exit() called redundantly");
+ return NS_OK;
+ }
+
+ mTerminated = true;
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ nsSandboxViolationSink::Stop();
+#endif
+
+ // Quoting from Apple's doc on the [NSApplication stop:] method (from their
+ // doc on the NSApplication class): "If this method is invoked during a
+ // modal event loop, it will break that loop but not the main event loop."
+ // nsAppShell::Exit() shouldn't be called from a modal event loop. So if
+ // it is we complain about it (to users of debug builds) and call [NSApp
+ // stop:] one extra time. (I'm not sure if modal event loops can be nested
+ // -- Apple's docs don't say one way or the other. But the return value
+ // of [NSApp _isRunningModal] doesn't change immediately after a call to
+ // [NSApp stop:], so we have to assume that one extra call to [NSApp stop:]
+ // will do the job.)
+ BOOL cocoaModal = [NSApp _isRunningModal];
+ NS_ASSERTION(!cocoaModal,
+ "Don't call nsAppShell::Exit() from a modal event loop!");
+ if (cocoaModal)
+ [NSApp stop:nullptr];
+ [NSApp stop:nullptr];
+
+ // A call to Exit() just after a call to ScheduleNativeEventCallback()
+ // prevents the (normally) matching call to ProcessGeckoEvents() from
+ // happening. If we've been called from ProcessGeckoEvents() (as usually
+ // happens), we take care of it there. But if we have an unbalanced call
+ // to ScheduleNativeEventCallback() and ProcessGeckoEvents() isn't on the
+ // stack, we need to take care of the problem here.
+ if (!mNativeEventCallbackDepth && mNativeEventScheduledDepth) {
+ int32_t releaseCount = PR_ATOMIC_SET(&mNativeEventScheduledDepth, 0);
+ while (releaseCount-- > 0)
+ NS_RELEASE_THIS();
+ }
+
+ return nsBaseAppShell::Exit();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// OnProcessNextEvent
+//
+// This nsIThreadObserver method is called prior to processing an event.
+// Set up an autorelease pool that will service any autoreleased Cocoa
+// objects during this event. This includes native events processed by
+// ProcessNextNativeEvent. The autorelease pool will be popped by
+// AfterProcessNextEvent, it is important for these two methods to be
+// tightly coupled.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::OnProcessNextEvent(nsIThreadInternal *aThread, bool aMayWait)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_ASSERTION(mAutoreleasePools,
+ "No stack on which to store autorelease pool");
+
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+ ::CFArrayAppendValue(mAutoreleasePools, pool);
+
+ return nsBaseAppShell::OnProcessNextEvent(aThread, aMayWait);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// AfterProcessNextEvent
+//
+// This nsIThreadObserver method is called after event processing is complete.
+// The Cocoa implementation cleans up the autorelease pool create by the
+// previous OnProcessNextEvent call.
+//
+// public
+NS_IMETHODIMP
+nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
+ bool aEventWasProcessed)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ CFIndex count = ::CFArrayGetCount(mAutoreleasePools);
+
+ NS_ASSERTION(mAutoreleasePools && count,
+ "Processed an event, but there's no autorelease pool?");
+
+ const NSAutoreleasePool* pool = static_cast<const NSAutoreleasePool*>
+ (::CFArrayGetValueAtIndex(mAutoreleasePools, count - 1));
+ ::CFArrayRemoveValueAtIndex(mAutoreleasePools, count - 1);
+ [pool release];
+
+ return nsBaseAppShell::AfterProcessNextEvent(aThread, aEventWasProcessed);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+
+// AppShellDelegate implementation
+
+
+@implementation AppShellDelegate
+// initWithAppShell:
+//
+// Constructs the AppShellDelegate object
+- (id)initWithAppShell:(nsAppShell*)aAppShell
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [self init])) {
+ mAppShell = aAppShell;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationWillTerminate:)
+ name:NSApplicationWillTerminateNotification
+ object:NSApp];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationDidBecomeActive:)
+ name:NSApplicationDidBecomeActiveNotification
+ object:NSApp];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(beginMenuTracking:)
+ name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:nil];
+ }
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationWillTerminate:
+//
+// Notify the nsAppShell that native event processing should be discontinued.
+- (void)applicationWillTerminate:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mAppShell->WillTerminate();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// applicationDidBecomeActive
+//
+// Make sure TextInputHandler::sLastModifierState is updated when we become
+// active (since we won't have received [ChildView flagsChanged:] messages
+// while inactive).
+- (void)applicationDidBecomeActive:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // [NSEvent modifierFlags] is valid on every kind of event, so we don't need
+ // to worry about getting an NSInternalInconsistencyException here.
+ NSEvent* currentEvent = [NSApp currentEvent];
+ if (currentEvent) {
+ TextInputHandler::sLastModifierState =
+ [currentEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// beginMenuTracking
+//
+// Roll up our context menu (if any) when some other app (or the OS) opens
+// any sort of menu. But make sure we don't do this for notifications we
+// send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
+- (void)beginMenuTracking:(NSNotification*)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSString *sender = [aNotification object];
+ if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget)
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// We hook terminate: in order to make OS-initiated termination work nicely
+// with Gecko's shutdown sequence. (Two ways to trigger OS-initiated
+// termination: 1) Quit from the Dock menu; 2) Log out from (or shut down)
+// your computer while the browser is active.)
+@interface NSApplication (MethodSwizzling)
+- (void)nsAppShell_NSApplication_terminate:(id)sender;
+@end
+
+@implementation NSApplication (MethodSwizzling)
+
+// Called by the OS after [MacApplicationDelegate applicationShouldTerminate:]
+// has returned NSTerminateNow. This method "subclasses" and replaces the
+// OS's original implementation. The only thing the orginal method does which
+// we need is that it posts NSApplicationWillTerminateNotification. Everything
+// else is unneeded (because it's handled elsewhere), or actively interferes
+// with Gecko's shutdown sequence. For example the original terminate: method
+// causes the app to exit() inside [NSApp run] (called from nsAppShell::Run()
+// above), which means that nothing runs after the call to nsAppStartup::Run()
+// in XRE_Main(), which in particular means that ScopedXPCOMStartup's destructor
+// and NS_ShutdownXPCOM() never get called.
+- (void)nsAppShell_NSApplication_terminate:(id)sender
+{
+ [[NSNotificationCenter defaultCenter] postNotificationName:NSApplicationWillTerminateNotification
+ object:NSApp];
+}
+
+@end
diff --git a/widget/cocoa/nsBidiKeyboard.h b/widget/cocoa/nsBidiKeyboard.h
new file mode 100644
index 0000000000..e7e7ac8722
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsBidiKeyboard_h_
+#define nsBidiKeyboard_h_
+
+#include "nsIBidiKeyboard.h"
+
+class nsBidiKeyboard : public nsIBidiKeyboard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+protected:
+ virtual ~nsBidiKeyboard();
+};
+
+#endif // nsBidiKeyboard_h_
diff --git a/widget/cocoa/nsBidiKeyboard.mm b/widget/cocoa/nsBidiKeyboard.mm
new file mode 100644
index 0000000000..e0fc86aeb7
--- /dev/null
+++ b/widget/cocoa/nsBidiKeyboard.mm
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsBidiKeyboard.h"
+#include "nsCocoaUtils.h"
+#include "TextInputHandler.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard() : nsIBidiKeyboard()
+{
+ Reset();
+}
+
+nsBidiKeyboard::~nsBidiKeyboard()
+{
+}
+
+NS_IMETHODIMP nsBidiKeyboard::Reset()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::IsLangRTL(bool *aIsRTL)
+{
+ *aIsRTL = TISInputSourceWrapper::CurrentInputSource().IsForRTLLanguage();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult)
+{
+ // not implemented yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/widget/cocoa/nsChangeObserver.h b/widget/cocoa/nsChangeObserver.h
new file mode 100644
index 0000000000..1b9a001735
--- /dev/null
+++ b/widget/cocoa/nsChangeObserver.h
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 2; 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/. */
+
+#ifndef nsChangeObserver_h_
+#define nsChangeObserver_h_
+
+class nsIContent;
+class nsIDocument;
+class nsIAtom;
+
+#define NS_DECL_CHANGEOBSERVER \
+void ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute) override; \
+void ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer) override; \
+void ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsIContent *aChild) override;
+
+// Something that wants to be alerted to changes in attributes or changes in
+// its corresponding content object.
+//
+// This interface is used by our menu code so we only have to have one
+// nsIDocumentObserver.
+//
+// Any class that implements this interface must take care to unregister itself
+// on deletion.
+class nsChangeObserver
+{
+public:
+ // XXX use dom::Element
+ virtual void ObserveAttributeChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ nsIAtom* aAttribute)=0;
+
+ virtual void ObserveContentRemoved(nsIDocument* aDocument,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)=0;
+
+ virtual void ObserveContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild)=0;
+};
+
+#endif // nsChangeObserver_h_
diff --git a/widget/cocoa/nsChildView.h b/widget/cocoa/nsChildView.h
new file mode 100644
index 0000000000..2817c8d415
--- /dev/null
+++ b/widget/cocoa/nsChildView.h
@@ -0,0 +1,666 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsChildView_h_
+#define nsChildView_h_
+
+// formal protocols
+#include "mozView.h"
+#ifdef ACCESSIBILITY
+#include "mozilla/a11y/Accessible.h"
+#include "mozAccessibleProtocol.h"
+#endif
+
+#include "nsISupports.h"
+#include "nsBaseWidget.h"
+#include "nsWeakPtr.h"
+#include "TextInputHandler.h"
+#include "nsCocoaUtils.h"
+#include "gfxQuartzSurface.h"
+#include "GLContextTypes.h"
+#include "mozilla/Mutex.h"
+#include "nsRegion.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsString.h"
+#include "nsIDragService.h"
+#include "ViewRegion.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <AppKit/NSOpenGL.h>
+
+class nsChildView;
+class nsCocoaWindow;
+
+namespace {
+class GLPresenter;
+} // namespace
+
+namespace mozilla {
+class InputData;
+class PanGestureInput;
+class SwipeTracker;
+struct SwipeEventQueue;
+class VibrancyManager;
+namespace layers {
+class GLManager;
+class IAPZCTreeManager;
+} // namespace layers
+namespace widget {
+class RectTextureImage;
+class WidgetRenderingContext;
+} // namespace widget
+} // namespace mozilla
+
+@class PixelHostingView;
+
+@interface NSEvent (Undocumented)
+
+// Return Cocoa event's corresponding Carbon event. Not initialized (on
+// synthetic events) until the OS actually "sends" the event. This method
+// has been present in the same form since at least OS X 10.2.8.
+- (EventRef)_eventRef;
+
+@end
+
+@interface NSView (Undocumented)
+
+// Draws the title string of a window.
+// Present on NSThemeFrame since at least 10.6.
+// _drawTitleBar is somewhat complex, and has changed over the years
+// since OS X 10.6. But in that time it's never done anything that
+// would break when called outside of -[NSView drawRect:] (which we
+// sometimes do), or whose output can't be redirected to a
+// CGContextRef object (which we also sometimes do). This is likely
+// to remain true for the indefinite future. However we should
+// check _drawTitleBar in each new major version of OS X. For more
+// information see bug 877767.
+- (void)_drawTitleBar:(NSRect)aRect;
+
+// Returns an NSRect that is the bounding box for all an NSView's dirty
+// rectangles (ones that need to be redrawn). The full list of dirty
+// rectangles can be obtained by calling -[NSView _dirtyRegion] and then
+// calling -[NSRegion getRects:count:] on what it returns. Both these
+// methods have been present in the same form since at least OS X 10.5.
+// Unlike -[NSView getRectsBeingDrawn:count:], these methods can be called
+// outside a call to -[NSView drawRect:].
+- (NSRect)_dirtyRect;
+
+// Undocumented method of one or more of NSFrameView's subclasses. Called
+// when one or more of the titlebar buttons needs to be repositioned, to
+// disappear, or to reappear (say if the window's style changes). If
+// 'redisplay' is true, the entire titlebar (the window's top 22 pixels) is
+// marked as needing redisplay. This method has been present in the same
+// format since at least OS X 10.5.
+- (void)_tileTitlebarAndRedisplay:(BOOL)redisplay;
+
+// The following undocumented methods are used to work around bug 1069658,
+// which is an Apple bug or design flaw that effects Yosemite. None of them
+// were present prior to Yosemite (OS X 10.10).
+- (NSView *)titlebarView; // Method of NSThemeFrame
+- (NSView *)titlebarContainerView; // Method of NSThemeFrame
+- (BOOL)transparent; // Method of NSTitlebarView and NSTitlebarContainerView
+- (void)setTransparent:(BOOL)transparent; // Method of NSTitlebarView and
+ // NSTitlebarContainerView
+
+@end
+
+@interface ChildView : NSView<
+#ifdef ACCESSIBILITY
+ mozAccessible,
+#endif
+ mozView, NSTextInputClient>
+{
+@private
+ // the nsChildView that created the view. It retains this NSView, so
+ // the link back to it must be weak.
+ nsChildView* mGeckoChild;
+
+ // Text input handler for mGeckoChild and us. Note that this is a weak
+ // reference. Ideally, this should be a strong reference but a ChildView
+ // object can live longer than the mGeckoChild that owns it. And if
+ // mTextInputHandler were a strong reference, this would make it difficult
+ // for Gecko's leak detector to detect leaked TextInputHandler objects.
+ // This is initialized by [mozView installTextInputHandler:aHandler] and
+ // cleared by [mozView uninstallTextInputHandler].
+ mozilla::widget::TextInputHandler* mTextInputHandler; // [WEAK]
+
+ // when mouseDown: is called, we store its event here (strong)
+ NSEvent* mLastMouseDownEvent;
+
+ // Needed for IME support in e10s mode. Strong.
+ NSEvent* mLastKeyDownEvent;
+
+ // Whether the last mouse down event was blocked from Gecko.
+ BOOL mBlockedLastMouseDown;
+
+ // when acceptsFirstMouse: is called, we store the event here (strong)
+ NSEvent* mClickThroughMouseDownEvent;
+
+ // WheelStart/Stop events should always come in pairs. This BOOL records the
+ // last received event so that, when we receive one of the events, we make sure
+ // to send its pair event first, in case we didn't yet for any reason.
+ BOOL mExpectingWheelStop;
+
+ // Set to YES when our GL surface has been updated and we need to call
+ // updateGLContext before we composite.
+ BOOL mNeedsGLUpdate;
+
+ // Holds our drag service across multiple drag calls. The reference to the
+ // service is obtained when the mouse enters the view and is released when
+ // the mouse exits or there is a drop. This prevents us from having to
+ // re-establish the connection to the service manager many times per second
+ // when handling |draggingUpdated:| messages.
+ nsIDragService* mDragService;
+
+ NSOpenGLContext *mGLContext;
+
+ // Simple gestures support
+ //
+ // mGestureState is used to detect when Cocoa has called both
+ // magnifyWithEvent and rotateWithEvent within the same
+ // beginGestureWithEvent and endGestureWithEvent sequence. We
+ // discard the spurious gesture event so as not to confuse Gecko.
+ //
+ // mCumulativeMagnification keeps track of the total amount of
+ // magnification peformed during a magnify gesture so that we can
+ // send that value with the final MozMagnifyGesture event.
+ //
+ // mCumulativeRotation keeps track of the total amount of rotation
+ // performed during a rotate gesture so we can send that value with
+ // the final MozRotateGesture event.
+ enum {
+ eGestureState_None,
+ eGestureState_StartGesture,
+ eGestureState_MagnifyGesture,
+ eGestureState_RotateGesture
+ } mGestureState;
+ float mCumulativeMagnification;
+ float mCumulativeRotation;
+
+#ifdef __LP64__
+ // Support for fluid swipe tracking.
+ BOOL* mCancelSwipeAnimation;
+#endif
+
+ // Whether this uses off-main-thread compositing.
+ BOOL mUsingOMTCompositor;
+
+ // The mask image that's used when painting into the titlebar using basic
+ // CGContext painting (i.e. non-accelerated).
+ CGImageRef mTopLeftCornerMask;
+
+ // Subviews of self, which act as container views for vibrancy views and
+ // non-draggable views.
+ NSView* mVibrancyViewsContainer; // [STRONG]
+ NSView* mNonDraggableViewsContainer; // [STRONG]
+
+ // The view that does our drawing. This is a subview of self so that it can
+ // be ordered on top of mVibrancyViewsContainer.
+ PixelHostingView* mPixelHostingView;
+}
+
+// class initialization
++ (void)initialize;
+
++ (void)registerViewForDraggedTypes:(NSView*)aView;
+
+// these are sent to the first responder when the window key status changes
+- (void)viewsWindowDidBecomeKey;
+- (void)viewsWindowDidResignKey;
+
+// Stop NSView hierarchy being changed during [ChildView drawRect:]
+- (void)delayedTearDown;
+
+- (void)sendFocusEvent:(mozilla::EventMessage)eventMessage;
+
+- (void)handleMouseMoved:(NSEvent*)aEvent;
+
+- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
+ enter:(BOOL)aEnter
+ exitFrom:(mozilla::WidgetMouseEvent::ExitFrom)aExitFrom;
+
+- (void)updateGLContext;
+- (void)_surfaceNeedsUpdate:(NSNotification*)notification;
+
+- (bool)preRender:(NSOpenGLContext *)aGLContext;
+- (void)postRender:(NSOpenGLContext *)aGLContext;
+
+- (NSView*)vibrancyViewsContainer;
+- (NSView*)nonDraggableViewsContainer;
+- (NSView*)pixelHostingView;
+
+- (BOOL)isCoveringTitlebar;
+
+- (void)viewWillStartLiveResize;
+- (void)viewDidEndLiveResize;
+
+- (NSColor*)vibrancyFillColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType;
+- (NSColor*)vibrancyFontSmoothingBackgroundColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType;
+
+// Simple gestures support
+//
+// XXX - The swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
+// rotateWithEvent, and endGestureWithEvent methods are part of a
+// PRIVATE interface exported by nsResponder and reverse-engineering
+// was necessary to obtain the methods' prototypes. Thus, Apple may
+// change the interface in the future without notice.
+//
+// The prototypes were obtained from the following link:
+// http://cocoadex.com/2008/02/nsevent-modifications-swipe-ro.html
+- (void)swipeWithEvent:(NSEvent *)anEvent;
+- (void)beginGestureWithEvent:(NSEvent *)anEvent;
+- (void)magnifyWithEvent:(NSEvent *)anEvent;
+- (void)smartMagnifyWithEvent:(NSEvent *)anEvent;
+- (void)rotateWithEvent:(NSEvent *)anEvent;
+- (void)endGestureWithEvent:(NSEvent *)anEvent;
+
+- (void)scrollWheel:(NSEvent *)anEvent;
+
+- (void)setUsingOMTCompositor:(BOOL)aUseOMTC;
+
+- (NSEvent*)lastKeyDownEvent;
+@end
+
+class ChildViewMouseTracker {
+
+public:
+
+ static void MouseMoved(NSEvent* aEvent);
+ static void MouseScrolled(NSEvent* aEvent);
+ static void OnDestroyView(ChildView* aView);
+ static void OnDestroyWindow(NSWindow* aWindow);
+ static BOOL WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent,
+ ChildView* aView, BOOL isClickThrough = NO);
+ static void MouseExitedWindow(NSEvent* aEvent);
+ static void MouseEnteredWindow(NSEvent* aEvent);
+ static void ReEvaluateMouseEnterState(NSEvent* aEvent = nil, ChildView* aOldView = nil);
+ static void ResendLastMouseMoveEvent();
+ static ChildView* ViewForEvent(NSEvent* aEvent);
+
+ static ChildView* sLastMouseEventView;
+ static NSEvent* sLastMouseMoveEvent;
+ static NSWindow* sWindowUnderMouse;
+ static NSPoint sLastScrollEventScreenLocation;
+};
+
+//-------------------------------------------------------------------------
+//
+// nsChildView
+//
+//-------------------------------------------------------------------------
+
+class nsChildView : public nsBaseWidget
+{
+private:
+ typedef nsBaseWidget Inherited;
+ typedef mozilla::layers::IAPZCTreeManager IAPZCTreeManager;
+
+public:
+ nsChildView();
+
+ // nsIWidget interface
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+
+ virtual void Destroy() override;
+
+ NS_IMETHOD Show(bool aState) override;
+ virtual bool IsVisible() const override;
+
+ NS_IMETHOD SetParent(nsIWidget* aNewParent) override;
+ virtual nsIWidget* GetParent(void) override;
+ virtual float GetDPI() override;
+
+ NS_IMETHOD Move(double aX, double aY) override;
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY,
+ double aWidth, double aHeight, bool aRepaint) override;
+
+ NS_IMETHOD Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ NS_IMETHOD SetFocus(bool aRaise) override;
+ virtual LayoutDeviceIntRect GetBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+
+ // Returns the "backing scale factor" of the view's window, which is the
+ // ratio of pixels in the window's backing store to Cocoa points. Prior to
+ // HiDPI support in OS X 10.7, this was always 1.0, but in HiDPI mode it
+ // will be 2.0 (and might potentially other values as screen resolutions
+ // evolve). This gives the relationship between what Gecko calls "device
+ // pixels" and the Cocoa "points" coordinate system.
+ CGFloat BackingScaleFactor() const;
+
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final {
+ return mozilla::DesktopToLayoutDeviceScale(BackingScaleFactor());
+ }
+
+ // Call if the window's backing scale factor changes - i.e., it is moved
+ // between HiDPI and non-HiDPI screens
+ void BackingScaleFactorChanged();
+
+ virtual double GetDefaultScaleInternal() override;
+
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect &aRect) override;
+
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual bool ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect) override;
+
+ static bool ConvertStatus(nsEventStatus aStatus)
+ { return aStatus == nsEventStatus_eConsumeNoDefault; }
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ virtual bool WidgetTypeSupportsAcceleration() override;
+ virtual bool ShouldUseOffMainThreadCompositing() override;
+
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor, uint32_t aHotspotX, uint32_t aHotspotY) override;
+
+ NS_IMETHOD SetTitle(const nsAString& title) override;
+
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override;
+
+ virtual bool HasPendingInputEvent() override;
+
+ NS_IMETHOD ActivateNativeMenuItemAt(const nsAString& indexString) override;
+ NS_IMETHOD ForceUpdateNativeMenuAt(const nsAString& indexString) override;
+ NS_IMETHOD GetSelectionAsPlaintext(nsAString& aResult) override;
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override;
+ NS_IMETHOD_(TextEventDispatcherListener*)
+ GetNativeTextEventDispatcherListener() override;
+ NS_IMETHOD AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent) override;
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+ bool ExecuteNativeKeyBindingRemapped(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aCocoaKeyCode);
+ virtual nsIMEUpdatePreference GetIMEUpdatePreference() override;
+
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+
+ virtual nsresult SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override
+ { return SynthesizeNativeMouseEvent(aPoint, NSMouseMoved, 0, aObserver); }
+ virtual nsresult SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ double aDeltaX,
+ double aDeltaY,
+ double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver) override;
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+
+ // Mac specific methods
+
+ virtual bool DispatchWindowEvent(mozilla::WidgetGUIEvent& event);
+
+ void WillPaintWindow();
+ bool PaintWindow(LayoutDeviceIntRegion aRegion);
+ bool PaintWindowInContext(CGContextRef aContext, const LayoutDeviceIntRegion& aRegion,
+ mozilla::gfx::IntSize aSurfaceSize);
+
+#ifdef ACCESSIBILITY
+ already_AddRefed<mozilla::a11y::Accessible> GetDocumentAccessible();
+#endif
+
+ virtual void CreateCompositor() override;
+ virtual void PrepareWindowEffects() override;
+ virtual void CleanupWindowEffects() override;
+ virtual bool PreRender(mozilla::widget::WidgetRenderingContext* aContext) override;
+ virtual void PostRender(mozilla::widget::WidgetRenderingContext* aContext) override;
+ virtual void DrawWindowOverlay(mozilla::widget::WidgetRenderingContext* aManager,
+ LayoutDeviceIntRect aRect) override;
+
+ virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+
+ virtual void UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion) override;
+ LayoutDeviceIntRegion GetNonDraggableRegion() { return mNonDraggableRegion.Region(); }
+
+ virtual void ReportSwipeStarted(uint64_t aInputBlockId, bool aStartSwipe) override;
+
+ virtual void LookUpDictionary(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical,
+ const LayoutDeviceIntPoint& aPoint) override;
+
+ void ResetParent();
+
+ static bool DoHasPendingInputEvent();
+ static uint32_t GetCurrentInputEventCount();
+ static void UpdateCurrentInputEventCount();
+
+ NSView<mozView>* GetEditorView();
+
+ nsCocoaWindow* GetXULWindowWidget();
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ mozilla::widget::TextInputHandler* GetTextInputHandler()
+ {
+ return mTextInputHandler;
+ }
+
+ void ClearVibrantAreas();
+ NSColor* VibrancyFillColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType);
+ NSColor* VibrancyFontSmoothingBackgroundColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType);
+
+ // unit conversion convenience functions
+ int32_t CocoaPointsToDevPixels(CGFloat aPts) const {
+ return nsCocoaUtils::CocoaPointsToDevPixels(aPts, BackingScaleFactor());
+ }
+ LayoutDeviceIntPoint CocoaPointsToDevPixels(const NSPoint& aPt) const {
+ return nsCocoaUtils::CocoaPointsToDevPixels(aPt, BackingScaleFactor());
+ }
+ LayoutDeviceIntPoint CocoaPointsToDevPixelsRoundDown(const NSPoint& aPt) const {
+ return nsCocoaUtils::CocoaPointsToDevPixelsRoundDown(aPt, BackingScaleFactor());
+ }
+ LayoutDeviceIntRect CocoaPointsToDevPixels(const NSRect& aRect) const {
+ return nsCocoaUtils::CocoaPointsToDevPixels(aRect, BackingScaleFactor());
+ }
+ CGFloat DevPixelsToCocoaPoints(int32_t aPixels) const {
+ return nsCocoaUtils::DevPixelsToCocoaPoints(aPixels, BackingScaleFactor());
+ }
+ NSRect DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect) const {
+ return nsCocoaUtils::DevPixelsToCocoaPoints(aRect, BackingScaleFactor());
+ }
+
+ already_AddRefed<mozilla::gfx::DrawTarget>
+ StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ mozilla::layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawing() override;
+ void CleanupRemoteDrawing() override;
+ bool InitCompositor(mozilla::layers::Compositor* aCompositor) override;
+
+ NS_IMETHOD StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted) override;
+
+ virtual void SetPluginFocused(bool& aFocused) override;
+
+ bool IsPluginFocused() { return mPluginFocused; }
+
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+
+ void DispatchAPZWheelInputEvent(mozilla::InputData& aEvent, bool aCanTriggerSwipe);
+
+ void SwipeFinished();
+
+protected:
+ virtual ~nsChildView();
+
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+
+ void TearDownView();
+
+ virtual already_AddRefed<nsIWidget>
+ AllocateChildPopupWidget() override
+ {
+ static NS_DEFINE_IID(kCPopUpCID, NS_POPUP_CID);
+ nsCOMPtr<nsIWidget> widget = do_CreateInstance(kCPopUpCID);
+ return widget.forget();
+ }
+
+ void ConfigureAPZCTreeManager() override;
+ void ConfigureAPZControllerThread() override;
+
+ void DoRemoteComposition(const LayoutDeviceIntRect& aRenderRect);
+
+ // Overlay drawing functions for OpenGL drawing
+ void DrawWindowOverlay(mozilla::layers::GLManager* aManager, LayoutDeviceIntRect aRect);
+ void MaybeDrawResizeIndicator(mozilla::layers::GLManager* aManager);
+ void MaybeDrawRoundedCorners(mozilla::layers::GLManager* aManager, const LayoutDeviceIntRect& aRect);
+ void MaybeDrawTitlebar(mozilla::layers::GLManager* aManager);
+
+ // Redraw the contents of mTitlebarCGContext on the main thread, as
+ // determined by mDirtyTitlebarRegion.
+ void UpdateTitlebarCGContext();
+
+ LayoutDeviceIntRect RectContainingTitlebarControls();
+ void UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries);
+ mozilla::VibrancyManager& EnsureVibrancyManager();
+
+ nsIWidget* GetWidgetForListenerEvents();
+
+ struct SwipeInfo {
+ bool wantsSwipe;
+ uint32_t allowedDirections;
+ };
+
+ SwipeInfo SendMayStartSwipe(const mozilla::PanGestureInput& aSwipeStartEvent);
+ void TrackScrollEventAsSwipe(const mozilla::PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections);
+
+protected:
+
+ ChildView<mozView>* mView; // my parallel cocoa view, [STRONG]
+ RefPtr<mozilla::widget::TextInputHandler> mTextInputHandler;
+ InputContext mInputContext;
+
+ NSView<mozView>* mParentView;
+ nsIWidget* mParentWidget;
+
+#ifdef ACCESSIBILITY
+ // weak ref to this childview's associated mozAccessible for speed reasons
+ // (we get queried for it *a lot* but don't want to own it)
+ nsWeakPtr mAccessible;
+#endif
+
+ // Protects the view from being teared down while a composition is in
+ // progress on the compositor thread.
+ mozilla::Mutex mViewTearDownLock;
+
+ mozilla::Mutex mEffectsLock;
+
+ // May be accessed from any thread, protected
+ // by mEffectsLock.
+ bool mShowsResizeIndicator;
+ LayoutDeviceIntRect mResizeIndicatorRect;
+ bool mHasRoundedBottomCorners;
+ int mDevPixelCornerRadius;
+ bool mIsCoveringTitlebar;
+ bool mIsFullscreen;
+ bool mIsOpaque;
+ LayoutDeviceIntRect mTitlebarRect;
+
+ // The area of mTitlebarCGContext that needs to be redrawn during the next
+ // transaction. Accessed from any thread, protected by mEffectsLock.
+ LayoutDeviceIntRegion mUpdatedTitlebarRegion;
+ CGContextRef mTitlebarCGContext;
+
+ // Compositor thread only
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mResizerImage;
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mCornerMaskImage;
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mTitlebarImage;
+ mozilla::UniquePtr<mozilla::widget::RectTextureImage> mBasicCompositorImage;
+
+ // The area of mTitlebarCGContext that has changed and needs to be
+ // uploaded to to mTitlebarImage. Main thread only.
+ nsIntRegion mDirtyTitlebarRegion;
+
+ mozilla::ViewRegion mNonDraggableRegion;
+
+ // Cached value of [mView backingScaleFactor], to avoid sending two obj-c
+ // messages (respondsToSelector, backingScaleFactor) every time we need to
+ // use it.
+ // ** We'll need to reinitialize this if the backing resolution changes. **
+ mutable CGFloat mBackingScaleFactor;
+
+ bool mVisible;
+ bool mDrawing;
+ bool mIsDispatchPaint; // Is a paint event being dispatched
+
+ bool mPluginFocused;
+
+ // Used in OMTC BasicLayers mode. Presents the BasicCompositor result
+ // surface to the screen using an OpenGL context.
+ mozilla::UniquePtr<GLPresenter> mGLPresenter;
+
+ mozilla::UniquePtr<mozilla::VibrancyManager> mVibrancyManager;
+ RefPtr<mozilla::SwipeTracker> mSwipeTracker;
+ mozilla::UniquePtr<mozilla::SwipeEventQueue> mSwipeEventQueue;
+
+ // Only used for drawRect-based painting in popups.
+ RefPtr<mozilla::gfx::DrawTarget> mBackingSurface;
+
+ // This flag is only used when APZ is off. It indicates that the current pan
+ // gesture was processed as a swipe. Sometimes the swipe animation can finish
+ // before momentum events of the pan gesture have stopped firing, so this
+ // flag tells us that we shouldn't allow the remaining events to cause
+ // scrolling. It is reset to false once a new gesture starts (as indicated by
+ // a PANGESTURE_(MAY)START event).
+ bool mCurrentPanGestureBelongsToSwipe;
+
+ static uint32_t sLastInputEventCount;
+
+ void ReleaseTitlebarCGContext();
+
+ // This is used by SynthesizeNativeTouchPoint to maintain state between
+ // multiple synthesized points
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+};
+
+#endif // nsChildView_h_
diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm
new file mode 100644
index 0000000000..868687fe16
--- /dev/null
+++ b/widget/cocoa/nsChildView.mm
@@ -0,0 +1,6151 @@
+/* -*- Mode: objc; tab-width: 2; 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 "mozilla/ArrayUtils.h"
+
+#include "mozilla/Logging.h"
+
+#include <unistd.h>
+#include <math.h>
+
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+
+#include "nsArrayUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+
+#include "nsFontMetrics.h"
+#include "nsIRollupListener.h"
+#include "nsViewManager.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsGfxCIID.h"
+#include "nsIDOMSimpleGestureEvent.h"
+#include "nsThemeConstants.h"
+#include "nsIWidgetListener.h"
+#include "nsIPresShell.h"
+
+#include "nsDragService.h"
+#include "nsClipboard.h"
+#include "nsCursorManager.h"
+#include "nsWindowMap.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuBarX.h"
+#include "NativeKeyBindings.h"
+#include "ComplexTextInputPanel.h"
+
+#include "gfxContext.h"
+#include "gfxQuartzSurface.h"
+#include "gfxUtils.h"
+#include "nsRegion.h"
+#include "Layers.h"
+#include "ClientLayerManager.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "GfxTexturesReporter.h"
+#include "GLTextureImage.h"
+#include "GLContextProvider.h"
+#include "GLContextCGL.h"
+#include "ScopedGLHelpers.h"
+#include "HeapCopyOfStackArray.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/GLManager.h"
+#include "mozilla/layers/CompositorOGL.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/BasicCompositor.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "gfxUtils.h"
+#include "gfxPrefs.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/gfx/MacIOSurface.h"
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#include "mozilla/a11y/Platform.h"
+#endif
+
+#include "mozilla/Preferences.h"
+
+#include <dlfcn.h>
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include "GeckoProfiler.h"
+
+#include "nsIDOMWheelEvent.h"
+#include "mozilla/layers/ChromeProcessController.h"
+#include "nsLayoutUtils.h"
+#include "InputData.h"
+#include "RectTextureImage.h"
+#include "SwipeTracker.h"
+#include "VibrancyManager.h"
+#include "nsNativeThemeCocoa.h"
+#include "nsIDOMWindowUtils.h"
+#include "Units.h"
+#include "UnitTransforms.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+using namespace mozilla::gl;
+using namespace mozilla::widget;
+
+using mozilla::gfx::Matrix4x4;
+
+#undef DEBUG_UPDATE
+#undef INVALIDATE_DEBUGGING // flash areas as they are invalidated
+
+// Don't put more than this many rects in the dirty region, just fluff
+// out to the bounding-box if there are more
+#define MAX_RECTS_IN_REGION 100
+
+PRLogModuleInfo* sCocoaLog = nullptr;
+
+extern "C" {
+ CG_EXTERN void CGContextResetCTM(CGContextRef);
+ CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
+ CG_EXTERN void CGContextResetClip(CGContextRef);
+
+ typedef CFTypeRef CGSRegionObj;
+ CGError CGSNewRegionWithRect(const CGRect *rect, CGSRegionObj *outRegion);
+ CGError CGSNewRegionWithRectList(const CGRect *rects, int rectCount, CGSRegionObj *outRegion);
+}
+
+// defined in nsMenuBarX.mm
+extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
+
+static bool gChildViewMethodsSwizzled = false;
+
+extern nsIArray *gDraggedTransferables;
+
+ChildView* ChildViewMouseTracker::sLastMouseEventView = nil;
+NSEvent* ChildViewMouseTracker::sLastMouseMoveEvent = nil;
+NSWindow* ChildViewMouseTracker::sWindowUnderMouse = nil;
+NSPoint ChildViewMouseTracker::sLastScrollEventScreenLocation = NSZeroPoint;
+
+#ifdef INVALIDATE_DEBUGGING
+static void blinkRect(Rect* r);
+static void blinkRgn(RgnHandle rgn);
+#endif
+
+bool gUserCancelledDrag = false;
+
+uint32_t nsChildView::sLastInputEventCount = 0;
+
+// The view that will do our drawing or host our NSOpenGLContext or Core Animation layer.
+@interface PixelHostingView : NSView {
+}
+@end
+
+@interface ChildView(Private)
+
+// sets up our view, attaching it to its owning gecko view
+- (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild;
+
+// set up a gecko mouse event based on a cocoa mouse event
+- (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetWheelEvent*)outWheelEvent;
+- (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetInputEvent*)outGeckoEvent;
+
+- (NSMenu*)contextMenu;
+
+- (BOOL)isRectObscuredBySubview:(NSRect)inRect;
+
+- (LayoutDeviceIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect;
+- (BOOL)isUsingOpenGL;
+- (void)drawUsingOpenGL;
+
+- (BOOL)hasRoundedBottomCorners;
+- (CGFloat)cornerRadius;
+- (void)clearCorners;
+
+-(void)setGLOpaque:(BOOL)aOpaque;
+
+// Overlay drawing functions for traditional CGContext drawing
+- (void)drawTitleString;
+- (void)drawTitlebarHighlight;
+- (void)maskTopCornersInContext:(CGContextRef)aContext;
+
+#if USE_CLICK_HOLD_CONTEXTMENU
+ // called on a timer two seconds after a mouse down to see if we should display
+ // a context menu (click-hold)
+- (void)clickHoldCallback:(id)inEvent;
+#endif
+
+#ifdef ACCESSIBILITY
+- (id<mozAccessible>)accessible;
+#endif
+
+- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint;
+- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint;
+
+- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent;
+- (void)updateWindowDraggableState;
+
+- (bool)shouldConsiderStartingSwipeFromEvent:(NSEvent*)aEvent;
+
+@end
+
+@interface NSView(NSThemeFrameCornerRadius)
+- (float)roundedCornerRadius;
+@end
+
+@interface NSWindow(NSWindowShouldZoomOnDoubleClick)
++ (BOOL)_shouldZoomOnDoubleClick; // present on 10.7 and above
+@end
+
+// Starting with 10.7 the bottom corners of all windows are rounded.
+// Unfortunately, the standard rounding that OS X applies to OpenGL views
+// does not use anti-aliasing and looks very crude. Since we want a smooth,
+// anti-aliased curve, we'll draw it ourselves.
+// Additionally, we need to turn off the OS-supplied rounding because it
+// eats into our corner's curve. We do that by overriding an NSSurface method.
+@interface NSSurface @end
+
+@implementation NSSurface(DontCutOffCorners)
+- (CGSRegionObj)_createRoundedBottomRegionForRect:(CGRect)rect
+{
+ // Create a normal rect region without rounded bottom corners.
+ CGSRegionObj region;
+ CGSNewRegionWithRect(&rect, &region);
+ return region;
+}
+@end
+
+#pragma mark -
+
+// Flips a screen coordinate from a point in the cocoa coordinate system (bottom-left rect) to a point
+// that is a "flipped" cocoa coordinate system (starts in the top-left).
+static inline void
+FlipCocoaScreenCoordinate(NSPoint &inPoint)
+{
+ inPoint.y = nsCocoaUtils::FlippedScreenY(inPoint.y);
+}
+
+void EnsureLogInitialized()
+{
+ if (!sCocoaLog) {
+ sCocoaLog = PR_NewLogModule("nsCocoaWidgets");
+ }
+}
+
+namespace {
+
+// Used for OpenGL drawing from the compositor thread for OMTC BasicLayers.
+// We need to use OpenGL for this because there seems to be no other robust
+// way of drawing from a secondary thread without locking, which would cause
+// deadlocks in our setup. See bug 882523.
+class GLPresenter : public GLManager
+{
+public:
+ static mozilla::UniquePtr<GLPresenter> CreateForWindow(nsIWidget* aWindow)
+ {
+ // Contrary to CompositorOGL, we allow unaccelerated OpenGL contexts to be
+ // used. BasicCompositor only requires very basic GL functionality.
+ RefPtr<GLContext> context = gl::GLContextProvider::CreateForWindow(aWindow, false);
+ return context ? MakeUnique<GLPresenter>(context) : nullptr;
+ }
+
+ explicit GLPresenter(GLContext* aContext);
+ virtual ~GLPresenter();
+
+ virtual GLContext* gl() const override { return mGLContext; }
+ virtual ShaderProgramOGL* GetProgram(GLenum aTarget, gfx::SurfaceFormat aFormat) override
+ {
+ MOZ_ASSERT(aTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+ MOZ_ASSERT(aFormat == gfx::SurfaceFormat::R8G8B8A8);
+ return mRGBARectProgram.get();
+ }
+ virtual const gfx::Matrix4x4& GetProjMatrix() const override
+ {
+ return mProjMatrix;
+ }
+ virtual void ActivateProgram(ShaderProgramOGL *aProg) override
+ {
+ mGLContext->fUseProgram(aProg->GetProgram());
+ }
+ virtual void BindAndDrawQuad(ShaderProgramOGL *aProg,
+ const gfx::Rect& aLayerRect,
+ const gfx::Rect& aTextureRect) override;
+
+ void BeginFrame(LayoutDeviceIntSize aRenderSize);
+ void EndFrame();
+
+ NSOpenGLContext* GetNSOpenGLContext()
+ {
+ return GLContextCGL::Cast(mGLContext)->GetNSOpenGLContext();
+ }
+
+protected:
+ RefPtr<mozilla::gl::GLContext> mGLContext;
+ mozilla::UniquePtr<mozilla::layers::ShaderProgramOGL> mRGBARectProgram;
+ gfx::Matrix4x4 mProjMatrix;
+ GLuint mQuadVBO;
+};
+
+} // unnamed namespace
+
+namespace mozilla {
+
+struct SwipeEventQueue {
+ SwipeEventQueue(uint32_t aAllowedDirections, uint64_t aInputBlockId)
+ : allowedDirections(aAllowedDirections)
+ , inputBlockId(aInputBlockId)
+ {}
+
+ nsTArray<PanGestureInput> queuedEvents;
+ uint32_t allowedDirections;
+ uint64_t inputBlockId;
+};
+
+} // namespace mozilla
+
+#pragma mark -
+
+nsChildView::nsChildView() : nsBaseWidget()
+, mView(nullptr)
+, mParentView(nullptr)
+, mParentWidget(nullptr)
+, mViewTearDownLock("ChildViewTearDown")
+, mEffectsLock("WidgetEffects")
+, mShowsResizeIndicator(false)
+, mHasRoundedBottomCorners(false)
+, mIsCoveringTitlebar(false)
+, mIsFullscreen(false)
+, mIsOpaque(false)
+, mTitlebarCGContext(nullptr)
+, mBackingScaleFactor(0.0)
+, mVisible(false)
+, mDrawing(false)
+, mIsDispatchPaint(false)
+{
+ EnsureLogInitialized();
+}
+
+nsChildView::~nsChildView()
+{
+ ReleaseTitlebarCGContext();
+
+ if (mSwipeTracker) {
+ mSwipeTracker->Destroy();
+ mSwipeTracker = nullptr;
+ }
+
+ // Notify the children that we're gone. childView->ResetParent() can change
+ // our list of children while it's being iterated, so the way we iterate the
+ // list must allow for this.
+ for (nsIWidget* kid = mLastChild; kid;) {
+ nsChildView* childView = static_cast<nsChildView*>(kid);
+ kid = kid->GetPrevSibling();
+ childView->ResetParent();
+ }
+
+ NS_WARNING_ASSERTION(
+ mOnDestroyCalled,
+ "nsChildView object destroyed without calling Destroy()");
+
+ DestroyCompositor();
+
+ // An nsChildView object that was in use can be destroyed without Destroy()
+ // ever being called on it. So we also need to do a quick, safe cleanup
+ // here (it's too late to just call Destroy(), which can cause crashes).
+ // It's particularly important to make sure widgetDestroyed is called on our
+ // mView -- this method NULLs mView's mGeckoChild, and NULL checks on
+ // mGeckoChild are used throughout the ChildView class to tell if it's safe
+ // to use a ChildView object.
+ [mView widgetDestroyed]; // Safe if mView is nil.
+ mParentWidget = nil;
+ TearDownView(); // Safe if called twice.
+}
+
+void
+nsChildView::ReleaseTitlebarCGContext()
+{
+ if (mTitlebarCGContext) {
+ CGContextRelease(mTitlebarCGContext);
+ mTitlebarCGContext = nullptr;
+ }
+}
+
+nsresult
+nsChildView::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Because the hidden window is created outside of an event loop,
+ // we need to provide an autorelease pool to avoid leaking cocoa objects
+ // (see bug 559075).
+ nsAutoreleasePool localPool;
+
+ // See NSView (MethodSwizzling) below.
+ if (!gChildViewMethodsSwizzled) {
+ nsToolkit::SwizzleMethods([NSView class], @selector(mouseDownCanMoveWindow),
+ @selector(nsChildView_NSView_mouseDownCanMoveWindow));
+ gChildViewMethodsSwizzled = true;
+ }
+
+ mBounds = aRect;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ BaseCreate(aParent, aInitData);
+
+ // inherit things from the parent view and create our parallel
+ // NSView in the Cocoa display system
+ mParentView = nil;
+ if (aParent) {
+ // inherit the top-level window. NS_NATIVE_WIDGET is always a NSView
+ // regardless of if we're asking a window or a view (for compatibility
+ // with windows).
+ mParentView = (NSView<mozView>*)aParent->GetNativeData(NS_NATIVE_WIDGET);
+ mParentWidget = aParent;
+ } else {
+ // This is the normal case. When we're the root widget of the view hiararchy,
+ // aNativeParent will be the contentView of our window, since that's what
+ // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW.
+ mParentView = reinterpret_cast<NSView<mozView>*>(aNativeParent);
+ }
+
+ // create our parallel NSView and hook it up to our parent. Recall
+ // that NS_NATIVE_WIDGET is the NSView.
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView);
+ NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor);
+ mView = [[[[ChildView alloc] initWithFrame:r geckoChild:this] autorelease] retain];
+
+ if (!mView) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this view was created in a Gecko view hierarchy, the initial state
+ // is hidden. If the view is attached only to a native NSView but has
+ // no Gecko parent (as in embedding), the initial state is visible.
+ if (mParentWidget)
+ [mView setHidden:YES];
+ else
+ mVisible = true;
+
+ // Hook it up in the NSView hierarchy.
+ if (mParentView) {
+ [mParentView addSubview:mView];
+ }
+
+ // if this is a ChildView, make sure that our per-window data
+ // is set up
+ if ([mView isKindOfClass:[ChildView class]])
+ [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:[mView window]];
+
+ NS_ASSERTION(!mTextInputHandler, "mTextInputHandler has already existed");
+ mTextInputHandler = new TextInputHandler(this, mView);
+
+ mPluginFocused = false;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+
+void nsChildView::TearDownView()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mView)
+ return;
+
+ NSWindow* win = [mView window];
+ NSResponder* responder = [win firstResponder];
+
+ // We're being unhooked from the view hierarchy, don't leave our view
+ // or a child view as the window first responder.
+ if (responder && [responder isKindOfClass:[NSView class]] &&
+ [(NSView*)responder isDescendantOf:mView]) {
+ [win makeFirstResponder:[mView superview]];
+ }
+
+ // If mView is win's contentView, win (mView's NSWindow) "owns" mView --
+ // win has retained mView, and will detach it from the view hierarchy and
+ // release it when necessary (when win is itself destroyed (in a call to
+ // [win dealloc])). So all we need to do here is call [mView release] (to
+ // match the call to [mView retain] in nsChildView::StandardCreate()).
+ // Also calling [mView removeFromSuperviewWithoutNeedingDisplay] causes
+ // mView to be released again and dealloced, while remaining win's
+ // contentView. So if we do that here, win will (for a short while) have
+ // an invalid contentView (for the consequences see bmo bugs 381087 and
+ // 374260).
+ if ([mView isEqual:[win contentView]]) {
+ [mView release];
+ } else {
+ // Stop NSView hierarchy being changed during [ChildView drawRect:]
+ [mView performSelectorOnMainThread:@selector(delayedTearDown) withObject:nil waitUntilDone:false];
+ }
+ mView = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsCocoaWindow*
+nsChildView::GetXULWindowWidget()
+{
+ id windowDelegate = [[mView window] delegate];
+ if (windowDelegate && [windowDelegate isKindOfClass:[WindowDelegate class]]) {
+ return [(WindowDelegate *)windowDelegate geckoWidget];
+ }
+ return nullptr;
+}
+
+void nsChildView::Destroy()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Make sure that no composition is in progress while disconnecting
+ // ourselves from the view.
+ MutexAutoLock lock(mViewTearDownLock);
+
+ if (mOnDestroyCalled)
+ return;
+ mOnDestroyCalled = true;
+
+ // Stuff below may delete the last ref to this
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ [mView widgetDestroyed];
+
+ nsBaseWidget::Destroy();
+
+ NotifyWindowDestroyed();
+ mParentWidget = nil;
+
+ TearDownView();
+
+ nsBaseWidget::OnDestroy();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+#if 0
+static void PrintViewHierarchy(NSView *view)
+{
+ while (view) {
+ NSLog(@" view is %x, frame %@", view, NSStringFromRect([view frame]));
+ view = [view superview];
+ }
+}
+#endif
+
+// Return native data according to aDataType
+void* nsChildView::GetNativeData(uint32_t aDataType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ void* retVal = nullptr;
+
+ switch (aDataType)
+ {
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = (void*)mView;
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = [mView window];
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a Mac OS X child view!");
+ retVal = nullptr;
+ break;
+
+ case NS_NATIVE_OFFSETX:
+ retVal = 0;
+ break;
+
+ case NS_NATIVE_OFFSETY:
+ retVal = 0;
+ break;
+
+ case NS_RAW_NATIVE_IME_CONTEXT:
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ retVal = [mView inputContext];
+ // If input context isn't available on this widget, we should set |this|
+ // instead of nullptr since if this returns nullptr, IMEStateManager
+ // cannot manage composition with TextComposition instance. Although,
+ // this case shouldn't occur.
+ if (NS_WARN_IF(!retVal)) {
+ retVal = this;
+ }
+ break;
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+#pragma mark -
+
+nsTransparencyMode nsChildView::GetTransparencyMode()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ nsCocoaWindow* windowWidget = GetXULWindowWidget();
+ return windowWidget ? windowWidget->GetTransparencyMode() : eTransparencyOpaque;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
+}
+
+// This is called by nsContainerFrame on the root widget for all window types
+// except popup windows (when nsCocoaWindow::SetTransparencyMode is used instead).
+void nsChildView::SetTransparencyMode(nsTransparencyMode aMode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsCocoaWindow* windowWidget = GetXULWindowWidget();
+ if (windowWidget) {
+ windowWidget->SetTransparencyMode(aMode);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+bool nsChildView::IsVisible() const
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mVisible) {
+ return mVisible;
+ }
+
+ // mVisible does not accurately reflect the state of a hidden tabbed view
+ // so verify that the view has a window as well
+ // then check native widget hierarchy visibility
+ return ([mView window] != nil) && !NSIsEmptyRect([mView visibleRect]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+// Some NSView methods (e.g. setFrame and setHidden) invalidate the view's
+// bounds in our window. However, we don't want these invalidations because
+// they are unnecessary and because they actually slow us down since we
+// block on the compositor inside drawRect.
+// When we actually need something invalidated, there will be an explicit call
+// to Invalidate from Gecko, so turning these automatic invalidations off
+// won't hurt us in the non-OMTC case.
+// The invalidations inside these NSView methods happen via a call to the
+// private method -[NSWindow _setNeedsDisplayInRect:]. Our BaseWindow
+// implementation of that method is augmented to let us ignore those calls
+// using -[BaseWindow disable/enableSetNeedsDisplay].
+static void
+ManipulateViewWithoutNeedingDisplay(NSView* aView, void (^aCallback)())
+{
+ BaseWindow* win = nil;
+ if ([[aView window] isKindOfClass:[BaseWindow class]]) {
+ win = (BaseWindow*)[aView window];
+ }
+ [win disableSetNeedsDisplay];
+ aCallback();
+ [win enableSetNeedsDisplay];
+}
+
+// Hide or show this component
+NS_IMETHODIMP nsChildView::Show(bool aState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (aState != mVisible) {
+ // Provide an autorelease pool because this gets called during startup
+ // on the "hidden window", resulting in cocoa object leakage if there's
+ // no pool in place.
+ nsAutoreleasePool localPool;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setHidden:!aState];
+ });
+
+ mVisible = aState;
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Change the parent of this widget
+NS_IMETHODIMP
+nsChildView::SetParent(nsIWidget* aNewParent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mOnDestroyCalled)
+ return NS_OK;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ if (mParentWidget) {
+ mParentWidget->RemoveChild(this);
+ }
+
+ if (aNewParent) {
+ ReparentNativeWidget(aNewParent);
+ } else {
+ [mView removeFromSuperview];
+ mParentView = nil;
+ }
+
+ mParentWidget = aNewParent;
+
+ if (mParentWidget) {
+ mParentWidget->AddChild(this);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsChildView::ReparentNativeWidget(nsIWidget* aNewParent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_PRECONDITION(aNewParent, "");
+
+ if (mOnDestroyCalled)
+ return;
+
+ NSView<mozView>* newParentView =
+ (NSView<mozView>*)aNewParent->GetNativeData(NS_NATIVE_WIDGET);
+ NS_ENSURE_TRUE_VOID(newParentView);
+
+ // we hold a ref to mView, so this is safe
+ [mView removeFromSuperview];
+ mParentView = newParentView;
+ [mParentView addSubview:mView];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsChildView::ResetParent()
+{
+ if (!mOnDestroyCalled) {
+ if (mParentWidget)
+ mParentWidget->RemoveChild(this);
+ if (mView)
+ [mView removeFromSuperview];
+ }
+ mParentWidget = nullptr;
+}
+
+nsIWidget*
+nsChildView::GetParent()
+{
+ return mParentWidget;
+}
+
+float
+nsChildView::GetDPI()
+{
+ NSWindow* window = [mView window];
+ if (window && [window isKindOfClass:[BaseWindow class]]) {
+ return [(BaseWindow*)window getDPI];
+ }
+
+ return 96.0;
+}
+
+NS_IMETHODIMP nsChildView::Enable(bool aState)
+{
+ return NS_OK;
+}
+
+bool nsChildView::IsEnabled() const
+{
+ return true;
+}
+
+NS_IMETHODIMP nsChildView::SetFocus(bool aRaise)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindow* window = [mView window];
+ if (window)
+ [window makeFirstResponder:mView];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Override to set the cursor on the mac
+NS_IMETHODIMP nsChildView::SetCursor(nsCursor aCursor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ([mView isDragInProgress])
+ return NS_OK; // Don't change the cursor during dragging.
+
+ nsBaseWidget::SetCursor(aCursor);
+ return [[nsCursorManager sharedInstance] setCursor:aCursor];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// implement to fix "hidden virtual function" warning
+NS_IMETHODIMP nsChildView::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsBaseWidget::SetCursor(aCursor, aHotspotX, aHotspotY);
+ return [[nsCursorManager sharedInstance] setCursorWithImage:aCursor hotSpotX:aHotspotX hotSpotY:aHotspotY scaleFactor:BackingScaleFactor()];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#pragma mark -
+
+// Get this component dimension
+LayoutDeviceIntRect
+nsChildView::GetBounds()
+{
+ return !mView ? mBounds : CocoaPointsToDevPixels([mView frame]);
+}
+
+LayoutDeviceIntRect
+nsChildView::GetClientBounds()
+{
+ LayoutDeviceIntRect rect = GetBounds();
+ if (!mParentWidget) {
+ // For top level widgets we want the position on screen, not the position
+ // of this view inside the window.
+ rect.MoveTo(WidgetToScreenOffset());
+ }
+ return rect;
+}
+
+LayoutDeviceIntRect
+nsChildView::GetScreenBounds()
+{
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveTo(WidgetToScreenOffset());
+ return rect;
+}
+
+double
+nsChildView::GetDefaultScaleInternal()
+{
+ return BackingScaleFactor();
+}
+
+CGFloat
+nsChildView::BackingScaleFactor() const
+{
+ if (mBackingScaleFactor > 0.0) {
+ return mBackingScaleFactor;
+ }
+ if (!mView) {
+ return 1.0;
+ }
+ mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mView);
+ return mBackingScaleFactor;
+}
+
+void
+nsChildView::BackingScaleFactorChanged()
+{
+ CGFloat newScale = nsCocoaUtils::GetBackingScaleFactor(mView);
+
+ // ignore notification if it hasn't really changed (or maybe we have
+ // disabled HiDPI mode via prefs)
+ if (mBackingScaleFactor == newScale) {
+ return;
+ }
+
+ mBackingScaleFactor = newScale;
+ NSRect frame = [mView frame];
+ mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, newScale);
+
+ if (mWidgetListener && !mWidgetListener->GetXULWindow()) {
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ }
+ }
+}
+
+int32_t
+nsChildView::RoundsWidgetCoordinatesTo()
+{
+ if (BackingScaleFactor() == 2.0) {
+ return 2;
+ }
+ return 1;
+}
+
+// Move this component, aX and aY are in the parent widget coordinate system
+NS_IMETHODIMP nsChildView::Move(double aX, double aY)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t x = NSToIntRound(aX);
+ int32_t y = NSToIntRound(aY);
+
+ if (!mView || (mBounds.x == x && mBounds.y == y))
+ return NS_OK;
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ NotifyRollupGeometryChange();
+ ReportMoveEvent();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsChildView::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t width = NSToIntRound(aWidth);
+ int32_t height = NSToIntRound(aHeight);
+
+ if (!mView || (mBounds.width == width && mBounds.height == height))
+ return NS_OK;
+
+ mBounds.width = width;
+ mBounds.height = height;
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ if (mVisible && aRepaint) {
+ [[mView pixelHostingView] setNeedsDisplay:YES];
+ }
+
+ NotifyRollupGeometryChange();
+ ReportSizeEvent();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsChildView::Resize(double aX, double aY,
+ double aWidth, double aHeight, bool aRepaint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ int32_t x = NSToIntRound(aX);
+ int32_t y = NSToIntRound(aY);
+ int32_t width = NSToIntRound(aWidth);
+ int32_t height = NSToIntRound(aHeight);
+
+ BOOL isMoving = (mBounds.x != x || mBounds.y != y);
+ BOOL isResizing = (mBounds.width != width || mBounds.height != height);
+ if (!mView || (!isMoving && !isResizing))
+ return NS_OK;
+
+ if (isMoving) {
+ mBounds.x = x;
+ mBounds.y = y;
+ }
+ if (isResizing) {
+ mBounds.width = width;
+ mBounds.height = height;
+ }
+
+ ManipulateViewWithoutNeedingDisplay(mView, ^{
+ [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
+ });
+
+ if (mVisible && aRepaint) {
+ [[mView pixelHostingView] setNeedsDisplay:YES];
+ }
+
+ NotifyRollupGeometryChange();
+ if (isMoving) {
+ ReportMoveEvent();
+ if (mOnDestroyCalled)
+ return NS_OK;
+ }
+ if (isResizing)
+ ReportSizeEvent();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static const int32_t resizeIndicatorWidth = 15;
+static const int32_t resizeIndicatorHeight = 15;
+bool nsChildView::ShowsResizeIndicator(LayoutDeviceIntRect* aResizerRect)
+{
+ NSView *topLevelView = mView, *superView = nil;
+ while ((superView = [topLevelView superview]))
+ topLevelView = superView;
+
+ if (![[topLevelView window] showsResizeIndicator] ||
+ !([[topLevelView window] styleMask] & NSResizableWindowMask))
+ return false;
+
+ if (aResizerRect) {
+ NSSize bounds = [topLevelView bounds].size;
+ NSPoint corner = NSMakePoint(bounds.width, [topLevelView isFlipped] ? bounds.height : 0);
+ corner = [topLevelView convertPoint:corner toView:mView];
+ aResizerRect->SetRect(NSToIntRound(corner.x) - resizeIndicatorWidth,
+ NSToIntRound(corner.y) - resizeIndicatorHeight,
+ resizeIndicatorWidth, resizeIndicatorHeight);
+ }
+ return true;
+}
+
+nsresult nsChildView::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+ int32_t aNativeKeyCode,
+ uint32_t aModifierFlags,
+ const nsAString& aCharacters,
+ const nsAString& aUnmodifiedCharacters,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "keyevent");
+ return mTextInputHandler->SynthesizeNativeKeyEvent(aNativeKeyboardLayout,
+ aNativeKeyCode,
+ aModifierFlags,
+ aCharacters,
+ aUnmodifiedCharacters);
+}
+
+nsresult nsChildView::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ NSPoint pt =
+ nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
+
+ // Move the mouse cursor to the requested position and reconnect it to the mouse.
+ CGWarpMouseCursorPosition(NSPointToCGPoint(pt));
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ // aPoint is given with the origin on the top left, but convertScreenToBase
+ // expects a point in a coordinate system that has its origin on the bottom left.
+ NSPoint screenPoint = NSMakePoint(pt.x, nsCocoaUtils::FlippedScreenY(pt.y));
+ NSPoint windowPoint =
+ nsCocoaUtils::ConvertPointFromScreen([mView window], screenPoint);
+
+ NSEvent* event = [NSEvent mouseEventWithType:(NSEventType)aNativeMessage
+ location:windowPoint
+ modifierFlags:aModifierFlags
+ timestamp:[[NSProcessInfo processInfo] systemUptime]
+ windowNumber:[[mView window] windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:1
+ pressure:0.0];
+
+ if (!event)
+ return NS_ERROR_FAILURE;
+
+ if ([[mView window] isKindOfClass:[BaseWindow class]]) {
+ // Tracking area events don't end up in their tracking areas when sent
+ // through [NSApp sendEvent:], so pass them directly to the right methods.
+ BaseWindow* window = (BaseWindow*)[mView window];
+ if (aNativeMessage == NSMouseEntered) {
+ [window mouseEntered:event];
+ return NS_OK;
+ }
+ if (aNativeMessage == NSMouseExited) {
+ [window mouseExited:event];
+ return NS_OK;
+ }
+ if (aNativeMessage == NSMouseMoved) {
+ [window mouseMoved:event];
+ return NS_OK;
+ }
+ }
+
+ [NSApp sendEvent:event];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsChildView::SynthesizeNativeMouseScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ double aDeltaX,
+ double aDeltaY,
+ double aDeltaZ,
+ uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+
+ NSPoint pt =
+ nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
+
+ // Move the mouse cursor to the requested position and reconnect it to the mouse.
+ CGWarpMouseCursorPosition(NSPointToCGPoint(pt));
+ CGAssociateMouseAndMouseCursorPosition(true);
+
+ // Mostly copied from http://stackoverflow.com/a/6130349
+ CGScrollEventUnit units =
+ (aAdditionalFlags & nsIDOMWindowUtils::MOUSESCROLL_SCROLL_LINES)
+ ? kCGScrollEventUnitLine : kCGScrollEventUnitPixel;
+ CGEventRef cgEvent = CGEventCreateScrollWheelEvent(NULL, units, 3, aDeltaY, aDeltaX, aDeltaZ);
+ if (!cgEvent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CGEventPost(kCGHIDEventTap, cgEvent);
+ CFRelease(cgEvent);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsChildView::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ mozilla::LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), PR_IntervalNow(), TimeStamp::Now(),
+ aPointerId, aPointerState, pointInWindow, aPointerPressure,
+ aPointerOrientation);
+ DispatchTouchInput(inputToDispatch);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// First argument has to be an NSMenu representing the application's top-level
+// menu bar. The returned item is *not* retained.
+static NSMenuItem* NativeMenuItemWithLocation(NSMenu* menubar, NSString* locationString)
+{
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return nil;
+
+ NSMenu* currentSubmenu = [NSApp mainMenu];
+ for (unsigned int i = 0; i < indexCount; i++) {
+ int targetIndex;
+ // We remove the application menu from consideration for the top-level menu
+ if (i == 0)
+ targetIndex = [[indexes objectAtIndex:i] intValue] + 1;
+ else
+ targetIndex = [[indexes objectAtIndex:i] intValue];
+ int itemCount = [currentSubmenu numberOfItems];
+ if (targetIndex < itemCount) {
+ NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+ // if this is the last index just return the menu item
+ if (i == (indexCount - 1))
+ return menuItem;
+ // if this is not the last index find the submenu and keep going
+ if ([menuItem hasSubmenu])
+ currentSubmenu = [menuItem submenu];
+ else
+ return nil;
+ }
+ }
+
+ return nil;
+}
+
+// Used for testing native menu system structure and event handling.
+NS_IMETHODIMP nsChildView::ActivateNativeMenuItemAt(const nsAString& indexString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSMenuItem* item = NativeMenuItemWithLocation([NSApp mainMenu], locationString);
+ // We can't perform an action on an item with a submenu, that will raise
+ // an obj-c exception.
+ if (item && ![item hasSubmenu]) {
+ NSMenu* parent = [item menu];
+ if (parent) {
+ // NSLog(@"Performing action for native menu item titled: %@\n",
+ // [[currentSubmenu itemAtIndex:targetIndex] title]);
+ [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Used for testing native menu system structure and event handling.
+NS_IMETHODIMP nsChildView::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsCocoaWindow *widget = GetXULWindowWidget();
+ if (widget) {
+ nsMenuBarX* mb = widget->GetMenuBar();
+ if (mb) {
+ if (indexString.IsEmpty())
+ mb->ForceNativeMenuReload();
+ else
+ mb->ForceUpdateNativeMenuAt(indexString);
+ }
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#pragma mark -
+
+#ifdef INVALIDATE_DEBUGGING
+
+static Boolean KeyDown(const UInt8 theKey)
+{
+ KeyMap map;
+ GetKeys(map);
+ return ((*((UInt8 *)map + (theKey >> 3)) >> (theKey & 7)) & 1) != 0;
+}
+
+static Boolean caps_lock()
+{
+ return KeyDown(0x39);
+}
+
+static void blinkRect(Rect* r)
+{
+ StRegionFromPool oldClip;
+ if (oldClip != NULL)
+ ::GetClip(oldClip);
+
+ ::ClipRect(r);
+ ::InvertRect(r);
+ UInt32 end = ::TickCount() + 5;
+ while (::TickCount() < end) ;
+ ::InvertRect(r);
+
+ if (oldClip != NULL)
+ ::SetClip(oldClip);
+}
+
+static void blinkRgn(RgnHandle rgn)
+{
+ StRegionFromPool oldClip;
+ if (oldClip != NULL)
+ ::GetClip(oldClip);
+
+ ::SetClip(rgn);
+ ::InvertRgn(rgn);
+ UInt32 end = ::TickCount() + 5;
+ while (::TickCount() < end) ;
+ ::InvertRgn(rgn);
+
+ if (oldClip != NULL)
+ ::SetClip(oldClip);
+}
+
+#endif
+
+// Invalidate this component's visible area
+NS_IMETHODIMP nsChildView::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mView || !mVisible)
+ return NS_OK;
+
+ NS_ASSERTION(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
+ "Shouldn't need to invalidate with accelerated OMTC layers!");
+
+ [[mView pixelHostingView] setNeedsDisplayInRect:DevPixelsToCocoaPoints(aRect)];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+nsChildView::WidgetTypeSupportsAcceleration()
+{
+ // Don't use OpenGL for transparent windows or for popup windows.
+ return mView && [[mView window] isOpaque] &&
+ ![[mView window] isKindOfClass:[PopupWindow class]];
+}
+
+bool
+nsChildView::ShouldUseOffMainThreadCompositing()
+{
+ // Don't use OMTC for transparent windows or for popup windows.
+ if (!mView || ![[mView window] isOpaque] ||
+ [[mView window] isKindOfClass:[PopupWindow class]])
+ return false;
+
+ return nsBaseWidget::ShouldUseOffMainThreadCompositing();
+}
+
+inline uint16_t COLOR8TOCOLOR16(uint8_t color8)
+{
+ // return (color8 == 0xFF ? 0xFFFF : (color8 << 8));
+ return (color8 << 8) | color8; /* (color8 * 257) == (color8 * 0x0101) */
+}
+
+#pragma mark -
+
+nsresult nsChildView::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
+{
+ return NS_OK;
+}
+
+// Invokes callback and ProcessEvent methods on Event Listener object
+NS_IMETHODIMP nsChildView::DispatchEvent(WidgetGUIEvent* event,
+ nsEventStatus& aStatus)
+{
+ RefPtr<nsChildView> kungFuDeathGrip(this);
+
+#ifdef DEBUG
+ debug_DumpEvent(stdout, event->mWidget, event, "something", 0);
+#endif
+
+ NS_ASSERTION(!(mTextInputHandler && mTextInputHandler->IsIMEComposing() &&
+ event->HasKeyEventMessage()),
+ "Any key events should not be fired during IME composing");
+
+ if (event->mFlags.mIsSynthesizedForTests) {
+ WidgetKeyboardEvent* keyEvent = event->AsKeyboardEvent();
+ if (keyEvent) {
+ nsresult rv = mTextInputHandler->AttachNativeKeyEvent(*keyEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ aStatus = nsEventStatus_eIgnore;
+
+ nsIWidgetListener* listener = mWidgetListener;
+
+ // If the listener is NULL, check if the parent is a popup. If it is, then
+ // this child is the popup content view attached to a popup. Get the
+ // listener from the parent popup instead.
+ nsCOMPtr<nsIWidget> parentWidget = mParentWidget;
+ if (!listener && parentWidget) {
+ if (parentWidget->WindowType() == eWindowType_popup) {
+ // Check just in case event->mWidget isn't this widget
+ if (event->mWidget) {
+ listener = event->mWidget->GetWidgetListener();
+ }
+ if (!listener) {
+ event->mWidget = parentWidget;
+ listener = parentWidget->GetWidgetListener();
+ }
+ }
+ }
+
+ if (listener)
+ aStatus = listener->HandleEvent(event, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+bool nsChildView::DispatchWindowEvent(WidgetGUIEvent& event)
+{
+ nsEventStatus status;
+ DispatchEvent(&event, status);
+ return ConvertStatus(status);
+}
+
+nsIWidget*
+nsChildView::GetWidgetForListenerEvents()
+{
+ // If there is no listener, use the parent popup's listener if that exists.
+ if (!mWidgetListener && mParentWidget &&
+ mParentWidget->WindowType() == eWindowType_popup) {
+ return mParentWidget;
+ }
+
+ return this;
+}
+
+void nsChildView::WillPaintWindow()
+{
+ nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
+
+ nsIWidgetListener* listener = widget->GetWidgetListener();
+ if (listener) {
+ listener->WillPaintWindow(widget);
+ }
+}
+
+bool nsChildView::PaintWindow(LayoutDeviceIntRegion aRegion)
+{
+ nsCOMPtr<nsIWidget> widget = GetWidgetForListenerEvents();
+
+ nsIWidgetListener* listener = widget->GetWidgetListener();
+ if (!listener)
+ return false;
+
+ bool returnValue = false;
+ bool oldDispatchPaint = mIsDispatchPaint;
+ mIsDispatchPaint = true;
+ returnValue = listener->PaintWindow(widget, aRegion);
+
+ listener = widget->GetWidgetListener();
+ if (listener) {
+ listener->DidPaintWindow();
+ }
+
+ mIsDispatchPaint = oldDispatchPaint;
+ return returnValue;
+}
+
+bool
+nsChildView::PaintWindowInContext(CGContextRef aContext, const LayoutDeviceIntRegion& aRegion, gfx::IntSize aSurfaceSize)
+{
+ if (!mBackingSurface || mBackingSurface->GetSize() != aSurfaceSize) {
+ mBackingSurface =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(aSurfaceSize,
+ gfx::SurfaceFormat::B8G8R8A8);
+ if (!mBackingSurface) {
+ return false;
+ }
+ }
+
+ RefPtr<gfxContext> targetContext = gfxContext::CreateOrNull(mBackingSurface);
+ MOZ_ASSERT(targetContext); // already checked the draw target above
+
+ // Set up the clip region and clear existing contents in the backing surface.
+ targetContext->NewPath();
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const LayoutDeviceIntRect& r = iter.Get();
+ targetContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height));
+ mBackingSurface->ClearRect(gfx::Rect(r.ToUnknownRect()));
+ }
+ targetContext->Clip();
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(mView);
+ bool painted = false;
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ nsBaseWidget::AutoLayerManagerSetup
+ setupLayerManager(this, targetContext, BufferMode::BUFFER_NONE);
+ painted = PaintWindow(aRegion);
+ } else if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
+ // We only need this so that we actually get DidPaintWindow fired
+ painted = PaintWindow(aRegion);
+ }
+
+ uint8_t* data;
+ gfx::IntSize size;
+ int32_t stride;
+ gfx::SurfaceFormat format;
+
+ if (!mBackingSurface->LockBits(&data, &size, &stride, &format)) {
+ return false;
+ }
+
+ // Draw the backing surface onto the window.
+ CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, data, stride * size.height, NULL);
+ NSColorSpace* colorSpace = [[mView window] colorSpace];
+ CGImageRef image = CGImageCreate(size.width, size.height, 8, 32, stride,
+ [colorSpace CGColorSpace],
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst,
+ provider, NULL, false, kCGRenderingIntentDefault);
+ CGContextSaveGState(aContext);
+ CGContextTranslateCTM(aContext, 0, size.height);
+ CGContextScaleCTM(aContext, 1, -1);
+ CGContextSetBlendMode(aContext, kCGBlendModeCopy);
+ CGContextDrawImage(aContext, CGRectMake(0, 0, size.width, size.height), image);
+ CGImageRelease(image);
+ CGDataProviderRelease(provider);
+ CGContextRestoreGState(aContext);
+
+ mBackingSurface->ReleaseBits(data);
+
+ return painted;
+}
+
+#pragma mark -
+
+void nsChildView::ReportMoveEvent()
+{
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+}
+
+void nsChildView::ReportSizeEvent()
+{
+ if (mWidgetListener)
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+}
+
+#pragma mark -
+
+LayoutDeviceIntPoint nsChildView::GetClientOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSPoint origin = [mView convertPoint:NSMakePoint(0, 0) toView:nil];
+ origin.y = [[mView window] frame].size.height - origin.y;
+ return CocoaPointsToDevPixels(origin);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
+}
+
+// Return the offset between this child view and the screen.
+// @return -- widget origin in device-pixel coords
+LayoutDeviceIntPoint nsChildView::WidgetToScreenOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSPoint origin = NSMakePoint(0, 0);
+
+ // 1. First translate view origin point into window coords.
+ // The returned point is in bottom-left coordinates.
+ origin = [mView convertPoint:origin toView:nil];
+
+ // 2. We turn the window-coord rect's origin into screen (still bottom-left) coords.
+ origin = nsCocoaUtils::ConvertPointToScreen([mView window], origin);
+
+ // 3. Since we're dealing in bottom-left coords, we need to make it top-left coords
+ // before we pass it back to Gecko.
+ FlipCocoaScreenCoordinate(origin);
+
+ // convert to device pixels
+ return CocoaPointsToDevPixels(origin);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0,0));
+}
+
+NS_IMETHODIMP nsChildView::SetTitle(const nsAString& title)
+{
+ // child views don't have titles
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsChildView::GetAttention(int32_t aCycleCount)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [NSApp requestUserAttention:NSInformationalRequest];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+/* static */
+bool nsChildView::DoHasPendingInputEvent()
+{
+ return sLastInputEventCount != GetCurrentInputEventCount();
+}
+
+/* static */
+uint32_t nsChildView::GetCurrentInputEventCount()
+{
+ // Can't use kCGAnyInputEventType because that updates too rarely for us (and
+ // always in increments of 30+!) and because apparently it's sort of broken
+ // on Tiger. So just go ahead and query the counters we care about.
+ static const CGEventType eventTypes[] = {
+ kCGEventLeftMouseDown,
+ kCGEventLeftMouseUp,
+ kCGEventRightMouseDown,
+ kCGEventRightMouseUp,
+ kCGEventMouseMoved,
+ kCGEventLeftMouseDragged,
+ kCGEventRightMouseDragged,
+ kCGEventKeyDown,
+ kCGEventKeyUp,
+ kCGEventScrollWheel,
+ kCGEventTabletPointer,
+ kCGEventOtherMouseDown,
+ kCGEventOtherMouseUp,
+ kCGEventOtherMouseDragged
+ };
+
+ uint32_t eventCount = 0;
+ for (uint32_t i = 0; i < ArrayLength(eventTypes); ++i) {
+ eventCount +=
+ CGEventSourceCounterForEventType(kCGEventSourceStateCombinedSessionState,
+ eventTypes[i]);
+ }
+ return eventCount;
+}
+
+/* static */
+void nsChildView::UpdateCurrentInputEventCount()
+{
+ sLastInputEventCount = GetCurrentInputEventCount();
+}
+
+bool nsChildView::HasPendingInputEvent()
+{
+ return DoHasPendingInputEvent();
+}
+
+#pragma mark -
+
+NS_IMETHODIMP
+nsChildView::StartPluginIME(const mozilla::WidgetKeyboardEvent& aKeyboardEvent,
+ int32_t aPanelX, int32_t aPanelY,
+ nsString& aCommitted)
+{
+ NS_ENSURE_TRUE(mView, NS_ERROR_NOT_AVAILABLE);
+
+ ComplexTextInputPanel* ctiPanel =
+ ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+
+ ctiPanel->PlacePanel(aPanelX, aPanelY);
+ // We deliberately don't use TextInputHandler::GetCurrentKeyEvent() to
+ // obtain the NSEvent* we pass to InterpretKeyEvent(). This works fine in
+ // non-e10s mode. But in e10s mode TextInputHandler::HandleKeyDownEvent()
+ // has already returned, so the relevant KeyEventState* (and its NSEvent*)
+ // is already out of scope. Furthermore we don't *need* to use it.
+ // StartPluginIME() is only ever called to start a new IME session when none
+ // currently exists. So nested IME should never reach here, and so it should
+ // be fine to use the last key-down event received by -[ChildView keyDown:]
+ // (as we currently do).
+ ctiPanel->InterpretKeyEvent([(ChildView*)mView lastKeyDownEvent], aCommitted);
+
+ return NS_OK;
+}
+
+void
+nsChildView::SetPluginFocused(bool& aFocused)
+{
+ if (aFocused == mPluginFocused) {
+ return;
+ }
+ if (!aFocused) {
+ ComplexTextInputPanel* ctiPanel =
+ ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+ if (ctiPanel) {
+ ctiPanel->CancelComposition();
+ }
+ }
+ mPluginFocused = aFocused;
+}
+
+NS_IMETHODIMP_(void)
+nsChildView::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+
+ if (mTextInputHandler->IsFocused()) {
+ if (aContext.IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+ }
+
+ mInputContext = aContext;
+ switch (aContext.mIMEState.mEnabled) {
+ case IMEState::ENABLED:
+ case IMEState::PLUGIN:
+ mTextInputHandler->SetASCIICapableOnly(false);
+ mTextInputHandler->EnableIME(true);
+ if (mInputContext.mIMEState.mOpen != IMEState::DONT_CHANGE_OPEN_STATE) {
+ mTextInputHandler->SetIMEOpenState(
+ mInputContext.mIMEState.mOpen == IMEState::OPEN);
+ }
+ break;
+ case IMEState::DISABLED:
+ mTextInputHandler->SetASCIICapableOnly(false);
+ mTextInputHandler->EnableIME(false);
+ break;
+ case IMEState::PASSWORD:
+ mTextInputHandler->SetASCIICapableOnly(true);
+ mTextInputHandler->EnableIME(false);
+ break;
+ default:
+ NS_ERROR("not implemented!");
+ }
+}
+
+NS_IMETHODIMP_(InputContext)
+nsChildView::GetInputContext()
+{
+ switch (mInputContext.mIMEState.mEnabled) {
+ case IMEState::ENABLED:
+ case IMEState::PLUGIN:
+ if (mTextInputHandler) {
+ mInputContext.mIMEState.mOpen =
+ mTextInputHandler->IsIMEOpened() ? IMEState::OPEN : IMEState::CLOSED;
+ break;
+ }
+ // If mTextInputHandler is null, set CLOSED instead...
+ MOZ_FALLTHROUGH;
+ default:
+ mInputContext.mIMEState.mOpen = IMEState::CLOSED;
+ break;
+ }
+ return mInputContext;
+}
+
+NS_IMETHODIMP_(TextEventDispatcherListener*)
+nsChildView::GetNativeTextEventDispatcherListener()
+{
+ if (NS_WARN_IF(!mTextInputHandler)) {
+ return nullptr;
+ }
+ return mTextInputHandler;
+}
+
+NS_IMETHODIMP
+nsChildView::AttachNativeKeyEvent(mozilla::WidgetKeyboardEvent& aEvent)
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NS_ERROR_NOT_AVAILABLE);
+ return mTextInputHandler->AttachNativeKeyEvent(aEvent);
+}
+
+bool
+nsChildView::ExecuteNativeKeyBindingRemapped(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData,
+ uint32_t aGeckoKeyCode,
+ uint32_t aCocoaKeyCode)
+{
+ NSEvent *originalEvent = reinterpret_cast<NSEvent*>(aEvent.mNativeKeyEvent);
+
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mKeyCode = aGeckoKeyCode;
+
+ unichar ch = nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aGeckoKeyCode);
+ NSString *chars =
+ [[[NSString alloc] initWithCharacters:&ch length:1] autorelease];
+
+ modifiedEvent.mNativeKeyEvent =
+ [NSEvent keyEventWithType:[originalEvent type]
+ location:[originalEvent locationInWindow]
+ modifierFlags:[originalEvent modifierFlags]
+ timestamp:[originalEvent timestamp]
+ windowNumber:[originalEvent windowNumber]
+ context:[originalEvent context]
+ characters:chars
+ charactersIgnoringModifiers:chars
+ isARepeat:[originalEvent isARepeat]
+ keyCode:aCocoaKeyCode];
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(modifiedEvent, aCallback, aCallbackData);
+}
+
+NS_IMETHODIMP_(bool)
+nsChildView::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ // If the key is a cursor-movement arrow, and the current selection has
+ // vertical writing-mode, we'll remap so that the movement command
+ // generated (in terms of characters/lines) will be appropriate for
+ // the physical direction of the arrow.
+ if (aEvent.mKeyCode >= NS_VK_LEFT && aEvent.mKeyCode <= NS_VK_DOWN) {
+ WidgetQueryContentEvent query(true, eQuerySelectedText, this);
+ DispatchWindowEvent(query);
+
+ if (query.mSucceeded && query.mReply.mWritingMode.IsVertical()) {
+ uint32_t geckoKey = 0;
+ uint32_t cocoaKey = 0;
+
+ switch (aEvent.mKeyCode) {
+ case NS_VK_LEFT:
+ if (query.mReply.mWritingMode.IsVerticalLR()) {
+ geckoKey = NS_VK_UP;
+ cocoaKey = kVK_UpArrow;
+ } else {
+ geckoKey = NS_VK_DOWN;
+ cocoaKey = kVK_DownArrow;
+ }
+ break;
+
+ case NS_VK_RIGHT:
+ if (query.mReply.mWritingMode.IsVerticalLR()) {
+ geckoKey = NS_VK_DOWN;
+ cocoaKey = kVK_DownArrow;
+ } else {
+ geckoKey = NS_VK_UP;
+ cocoaKey = kVK_UpArrow;
+ }
+ break;
+
+ case NS_VK_UP:
+ geckoKey = NS_VK_LEFT;
+ cocoaKey = kVK_LeftArrow;
+ break;
+
+ case NS_VK_DOWN:
+ geckoKey = NS_VK_RIGHT;
+ cocoaKey = kVK_RightArrow;
+ break;
+ }
+
+ return ExecuteNativeKeyBindingRemapped(aType, aEvent, aCallback,
+ aCallbackData,
+ geckoKey, cocoaKey);
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(aEvent, aCallback, aCallbackData);
+}
+
+nsIMEUpdatePreference
+nsChildView::GetIMEUpdatePreference()
+{
+ // XXX Shouldn't we move floating window which shows composition string
+ // when plugin has focus and its parent is scrolled or the window is
+ // moved?
+ return nsIMEUpdatePreference();
+}
+
+NSView<mozView>* nsChildView::GetEditorView()
+{
+ NSView<mozView>* editorView = mView;
+ // We need to get editor's view. E.g., when the focus is in the bookmark
+ // dialog, the view is <panel> element of the dialog. At this time, the key
+ // events are processed the parent window's view that has native focus.
+ WidgetQueryContentEvent textContent(true, eQueryTextContent, this);
+ textContent.InitForQueryTextContent(0, 0);
+ DispatchWindowEvent(textContent);
+ if (textContent.mSucceeded && textContent.mReply.mFocusedWidget) {
+ NSView<mozView>* view = static_cast<NSView<mozView>*>(
+ textContent.mReply.mFocusedWidget->GetNativeData(NS_NATIVE_WIDGET));
+ if (view)
+ editorView = view;
+ }
+ return editorView;
+}
+
+#pragma mark -
+
+void
+nsChildView::CreateCompositor()
+{
+ nsBaseWidget::CreateCompositor();
+ if (mCompositorBridgeChild) {
+ [(ChildView *)mView setUsingOMTCompositor:true];
+ }
+}
+
+void
+nsChildView::ConfigureAPZCTreeManager()
+{
+ nsBaseWidget::ConfigureAPZCTreeManager();
+}
+
+void
+nsChildView::ConfigureAPZControllerThread()
+{
+ if (gfxPrefs::AsyncPanZoomSeparateEventThread()) {
+ // The EventThreadRunner is the controller thread, but it doesn't
+ // have a MessageLoop.
+ APZThreadUtils::SetControllerThread(nullptr);
+ } else {
+ nsBaseWidget::ConfigureAPZControllerThread();
+ }
+}
+
+LayoutDeviceIntRect
+nsChildView::RectContainingTitlebarControls()
+{
+ // Start with a thin strip at the top of the window for the highlight line.
+ NSRect rect = NSMakeRect(0, 0, [mView bounds].size.width,
+ [(ChildView*)mView cornerRadius]);
+
+ // If we draw the titlebar title string, increase the height to the default
+ // titlebar height. This height does not necessarily include all the titlebar
+ // controls because we may have moved them further down, but at least it will
+ // include the whole title text.
+ BaseWindow* window = (BaseWindow*)[mView window];
+ if ([window wantsTitleDrawn] && [window isKindOfClass:[ToolbarWindow class]]) {
+ CGFloat defaultTitlebarHeight = [(ToolbarWindow*)window titlebarHeight];
+ rect.size.height = std::max(rect.size.height, defaultTitlebarHeight);
+ }
+
+ // Add the rects of the titlebar controls.
+ for (id view in [window titlebarControls]) {
+ rect = NSUnionRect(rect, [mView convertRect:[view bounds] fromView:view]);
+ }
+ return CocoaPointsToDevPixels(rect);
+}
+
+void
+nsChildView::PrepareWindowEffects()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool canBeOpaque;
+ {
+ MutexAutoLock lock(mEffectsLock);
+ mShowsResizeIndicator = ShowsResizeIndicator(&mResizeIndicatorRect);
+ mHasRoundedBottomCorners = [(ChildView*)mView hasRoundedBottomCorners];
+ CGFloat cornerRadius = [(ChildView*)mView cornerRadius];
+ mDevPixelCornerRadius = cornerRadius * BackingScaleFactor();
+ mIsCoveringTitlebar = [(ChildView*)mView isCoveringTitlebar];
+ NSInteger styleMask = [[mView window] styleMask];
+ bool wasFullscreen = mIsFullscreen;
+ mIsFullscreen = (styleMask & NSFullScreenWindowMask) || !(styleMask & NSTitledWindowMask);
+
+ canBeOpaque = mIsFullscreen && wasFullscreen;
+ if (canBeOpaque && VibrancyManager::SystemSupportsVibrancy()) {
+ canBeOpaque = !EnsureVibrancyManager().HasVibrantRegions();
+ }
+ if (mIsCoveringTitlebar) {
+ mTitlebarRect = RectContainingTitlebarControls();
+ UpdateTitlebarCGContext();
+ }
+ }
+
+ // If we've just transitioned into or out of full screen then update the opacity on our GLContext.
+ if (canBeOpaque != mIsOpaque) {
+ mIsOpaque = canBeOpaque;
+ [(ChildView*)mView setGLOpaque:canBeOpaque];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsChildView::CleanupWindowEffects()
+{
+ mResizerImage = nullptr;
+ mCornerMaskImage = nullptr;
+ mTitlebarImage = nullptr;
+}
+
+bool
+nsChildView::PreRender(WidgetRenderingContext* aContext)
+{
+ UniquePtr<GLManager> manager(GLManager::CreateGLManager(aContext->mLayerManager));
+ if (!manager) {
+ return true;
+ }
+
+ // The lock makes sure that we don't attempt to tear down the view while
+ // compositing. That would make us unable to call postRender on it when the
+ // composition is done, thus keeping the GL context locked forever.
+ mViewTearDownLock.Lock();
+
+ NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext();
+
+ if (![(ChildView*)mView preRender:glContext]) {
+ mViewTearDownLock.Unlock();
+ return false;
+ }
+ return true;
+}
+
+void
+nsChildView::PostRender(WidgetRenderingContext* aContext)
+{
+ UniquePtr<GLManager> manager(GLManager::CreateGLManager(aContext->mLayerManager));
+ if (!manager) {
+ return;
+ }
+ NSOpenGLContext *glContext = GLContextCGL::Cast(manager->gl())->GetNSOpenGLContext();
+ [(ChildView*)mView postRender:glContext];
+ mViewTearDownLock.Unlock();
+}
+
+void
+nsChildView::DrawWindowOverlay(WidgetRenderingContext* aContext,
+ LayoutDeviceIntRect aRect)
+{
+ mozilla::UniquePtr<GLManager> manager(GLManager::CreateGLManager(aContext->mLayerManager));
+ if (manager) {
+ DrawWindowOverlay(manager.get(), aRect);
+ }
+}
+
+void
+nsChildView::DrawWindowOverlay(GLManager* aManager, LayoutDeviceIntRect aRect)
+{
+ GLContext* gl = aManager->gl();
+ ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST, false);
+
+ MaybeDrawTitlebar(aManager);
+ MaybeDrawResizeIndicator(aManager);
+ MaybeDrawRoundedCorners(aManager, aRect);
+}
+
+static void
+ClearRegion(gfx::DrawTarget *aDT, LayoutDeviceIntRegion aRegion)
+{
+ gfxUtils::ClipToRegion(aDT, aRegion.ToUnknownRegion());
+ aDT->ClearRect(gfx::Rect(0, 0, aDT->GetSize().width, aDT->GetSize().height));
+ aDT->PopClip();
+}
+
+static void
+DrawResizer(CGContextRef aCtx)
+{
+ CGContextSetShouldAntialias(aCtx, false);
+ CGPoint points[6];
+ points[0] = CGPointMake(13.0f, 4.0f);
+ points[1] = CGPointMake(3.0f, 14.0f);
+ points[2] = CGPointMake(13.0f, 8.0f);
+ points[3] = CGPointMake(7.0f, 14.0f);
+ points[4] = CGPointMake(13.0f, 12.0f);
+ points[5] = CGPointMake(11.0f, 14.0f);
+ CGContextSetRGBStrokeColor(aCtx, 0.00f, 0.00f, 0.00f, 0.15f);
+ CGContextStrokeLineSegments(aCtx, points, 6);
+
+ points[0] = CGPointMake(13.0f, 5.0f);
+ points[1] = CGPointMake(4.0f, 14.0f);
+ points[2] = CGPointMake(13.0f, 9.0f);
+ points[3] = CGPointMake(8.0f, 14.0f);
+ points[4] = CGPointMake(13.0f, 13.0f);
+ points[5] = CGPointMake(12.0f, 14.0f);
+ CGContextSetRGBStrokeColor(aCtx, 0.13f, 0.13f, 0.13f, 0.54f);
+ CGContextStrokeLineSegments(aCtx, points, 6);
+
+ points[0] = CGPointMake(13.0f, 6.0f);
+ points[1] = CGPointMake(5.0f, 14.0f);
+ points[2] = CGPointMake(13.0f, 10.0f);
+ points[3] = CGPointMake(9.0f, 14.0f);
+ points[5] = CGPointMake(13.0f, 13.9f);
+ points[4] = CGPointMake(13.0f, 14.0f);
+ CGContextSetRGBStrokeColor(aCtx, 0.84f, 0.84f, 0.84f, 0.55f);
+ CGContextStrokeLineSegments(aCtx, points, 6);
+}
+
+void
+nsChildView::MaybeDrawResizeIndicator(GLManager* aManager)
+{
+ MutexAutoLock lock(mEffectsLock);
+ if (!mShowsResizeIndicator) {
+ return;
+ }
+
+ if (!mResizerImage) {
+ mResizerImage = MakeUnique<RectTextureImage>();
+ }
+
+ LayoutDeviceIntSize size = mResizeIndicatorRect.Size();
+ mResizerImage->UpdateIfNeeded(size, LayoutDeviceIntRegion(), ^(gfx::DrawTarget* drawTarget, const LayoutDeviceIntRegion& updateRegion) {
+ ClearRegion(drawTarget, updateRegion);
+ gfx::BorrowedCGContext borrow(drawTarget);
+ DrawResizer(borrow.cg);
+ borrow.Finish();
+ });
+
+ mResizerImage->Draw(aManager, mResizeIndicatorRect.TopLeft());
+}
+
+// Draw the highlight line at the top of the titlebar.
+// This function draws into the current NSGraphicsContext and assumes flippedness.
+static void
+DrawTitlebarHighlight(NSSize aWindowSize, CGFloat aRadius, CGFloat aDevicePixelWidth)
+{
+ [NSGraphicsContext saveGraphicsState];
+
+ // Set up the clip path. We start with the outer rectangle and cut out a
+ // slightly smaller inner rectangle with rounded corners.
+ // The outer corners of the resulting path will be square, but they will be
+ // masked away in a later step.
+ NSBezierPath* path = [NSBezierPath bezierPath];
+ [path setWindingRule:NSEvenOddWindingRule];
+ NSRect pathRect = NSMakeRect(0, 0, aWindowSize.width, aRadius + 2);
+ [path appendBezierPathWithRect:pathRect];
+ pathRect = NSInsetRect(pathRect, aDevicePixelWidth, aDevicePixelWidth);
+ CGFloat innerRadius = aRadius - aDevicePixelWidth;
+ [path appendBezierPathWithRoundedRect:pathRect xRadius:innerRadius yRadius:innerRadius];
+ [path addClip];
+
+ // Now we fill the path with a subtle highlight gradient.
+ // We don't use NSGradient because it's 5x to 15x slower than the manual fill,
+ // as indicated by the performance test in bug 880620.
+ for (CGFloat y = 0; y < aRadius; y += aDevicePixelWidth) {
+ CGFloat t = y / aRadius;
+ [[NSColor colorWithDeviceWhite:1.0 alpha:0.4 * (1.0 - t)] set];
+ NSRectFillUsingOperation(NSMakeRect(0, y, aWindowSize.width, aDevicePixelWidth), NSCompositeSourceOver);
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+}
+
+static CGContextRef
+CreateCGContext(const LayoutDeviceIntSize& aSize)
+{
+ CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
+ CGContextRef ctx =
+ CGBitmapContextCreate(NULL,
+ aSize.width,
+ aSize.height,
+ 8 /* bitsPerComponent */,
+ aSize.width * 4,
+ cs,
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(cs);
+
+ CGContextTranslateCTM(ctx, 0, aSize.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ CGContextSetInterpolationQuality(ctx, kCGInterpolationLow);
+
+ return ctx;
+}
+
+LayoutDeviceIntSize
+TextureSizeForSize(const LayoutDeviceIntSize& aSize)
+{
+ return LayoutDeviceIntSize(RoundUpPow2(aSize.width),
+ RoundUpPow2(aSize.height));
+}
+
+// When this method is entered, mEffectsLock is already being held.
+void
+nsChildView::UpdateTitlebarCGContext()
+{
+ if (mTitlebarRect.IsEmpty()) {
+ ReleaseTitlebarCGContext();
+ return;
+ }
+
+ NSRect titlebarRect = DevPixelsToCocoaPoints(mTitlebarRect);
+ NSRect dirtyRect = [mView convertRect:[(BaseWindow*)[mView window] getAndResetNativeDirtyRect] fromView:nil];
+ NSRect dirtyTitlebarRect = NSIntersectionRect(titlebarRect, dirtyRect);
+
+ LayoutDeviceIntSize texSize = TextureSizeForSize(mTitlebarRect.Size());
+ if (!mTitlebarCGContext ||
+ CGBitmapContextGetWidth(mTitlebarCGContext) != size_t(texSize.width) ||
+ CGBitmapContextGetHeight(mTitlebarCGContext) != size_t(texSize.height)) {
+ dirtyTitlebarRect = titlebarRect;
+
+ ReleaseTitlebarCGContext();
+
+ mTitlebarCGContext = CreateCGContext(texSize);
+ }
+
+ if (NSIsEmptyRect(dirtyTitlebarRect)) {
+ return;
+ }
+
+ CGContextRef ctx = mTitlebarCGContext;
+
+ CGContextSaveGState(ctx);
+
+ double scale = BackingScaleFactor();
+ CGContextScaleCTM(ctx, scale, scale);
+
+ CGContextClipToRect(ctx, NSRectToCGRect(dirtyTitlebarRect));
+ CGContextClearRect(ctx, NSRectToCGRect(dirtyTitlebarRect));
+
+ NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
+
+ CGContextSaveGState(ctx);
+
+ BaseWindow* window = (BaseWindow*)[mView window];
+ NSView* frameView = [[window contentView] superview];
+ if (![frameView isFlipped]) {
+ CGContextTranslateCTM(ctx, 0, [frameView bounds].size.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ }
+ NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]];
+ [NSGraphicsContext setCurrentContext:context];
+
+ // Draw the title string.
+ if ([window wantsTitleDrawn] && [frameView respondsToSelector:@selector(_drawTitleBar:)]) {
+ [frameView _drawTitleBar:[frameView bounds]];
+ }
+
+ // Draw the titlebar controls into the titlebar image.
+ for (id view in [window titlebarControls]) {
+ NSRect viewFrame = [view frame];
+ NSRect viewRect = [mView convertRect:viewFrame fromView:frameView];
+ if (!NSIntersectsRect(dirtyTitlebarRect, viewRect)) {
+ continue;
+ }
+ // All of the titlebar controls we're interested in are subclasses of
+ // NSButton.
+ if (![view isKindOfClass:[NSButton class]]) {
+ continue;
+ }
+ NSButton *button = (NSButton *) view;
+ id cellObject = [button cell];
+ if (![cellObject isKindOfClass:[NSCell class]]) {
+ continue;
+ }
+ NSCell *cell = (NSCell *) cellObject;
+
+ CGContextSaveGState(ctx);
+ CGContextTranslateCTM(ctx, viewFrame.origin.x, viewFrame.origin.y);
+
+ if ([context isFlipped] != [view isFlipped]) {
+ CGContextTranslateCTM(ctx, 0, viewFrame.size.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ }
+
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[view isFlipped]]];
+
+ if ([window useBrightTitlebarForeground] && !nsCocoaFeatures::OnYosemiteOrLater() &&
+ view == [window standardWindowButton:NSWindowFullScreenButton]) {
+ // Make the fullscreen button visible on dark titlebar backgrounds by
+ // drawing it into a new transparency layer and turning it white.
+ CGRect r = NSRectToCGRect([view bounds]);
+ CGContextBeginTransparencyLayerWithRect(ctx, r, nullptr);
+
+ // Draw twice for double opacity.
+ [cell drawWithFrame:[button bounds] inView:button];
+ [cell drawWithFrame:[button bounds] inView:button];
+
+ // Make it white.
+ CGContextSetBlendMode(ctx, kCGBlendModeSourceIn);
+ CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
+ CGContextFillRect(ctx, r);
+ CGContextSetBlendMode(ctx, kCGBlendModeNormal);
+
+ CGContextEndTransparencyLayer(ctx);
+ } else {
+ [cell drawWithFrame:[button bounds] inView:button];
+ }
+
+ [NSGraphicsContext setCurrentContext:context];
+ CGContextRestoreGState(ctx);
+ }
+
+ CGContextRestoreGState(ctx);
+
+ DrawTitlebarHighlight([frameView bounds].size, [(ChildView*)mView cornerRadius],
+ DevPixelsToCocoaPoints(1));
+
+ [NSGraphicsContext setCurrentContext:oldContext];
+
+ CGContextRestoreGState(ctx);
+
+ mUpdatedTitlebarRegion.OrWith(CocoaPointsToDevPixels(dirtyTitlebarRect));
+}
+
+// This method draws an overlay in the top of the window which contains the
+// titlebar controls (e.g. close, min, zoom, fullscreen) and the titlebar
+// highlight effect.
+// This is necessary because the real titlebar controls are covered by our
+// OpenGL context. Note that in terms of the NSView hierarchy, our ChildView
+// is actually below the titlebar controls - that's why hovering and clicking
+// them works as expected - but their visual representation is only drawn into
+// the normal window buffer, and the window buffer surface lies below the
+// GLContext surface. In order to make the titlebar controls visible, we have
+// to redraw them inside the OpenGL context surface.
+void
+nsChildView::MaybeDrawTitlebar(GLManager* aManager)
+{
+ MutexAutoLock lock(mEffectsLock);
+ if (!mIsCoveringTitlebar || mIsFullscreen) {
+ return;
+ }
+
+ LayoutDeviceIntRegion updatedTitlebarRegion;
+ updatedTitlebarRegion.And(mUpdatedTitlebarRegion, mTitlebarRect);
+ mUpdatedTitlebarRegion.SetEmpty();
+
+ if (!mTitlebarImage) {
+ mTitlebarImage = MakeUnique<RectTextureImage>();
+ }
+
+ mTitlebarImage->UpdateFromCGContext(mTitlebarRect.Size(),
+ updatedTitlebarRegion,
+ mTitlebarCGContext);
+
+ mTitlebarImage->Draw(aManager, mTitlebarRect.TopLeft());
+}
+
+static void
+DrawTopLeftCornerMask(CGContextRef aCtx, int aRadius)
+{
+ CGContextSetRGBFillColor(aCtx, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillEllipseInRect(aCtx, CGRectMake(0, 0, aRadius * 2, aRadius * 2));
+}
+
+void
+nsChildView::MaybeDrawRoundedCorners(GLManager* aManager,
+ const LayoutDeviceIntRect& aRect)
+{
+ MutexAutoLock lock(mEffectsLock);
+
+ if (!mCornerMaskImage) {
+ mCornerMaskImage = MakeUnique<RectTextureImage>();
+ }
+
+ LayoutDeviceIntSize size(mDevPixelCornerRadius, mDevPixelCornerRadius);
+ mCornerMaskImage->UpdateIfNeeded(size, LayoutDeviceIntRegion(), ^(gfx::DrawTarget* drawTarget, const LayoutDeviceIntRegion& updateRegion) {
+ ClearRegion(drawTarget, updateRegion);
+ RefPtr<gfx::PathBuilder> builder = drawTarget->CreatePathBuilder();
+ builder->Arc(gfx::Point(mDevPixelCornerRadius, mDevPixelCornerRadius), mDevPixelCornerRadius, 0, 2.0f * M_PI);
+ RefPtr<gfx::Path> path = builder->Finish();
+ drawTarget->Fill(path,
+ gfx::ColorPattern(gfx::Color(1.0, 1.0, 1.0, 1.0)),
+ gfx::DrawOptions(1.0f, gfx::CompositionOp::OP_SOURCE));
+ });
+
+ // Use operator destination in: multiply all 4 channels with source alpha.
+ aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA,
+ LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA);
+
+ Matrix4x4 flipX = Matrix4x4::Scaling(-1, 1, 1);
+ Matrix4x4 flipY = Matrix4x4::Scaling(1, -1, 1);
+
+ if (mIsCoveringTitlebar && !mIsFullscreen) {
+ // Mask the top corners.
+ mCornerMaskImage->Draw(aManager, aRect.TopLeft());
+ mCornerMaskImage->Draw(aManager, aRect.TopRight(), flipX);
+ }
+
+ if (mHasRoundedBottomCorners && !mIsFullscreen) {
+ // Mask the bottom corners.
+ mCornerMaskImage->Draw(aManager, aRect.BottomLeft(), flipY);
+ mCornerMaskImage->Draw(aManager, aRect.BottomRight(), flipY * flipX);
+ }
+
+ // Reset blend mode.
+ aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
+ LOCAL_GL_ONE, LOCAL_GL_ONE);
+}
+
+static int32_t
+FindTitlebarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ int32_t aWindowWidth)
+{
+ int32_t titlebarBottom = 0;
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if ((g.mType == nsNativeThemeCocoa::eThemeGeometryTypeTitlebar) &&
+ g.mRect.X() <= 0 &&
+ g.mRect.XMost() >= aWindowWidth &&
+ g.mRect.Y() <= 0) {
+ titlebarBottom = std::max(titlebarBottom, g.mRect.YMost());
+ }
+ }
+ return titlebarBottom;
+}
+
+static int32_t
+FindUnifiedToolbarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ int32_t aWindowWidth, int32_t aTitlebarBottom)
+{
+ int32_t unifiedToolbarBottom = aTitlebarBottom;
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if ((g.mType == nsNativeThemeCocoa::eThemeGeometryTypeToolbar) &&
+ g.mRect.X() <= 0 &&
+ g.mRect.XMost() >= aWindowWidth &&
+ g.mRect.Y() <= aTitlebarBottom) {
+ unifiedToolbarBottom = std::max(unifiedToolbarBottom, g.mRect.YMost());
+ }
+ }
+ return unifiedToolbarBottom;
+}
+
+static LayoutDeviceIntRect
+FindFirstRectOfType(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if (g.mType == aThemeGeometryType) {
+ return g.mRect;
+ }
+ }
+ return LayoutDeviceIntRect();
+}
+
+void
+nsChildView::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries)
+{
+ if (![mView window])
+ return;
+
+ UpdateVibrancy(aThemeGeometries);
+
+ if (![[mView window] isKindOfClass:[ToolbarWindow class]])
+ return;
+
+ // Update unified toolbar height and sheet attachment position.
+ int32_t windowWidth = mBounds.width;
+ int32_t titlebarBottom = FindTitlebarBottom(aThemeGeometries, windowWidth);
+ int32_t unifiedToolbarBottom =
+ FindUnifiedToolbarBottom(aThemeGeometries, windowWidth, titlebarBottom);
+ int32_t toolboxBottom =
+ FindFirstRectOfType(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeToolbox).YMost();
+
+ ToolbarWindow* win = (ToolbarWindow*)[mView window];
+ bool drawsContentsIntoWindowFrame = [win drawsContentsIntoWindowFrame];
+ int32_t titlebarHeight = CocoaPointsToDevPixels([win titlebarHeight]);
+ int32_t contentOffset = drawsContentsIntoWindowFrame ? titlebarHeight : 0;
+ int32_t devUnifiedHeight = titlebarHeight + unifiedToolbarBottom - contentOffset;
+ [win setUnifiedToolbarHeight:DevPixelsToCocoaPoints(devUnifiedHeight)];
+ int32_t devSheetPosition = titlebarHeight + std::max(toolboxBottom, unifiedToolbarBottom) - contentOffset;
+ [win setSheetAttachmentPosition:DevPixelsToCocoaPoints(devSheetPosition)];
+
+ // Update titlebar control offsets.
+ LayoutDeviceIntRect windowButtonRect = FindFirstRectOfType(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeWindowButtons);
+ [win placeWindowButtons:[mView convertRect:DevPixelsToCocoaPoints(windowButtonRect) toView:nil]];
+ LayoutDeviceIntRect fullScreenButtonRect = FindFirstRectOfType(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeFullscreenButton);
+ [win placeFullScreenButton:[mView convertRect:DevPixelsToCocoaPoints(fullScreenButtonRect) toView:nil]];
+}
+
+static LayoutDeviceIntRegion
+GatherThemeGeometryRegion(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
+ nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ LayoutDeviceIntRegion region;
+ for (size_t i = 0; i < aThemeGeometries.Length(); ++i) {
+ const nsIWidget::ThemeGeometry& g = aThemeGeometries[i];
+ if (g.mType == aThemeGeometryType) {
+ region.OrWith(g.mRect);
+ }
+ }
+ return region;
+}
+
+template<typename Region>
+static void MakeRegionsNonOverlappingImpl(Region& aOutUnion) { }
+
+template<typename Region, typename ... Regions>
+static void MakeRegionsNonOverlappingImpl(Region& aOutUnion, Region& aFirst, Regions& ... aRest)
+{
+ MakeRegionsNonOverlappingImpl(aOutUnion, aRest...);
+ aFirst.SubOut(aOutUnion);
+ aOutUnion.OrWith(aFirst);
+}
+
+// Subtracts parts from regions in such a way that they don't have any overlap.
+// Each region in the argument list will have the union of all the regions
+// *following* it subtracted from itself. In other words, the arguments are
+// sorted low priority to high priority.
+template<typename Region, typename ... Regions>
+static void MakeRegionsNonOverlapping(Region& aFirst, Regions& ... aRest)
+{
+ Region unionOfAll;
+ MakeRegionsNonOverlappingImpl(unionOfAll, aFirst, aRest...);
+}
+
+void
+nsChildView::UpdateVibrancy(const nsTArray<ThemeGeometry>& aThemeGeometries)
+{
+ if (!VibrancyManager::SystemSupportsVibrancy()) {
+ return;
+ }
+
+ LayoutDeviceIntRegion sheetRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeSheet);
+ LayoutDeviceIntRegion vibrantLightRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeVibrancyLight);
+ LayoutDeviceIntRegion vibrantDarkRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeVibrancyDark);
+ LayoutDeviceIntRegion menuRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeMenu);
+ LayoutDeviceIntRegion tooltipRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeTooltip);
+ LayoutDeviceIntRegion highlightedMenuItemRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeHighlightedMenuItem);
+ LayoutDeviceIntRegion sourceListRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeSourceList);
+ LayoutDeviceIntRegion sourceListSelectionRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeSourceListSelection);
+ LayoutDeviceIntRegion activeSourceListSelectionRegion =
+ GatherThemeGeometryRegion(aThemeGeometries, nsNativeThemeCocoa::eThemeGeometryTypeActiveSourceListSelection);
+
+ MakeRegionsNonOverlapping(sheetRegion, vibrantLightRegion, vibrantDarkRegion,
+ menuRegion, tooltipRegion, highlightedMenuItemRegion,
+ sourceListRegion, sourceListSelectionRegion,
+ activeSourceListSelectionRegion);
+
+ auto& vm = EnsureVibrancyManager();
+ vm.UpdateVibrantRegion(VibrancyType::LIGHT, vibrantLightRegion);
+ vm.UpdateVibrantRegion(VibrancyType::TOOLTIP, tooltipRegion);
+ vm.UpdateVibrantRegion(VibrancyType::MENU, menuRegion);
+ vm.UpdateVibrantRegion(VibrancyType::HIGHLIGHTED_MENUITEM, highlightedMenuItemRegion);
+ vm.UpdateVibrantRegion(VibrancyType::SHEET, sheetRegion);
+ vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST, sourceListRegion);
+ vm.UpdateVibrantRegion(VibrancyType::SOURCE_LIST_SELECTION, sourceListSelectionRegion);
+ vm.UpdateVibrantRegion(VibrancyType::ACTIVE_SOURCE_LIST_SELECTION, activeSourceListSelectionRegion);
+ vm.UpdateVibrantRegion(VibrancyType::DARK, vibrantDarkRegion);
+}
+
+static VibrancyType
+ThemeGeometryTypeToVibrancyType(nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ switch (aThemeGeometryType) {
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrancyLight:
+ return VibrancyType::LIGHT;
+ case nsNativeThemeCocoa::eThemeGeometryTypeVibrancyDark:
+ return VibrancyType::DARK;
+ case nsNativeThemeCocoa::eThemeGeometryTypeTooltip:
+ return VibrancyType::TOOLTIP;
+ case nsNativeThemeCocoa::eThemeGeometryTypeMenu:
+ return VibrancyType::MENU;
+ case nsNativeThemeCocoa::eThemeGeometryTypeHighlightedMenuItem:
+ return VibrancyType::HIGHLIGHTED_MENUITEM;
+ case nsNativeThemeCocoa::eThemeGeometryTypeSheet:
+ return VibrancyType::SHEET;
+ case nsNativeThemeCocoa::eThemeGeometryTypeSourceList:
+ return VibrancyType::SOURCE_LIST;
+ case nsNativeThemeCocoa::eThemeGeometryTypeSourceListSelection:
+ return VibrancyType::SOURCE_LIST_SELECTION;
+ case nsNativeThemeCocoa::eThemeGeometryTypeActiveSourceListSelection:
+ return VibrancyType::ACTIVE_SOURCE_LIST_SELECTION;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+NSColor*
+nsChildView::VibrancyFillColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ return EnsureVibrancyManager().VibrancyFillColorForType(
+ ThemeGeometryTypeToVibrancyType(aThemeGeometryType));
+ }
+ return [NSColor whiteColor];
+}
+
+NSColor*
+nsChildView::VibrancyFontSmoothingBackgroundColorForThemeGeometryType(nsITheme::ThemeGeometryType aThemeGeometryType)
+{
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ return EnsureVibrancyManager().VibrancyFontSmoothingBackgroundColorForType(
+ ThemeGeometryTypeToVibrancyType(aThemeGeometryType));
+ }
+ return [NSColor clearColor];
+}
+
+mozilla::VibrancyManager&
+nsChildView::EnsureVibrancyManager()
+{
+ MOZ_ASSERT(mView, "Only call this once we have a view!");
+ if (!mVibrancyManager) {
+ mVibrancyManager = MakeUnique<VibrancyManager>(*this, [mView vibrancyViewsContainer]);
+ }
+ return *mVibrancyManager;
+}
+
+nsChildView::SwipeInfo
+nsChildView::SendMayStartSwipe(const mozilla::PanGestureInput& aSwipeStartEvent)
+{
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
+ uint32_t direction = (aSwipeStartEvent.mPanDisplacement.x > 0.0)
+ ? (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_RIGHT
+ : (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
+
+ // We're ready to start the animation. Tell Gecko about it, and at the same
+ // time ask it if it really wants to start an animation for this event.
+ // This event also reports back the directions that we can swipe in.
+ LayoutDeviceIntPoint position =
+ RoundedToInt(aSwipeStartEvent.mPanStartPoint * ScreenToLayoutDeviceScale(1));
+ WidgetSimpleGestureEvent geckoEvent =
+ SwipeTracker::CreateSwipeGestureEvent(eSwipeGestureMayStart, this,
+ position);
+ geckoEvent.mDirection = direction;
+ geckoEvent.mDelta = 0.0;
+ geckoEvent.mAllowedDirections = 0;
+ bool shouldStartSwipe = DispatchWindowEvent(geckoEvent); // event cancelled == swipe should start
+
+ SwipeInfo result = { shouldStartSwipe, geckoEvent.mAllowedDirections };
+ return result;
+}
+
+void
+nsChildView::TrackScrollEventAsSwipe(const mozilla::PanGestureInput& aSwipeStartEvent,
+ uint32_t aAllowedDirections)
+{
+ // If a swipe is currently being tracked kill it -- it's been interrupted
+ // by another gesture event.
+ if (mSwipeTracker) {
+ mSwipeTracker->CancelSwipe();
+ mSwipeTracker->Destroy();
+ mSwipeTracker = nullptr;
+ }
+
+ uint32_t direction = (aSwipeStartEvent.mPanDisplacement.x > 0.0)
+ ? (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_RIGHT
+ : (uint32_t)nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
+
+ mSwipeTracker = new SwipeTracker(*this, aSwipeStartEvent,
+ aAllowedDirections, direction);
+
+ if (!mAPZC) {
+ mCurrentPanGestureBelongsToSwipe = true;
+ }
+}
+
+void
+nsChildView::SwipeFinished()
+{
+ mSwipeTracker = nullptr;
+}
+
+already_AddRefed<gfx::DrawTarget>
+nsChildView::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion,
+ BufferMode* aBufferMode)
+{
+ // should have created the GLPresenter in InitCompositor.
+ MOZ_ASSERT(mGLPresenter);
+ if (!mGLPresenter) {
+ mGLPresenter = GLPresenter::CreateForWindow(this);
+
+ if (!mGLPresenter) {
+ return nullptr;
+ }
+ }
+
+ LayoutDeviceIntRegion dirtyRegion(aInvalidRegion);
+ LayoutDeviceIntSize renderSize = mBounds.Size();
+
+ if (!mBasicCompositorImage) {
+ mBasicCompositorImage = MakeUnique<RectTextureImage>();
+ }
+
+ RefPtr<gfx::DrawTarget> drawTarget =
+ mBasicCompositorImage->BeginUpdate(renderSize, dirtyRegion);
+
+ if (!drawTarget) {
+ // Composite unchanged textures.
+ DoRemoteComposition(mBounds);
+ return nullptr;
+ }
+
+ aInvalidRegion = mBasicCompositorImage->GetUpdateRegion();
+ *aBufferMode = BufferMode::BUFFER_NONE;
+
+ return drawTarget.forget();
+}
+
+void
+nsChildView::EndRemoteDrawing()
+{
+ mBasicCompositorImage->EndUpdate();
+ DoRemoteComposition(mBounds);
+}
+
+void
+nsChildView::CleanupRemoteDrawing()
+{
+ mBasicCompositorImage = nullptr;
+ mCornerMaskImage = nullptr;
+ mResizerImage = nullptr;
+ mTitlebarImage = nullptr;
+ mGLPresenter = nullptr;
+}
+
+bool
+nsChildView::InitCompositor(Compositor* aCompositor)
+{
+ if (aCompositor->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ if (!mGLPresenter) {
+ mGLPresenter = GLPresenter::CreateForWindow(this);
+ }
+
+ return !!mGLPresenter;
+ }
+ return true;
+}
+
+void
+nsChildView::DoRemoteComposition(const LayoutDeviceIntRect& aRenderRect)
+{
+ if (![(ChildView*)mView preRender:mGLPresenter->GetNSOpenGLContext()]) {
+ return;
+ }
+ mGLPresenter->BeginFrame(aRenderRect.Size());
+
+ // Draw the result from the basic compositor.
+ mBasicCompositorImage->Draw(mGLPresenter.get(), LayoutDeviceIntPoint(0, 0));
+
+ // DrawWindowOverlay doesn't do anything for non-GL, so it didn't paint
+ // anything during the basic compositor transaction. Draw the overlay now.
+ DrawWindowOverlay(mGLPresenter.get(), aRenderRect);
+
+ mGLPresenter->EndFrame();
+
+ [(ChildView*)mView postRender:mGLPresenter->GetNSOpenGLContext()];
+}
+
+@interface NonDraggableView : NSView
+@end
+
+@implementation NonDraggableView
+- (BOOL)mouseDownCanMoveWindow { return NO; }
+- (NSView*)hitTest:(NSPoint)aPoint { return nil; }
+@end
+
+void
+nsChildView::UpdateWindowDraggingRegion(const LayoutDeviceIntRegion& aRegion)
+{
+ // mView returns YES from mouseDownCanMoveWindow, so we need to put NSViews
+ // that return NO from mouseDownCanMoveWindow in the places that shouldn't
+ // be draggable. We can't do it the other way round because returning
+ // YES from mouseDownCanMoveWindow doesn't have any effect if there's a
+ // superview that returns NO.
+ LayoutDeviceIntRegion nonDraggable;
+ nonDraggable.Sub(LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height), aRegion);
+
+ __block bool changed = false;
+
+ // Suppress calls to setNeedsDisplay during NSView geometry changes.
+ ManipulateViewWithoutNeedingDisplay(mView, ^() {
+ changed = mNonDraggableRegion.UpdateRegion(
+ nonDraggable, *this, [mView nonDraggableViewsContainer], ^() {
+ return [[NonDraggableView alloc] initWithFrame:NSZeroRect];
+ });
+ });
+
+ if (changed) {
+ // Trigger an update to the window server. This will call
+ // mouseDownCanMoveWindow.
+ // Doing this manually is only necessary because we're suppressing
+ // setNeedsDisplay calls above.
+ [[mView window] setMovableByWindowBackground:NO];
+ [[mView window] setMovableByWindowBackground:YES];
+ }
+}
+
+void
+nsChildView::ReportSwipeStarted(uint64_t aInputBlockId,
+ bool aStartSwipe)
+{
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == aInputBlockId) {
+ if (aStartSwipe) {
+ PanGestureInput& startEvent = mSwipeEventQueue->queuedEvents[0];
+ TrackScrollEventAsSwipe(startEvent, mSwipeEventQueue->allowedDirections);
+ for (size_t i = 1; i < mSwipeEventQueue->queuedEvents.Length(); i++) {
+ mSwipeTracker->ProcessEvent(mSwipeEventQueue->queuedEvents[i]);
+ }
+ }
+ mSwipeEventQueue = nullptr;
+ }
+}
+
+void
+nsChildView::DispatchAPZWheelInputEvent(InputData& aEvent, bool aCanTriggerSwipe)
+{
+ if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
+ // Give the swipe tracker a first pass at the event. If a new pan gesture
+ // has been started since the beginning of the swipe, the swipe tracker
+ // will know to ignore the event.
+ nsEventStatus status = mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ WidgetWheelEvent event(true, eWheel, this);
+
+ if (mAPZC) {
+ uint64_t inputBlockId = 0;
+ ScrollableLayerGuid guid;
+
+ nsEventStatus result = mAPZC->ReceiveInputEvent(aEvent, &guid, &inputBlockId);
+ if (result == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ switch(aEvent.mInputType) {
+ case PANGESTURE_INPUT: {
+ PanGestureInput& panInput = aEvent.AsPanGestureInput();
+
+ event = panInput.ToWidgetWheelEvent(this);
+ if (aCanTriggerSwipe) {
+ SwipeInfo swipeInfo = SendMayStartSwipe(panInput);
+ event.mCanTriggerSwipe = swipeInfo.wantsSwipe;
+ if (swipeInfo.wantsSwipe) {
+ if (result == nsEventStatus_eIgnore) {
+ // APZ has determined and that scrolling horizontally in the
+ // requested direction is impossible, so it didn't do any
+ // scrolling for the event.
+ // We know now that MayStartSwipe wants a swipe, so we can start
+ // the swipe now.
+ TrackScrollEventAsSwipe(panInput, swipeInfo.allowedDirections);
+ } else {
+ // We don't know whether this event can start a swipe, so we need
+ // to queue up events and wait for a call to ReportSwipeStarted.
+ // APZ might already have started scrolling in response to the
+ // event if it knew that it's the right thing to do. In that case
+ // we'll still get a call to ReportSwipeStarted, and we will
+ // discard the queued events at that point.
+ mSwipeEventQueue = MakeUnique<SwipeEventQueue>(swipeInfo.allowedDirections,
+ inputBlockId);
+ }
+ }
+ }
+
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == inputBlockId) {
+ mSwipeEventQueue->queuedEvents.AppendElement(panInput);
+ }
+ break;
+ }
+ case SCROLLWHEEL_INPUT: {
+ event = aEvent.AsScrollWheelInput().ToWidgetWheelEvent(this);
+ break;
+ };
+ default:
+ MOZ_CRASH("unsupported event type");
+ return;
+ }
+ if (event.mMessage == eWheel &&
+ (event.mDeltaX != 0 || event.mDeltaY != 0)) {
+ ProcessUntransformedAPZEvent(&event, guid, inputBlockId, result);
+ }
+ return;
+ }
+
+ nsEventStatus status;
+ switch(aEvent.mInputType) {
+ case PANGESTURE_INPUT: {
+ PanGestureInput panInput = aEvent.AsPanGestureInput();
+ if (panInput.mType == PanGestureInput::PANGESTURE_MAYSTART ||
+ panInput.mType == PanGestureInput::PANGESTURE_START) {
+ mCurrentPanGestureBelongsToSwipe = false;
+ }
+ if (mCurrentPanGestureBelongsToSwipe) {
+ // Ignore this event. It's a momentum event from a scroll gesture
+ // that was processed as a swipe, and the swipe animation has
+ // already finished (so mSwipeTracker is already null).
+ MOZ_ASSERT(panInput.IsMomentum(),
+ "If the fingers are still on the touchpad, we should still have a SwipeTracker, and it should have consumed this event.");
+ return;
+ }
+
+ event = panInput.ToWidgetWheelEvent(this);
+ if (aCanTriggerSwipe) {
+ SwipeInfo swipeInfo = SendMayStartSwipe(panInput);
+
+ // We're in the non-APZ case here, but we still want to know whether
+ // the event was routed to a child process, so we use InputAPZContext
+ // to get that piece of information.
+ ScrollableLayerGuid guid;
+ InputAPZContext context(guid, 0, nsEventStatus_eIgnore);
+
+ event.mCanTriggerSwipe = swipeInfo.wantsSwipe;
+ DispatchEvent(&event, status);
+ if (swipeInfo.wantsSwipe) {
+ if (context.WasRoutedToChildProcess()) {
+ // We don't know whether this event can start a swipe, so we need
+ // to queue up events and wait for a call to ReportSwipeStarted.
+ mSwipeEventQueue = MakeUnique<SwipeEventQueue>(swipeInfo.allowedDirections, 0);
+ } else if (event.TriggersSwipe()) {
+ TrackScrollEventAsSwipe(panInput, swipeInfo.allowedDirections);
+ }
+ }
+
+ if (mSwipeEventQueue && mSwipeEventQueue->inputBlockId == 0) {
+ mSwipeEventQueue->queuedEvents.AppendElement(panInput);
+ }
+ return;
+ }
+ break;
+ }
+ case SCROLLWHEEL_INPUT: {
+ event = aEvent.AsScrollWheelInput().ToWidgetWheelEvent(this);
+ break;
+ }
+ default:
+ MOZ_CRASH("unexpected event type");
+ return;
+ }
+ if (event.mMessage == eWheel &&
+ (event.mDeltaX != 0 || event.mDeltaY != 0)) {
+ DispatchEvent(&event, status);
+ }
+}
+
+// When using 10.11, calling showDefinitionForAttributedString causes the
+// following exception on LookupViewService. (rdar://26476091)
+//
+// Exception: decodeObjectForKey: class "TitlebarAndBackgroundColor" not
+// loaded or does not exist
+//
+// So we set temporary color that is NSColor before calling it.
+
+class MOZ_RAII AutoBackgroundSetter final {
+public:
+ explicit AutoBackgroundSetter(NSView* aView) {
+ if (nsCocoaFeatures::OnElCapitanOrLater() &&
+ [[aView window] isKindOfClass:[ToolbarWindow class]]) {
+ mWindow = [(ToolbarWindow*)[aView window] retain];
+ [mWindow setTemporaryBackgroundColor];
+ } else {
+ mWindow = nullptr;
+ }
+ }
+
+ ~AutoBackgroundSetter() {
+ if (mWindow) {
+ [mWindow restoreBackgroundColor];
+ [mWindow release];
+ }
+ }
+
+private:
+ ToolbarWindow* mWindow; // strong
+};
+
+void
+nsChildView::LookUpDictionary(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRangeArray,
+ const bool aIsVertical,
+ const LayoutDeviceIntPoint& aPoint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSMutableAttributedString* attrStr =
+ nsCocoaUtils::GetNSMutableAttributedString(aText, aFontRangeArray,
+ aIsVertical,
+ BackingScaleFactor());
+ NSPoint pt =
+ nsCocoaUtils::DevPixelsToCocoaPoints(aPoint, BackingScaleFactor());
+ NSDictionary* attributes = [attrStr attributesAtIndex:0 effectiveRange:nil];
+ NSFont* font = [attributes objectForKey:NSFontAttributeName];
+ if (font) {
+ if (aIsVertical) {
+ pt.x -= [font descender];
+ } else {
+ pt.y += [font ascender];
+ }
+ }
+
+ AutoBackgroundSetter setter(mView);
+ [mView showDefinitionForAttributedString:attrStr atPoint:pt];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#ifdef ACCESSIBILITY
+already_AddRefed<a11y::Accessible>
+nsChildView::GetDocumentAccessible()
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return nullptr;
+
+ if (mAccessible) {
+ RefPtr<a11y::Accessible> ret;
+ CallQueryReferent(mAccessible.get(),
+ static_cast<a11y::Accessible**>(getter_AddRefs(ret)));
+ return ret.forget();
+ }
+
+ // need to fetch the accessible anew, because it has gone away.
+ // cache the accessible in our weak ptr
+ RefPtr<a11y::Accessible> acc = GetRootAccessible();
+ mAccessible = do_GetWeakReference(acc.get());
+
+ return acc.forget();
+}
+#endif
+
+// GLPresenter implementation
+
+GLPresenter::GLPresenter(GLContext* aContext)
+ : mGLContext(aContext)
+{
+ mGLContext->MakeCurrent();
+ ShaderConfigOGL config;
+ config.SetTextureTarget(LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+ mRGBARectProgram = MakeUnique<ShaderProgramOGL>(mGLContext,
+ ProgramProfileOGL::GetProfileFor(config));
+
+ // Create mQuadVBO.
+ mGLContext->fGenBuffers(1, &mQuadVBO);
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
+
+ // 1 quad, with the number of the quad (vertexID) encoded in w.
+ GLfloat vertices[] = {
+ 0.0f, 0.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 0.0f,
+ };
+ HeapCopyOfStackArray<GLfloat> verticesOnHeap(vertices);
+ mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER,
+ verticesOnHeap.ByteLength(),
+ verticesOnHeap.Data(),
+ LOCAL_GL_STATIC_DRAW);
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
+}
+
+GLPresenter::~GLPresenter()
+{
+ if (mQuadVBO) {
+ mGLContext->MakeCurrent();
+ mGLContext->fDeleteBuffers(1, &mQuadVBO);
+ mQuadVBO = 0;
+ }
+}
+
+void
+GLPresenter::BindAndDrawQuad(ShaderProgramOGL *aProgram,
+ const gfx::Rect& aLayerRect,
+ const gfx::Rect& aTextureRect)
+{
+ mGLContext->MakeCurrent();
+
+ gfx::Rect layerRects[4];
+ gfx::Rect textureRects[4];
+
+ layerRects[0] = aLayerRect;
+ textureRects[0] = aTextureRect;
+
+ aProgram->SetLayerRects(layerRects);
+ aProgram->SetTextureRects(textureRects);
+
+ const GLuint coordAttribIndex = 0;
+
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO);
+ mGLContext->fVertexAttribPointer(coordAttribIndex, 4,
+ LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0,
+ (GLvoid*)0);
+ mGLContext->fEnableVertexAttribArray(coordAttribIndex);
+ mGLContext->fDrawArrays(LOCAL_GL_TRIANGLES, 0, 6);
+ mGLContext->fDisableVertexAttribArray(coordAttribIndex);
+}
+
+void
+GLPresenter::BeginFrame(LayoutDeviceIntSize aRenderSize)
+{
+ mGLContext->MakeCurrent();
+
+ mGLContext->fViewport(0, 0, aRenderSize.width, aRenderSize.height);
+
+ // Matrix to transform (0, 0, width, height) to viewport space (-1.0, 1.0,
+ // 2, 2) and flip the contents.
+ gfx::Matrix viewMatrix = gfx::Matrix::Translation(-1.0, 1.0);
+ viewMatrix.PreScale(2.0f / float(aRenderSize.width),
+ 2.0f / float(aRenderSize.height));
+ viewMatrix.PreScale(1.0f, -1.0f);
+
+ gfx::Matrix4x4 matrix3d = gfx::Matrix4x4::From2D(viewMatrix);
+ matrix3d._33 = 0.0f;
+
+ // set the projection matrix for the next time the program is activated
+ mProjMatrix = matrix3d;
+
+ // Default blend function implements "OVER"
+ mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
+ LOCAL_GL_ONE, LOCAL_GL_ONE);
+ mGLContext->fEnable(LOCAL_GL_BLEND);
+
+ mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0);
+ mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);
+
+ mGLContext->fEnable(LOCAL_GL_TEXTURE_RECTANGLE_ARB);
+}
+
+void
+GLPresenter::EndFrame()
+{
+ mGLContext->SwapBuffers();
+ mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
+}
+
+class WidgetsReleaserRunnable final : public mozilla::Runnable
+{
+public:
+ explicit WidgetsReleaserRunnable(nsTArray<nsCOMPtr<nsIWidget>>&& aWidgetArray)
+ : mWidgetArray(aWidgetArray)
+ {
+ }
+
+ // Do nothing; all this runnable does is hold a reference the widgets in
+ // mWidgetArray, and those references will be dropped when this runnable
+ // is destroyed.
+
+private:
+ nsTArray<nsCOMPtr<nsIWidget>> mWidgetArray;
+};
+
+#pragma mark -
+
+// ViewRegionContainerView is a view class for certain subviews of ChildView
+// which contain the NSViews created for ViewRegions (see ViewRegion.h).
+// It doesn't do anything interesting, it only acts as a container so that it's
+// easier for ChildView to control the z order of its children.
+@interface ViewRegionContainerView : NSView {
+}
+@end
+
+@implementation ViewRegionContainerView
+
+- (NSView*)hitTest:(NSPoint)aPoint {
+ return nil; // Be transparent to mouse events.
+}
+
+- (BOOL)isFlipped {
+ return [[self superview] isFlipped];
+}
+
+- (BOOL)mouseDownCanMoveWindow {
+ return [[self superview] mouseDownCanMoveWindow];
+}
+
+@end
+;
+
+@implementation ChildView
+
+// globalDragPboard is non-null during native drag sessions that did not originate
+// in our native NSView (it is set in |draggingEntered:|). It is unset when the
+// drag session ends for this view, either with the mouse exiting or when a drop
+// occurs in this view.
+NSPasteboard* globalDragPboard = nil;
+
+// gLastDragView and gLastDragMouseDownEvent are used to communicate information
+// to the drag service during drag invocation (starting a drag in from the view).
+// gLastDragView is only non-null while mouseDragged is on the call stack.
+NSView* gLastDragView = nil;
+NSEvent* gLastDragMouseDownEvent = nil;
+
++ (void)initialize
+{
+ static BOOL initialized = NO;
+
+ if (!initialized) {
+ // Inform the OS about the types of services (from the "Services" menu)
+ // that we can handle.
+
+ NSArray *sendTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil];
+ NSArray *returnTypes = [[NSArray alloc] initWithObjects:NSStringPboardType,NSHTMLPboardType,nil];
+
+ [NSApp registerServicesMenuSendTypes:sendTypes returnTypes:returnTypes];
+
+ [sendTypes release];
+ [returnTypes release];
+
+ initialized = YES;
+ }
+}
+
++ (void)registerViewForDraggedTypes:(NSView*)aView
+{
+ [aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
+ NSStringPboardType,
+ NSHTMLPboardType,
+ NSURLPboardType,
+ NSFilesPromisePboardType,
+ kWildcardPboardType,
+ kCorePboardType_url,
+ kCorePboardType_urld,
+ kCorePboardType_urln,
+ nil]];
+}
+
+// initWithFrame:geckoChild:
+- (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super initWithFrame:inFrame])) {
+ mGeckoChild = inChild;
+ mBlockedLastMouseDown = NO;
+ mExpectingWheelStop = NO;
+
+ mLastMouseDownEvent = nil;
+ mLastKeyDownEvent = nil;
+ mClickThroughMouseDownEvent = nil;
+ mDragService = nullptr;
+
+ mGestureState = eGestureState_None;
+ mCumulativeMagnification = 0.0;
+ mCumulativeRotation = 0.0;
+
+ mNeedsGLUpdate = NO;
+
+ [self setFocusRingType:NSFocusRingTypeNone];
+
+#ifdef __LP64__
+ mCancelSwipeAnimation = nil;
+#endif
+
+ mNonDraggableViewsContainer = [[ViewRegionContainerView alloc] initWithFrame:[self bounds]];
+ mVibrancyViewsContainer = [[ViewRegionContainerView alloc] initWithFrame:[self bounds]];
+
+ [mNonDraggableViewsContainer setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [mVibrancyViewsContainer setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+ [self addSubview:mNonDraggableViewsContainer];
+ [self addSubview:mVibrancyViewsContainer];
+
+ mPixelHostingView = [[PixelHostingView alloc] initWithFrame:[self bounds]];
+ [mPixelHostingView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+ [self addSubview:mPixelHostingView];
+
+ mTopLeftCornerMask = NULL;
+ }
+
+ // register for things we'll take from other applications
+ [ChildView registerViewForDraggedTypes:self];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:NSControlTintDidChangeNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:NSSystemColorsDidChangeNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(scrollbarSystemMetricChanged)
+ name:NSPreferredScrollerStyleDidChangeNotification
+ object:nil];
+ [[NSDistributedNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(systemMetricsChanged)
+ name:@"AppleAquaScrollBarVariantChanged"
+ object:nil
+ suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(_surfaceNeedsUpdate:)
+ name:NSViewGlobalFrameDidChangeNotification
+ object:mPixelHostingView];
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// ComplexTextInputPanel's interpretKeyEvent hack won't work without this.
+// It makes calls to +[NSTextInputContext currentContext], deep in system
+// code, return the appropriate context.
+- (NSTextInputContext *)inputContext
+{
+ NSTextInputContext* pluginContext = NULL;
+ if (mGeckoChild && mGeckoChild->IsPluginFocused()) {
+ ComplexTextInputPanel* ctiPanel =
+ ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+ if (ctiPanel) {
+ pluginContext = (NSTextInputContext*) ctiPanel->GetInputContext();
+ }
+ }
+ if (pluginContext) {
+ return pluginContext;
+ } else {
+ if (!mGeckoChild) {
+ // -[ChildView widgetDestroyed] has been called, but
+ // -[ChildView delayedTearDown] has not yet completed. Accessing
+ // [super inputContext] now would uselessly recreate a text input context
+ // for us, under which -[ChildView validAttributesForMarkedText] would
+ // be called and the assertion checking for mTextInputHandler would fail.
+ // We return nil to avoid that.
+ return nil;
+ }
+ return [super inputContext];
+ }
+}
+
+- (void)installTextInputHandler:(TextInputHandler*)aHandler
+{
+ mTextInputHandler = aHandler;
+}
+
+- (void)uninstallTextInputHandler
+{
+ mTextInputHandler = nullptr;
+}
+
+- (bool)preRender:(NSOpenGLContext *)aGLContext
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (![self window] ||
+ ([[self window] isKindOfClass:[BaseWindow class]] &&
+ ![(BaseWindow*)[self window] isVisibleOrBeingShown])) {
+ // Before the window is shown, our GL context's front FBO is not
+ // framebuffer complete, so we refuse to render.
+ return false;
+ }
+
+ if (!mGLContext) {
+ mGLContext = aGLContext;
+ [mGLContext retain];
+ mNeedsGLUpdate = true;
+ }
+
+ CGLLockContext((CGLContextObj)[aGLContext CGLContextObj]);
+
+ if (mNeedsGLUpdate) {
+ [self updateGLContext];
+ mNeedsGLUpdate = NO;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+- (void)postRender:(NSOpenGLContext *)aGLContext
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ CGLUnlockContext((CGLContextObj)[aGLContext CGLContextObj]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSView*)vibrancyViewsContainer {
+ return mVibrancyViewsContainer;
+}
+
+- (NSView*)nonDraggableViewsContainer {
+ return mNonDraggableViewsContainer;
+}
+
+- (NSView*)pixelHostingView {
+ return mPixelHostingView;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mGLContext release];
+ [mLastMouseDownEvent release];
+ [mLastKeyDownEvent release];
+ [mClickThroughMouseDownEvent release];
+ CGImageRelease(mTopLeftCornerMask);
+ ChildViewMouseTracker::OnDestroyView(self);
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
+ [mVibrancyViewsContainer removeFromSuperview];
+ [mVibrancyViewsContainer release];
+ [mNonDraggableViewsContainer removeFromSuperview];
+ [mNonDraggableViewsContainer release];
+ [mPixelHostingView removeFromSuperview];
+ [mPixelHostingView release];
+
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)widgetDestroyed
+{
+ if (mTextInputHandler) {
+ mTextInputHandler->OnDestroyWidget(mGeckoChild);
+ mTextInputHandler = nullptr;
+ }
+ mGeckoChild = nullptr;
+
+ // Just in case we're destroyed abruptly and missed the draggingExited
+ // or performDragOperation message.
+ NS_IF_RELEASE(mDragService);
+}
+
+// mozView method, return our gecko child view widget. Note this does not AddRef.
+- (nsIWidget*) widget
+{
+ return static_cast<nsIWidget*>(mGeckoChild);
+}
+
+- (void)systemMetricsChanged
+{
+ if (mGeckoChild)
+ mGeckoChild->NotifyThemeChanged();
+}
+
+- (void)scrollbarSystemMetricChanged
+{
+ [self systemMetricsChanged];
+
+ if (mGeckoChild) {
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener) {
+ nsIPresShell* presShell = listener->GetPresShell();
+ if (presShell) {
+ presShell->ReconstructFrames();
+ }
+ }
+ }
+}
+
+- (NSString*)description
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSString stringWithFormat:@"ChildView %p, gecko child %p, frame %@", self, mGeckoChild, NSStringFromRect([self frame])];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Make the origin of this view the topLeft corner (gecko origin) rather
+// than the bottomLeft corner (standard cocoa origin).
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (BOOL)isOpaque
+{
+ return [[self window] isOpaque];
+}
+
+- (void)sendFocusEvent:(EventMessage)eventMessage
+{
+ if (!mGeckoChild)
+ return;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetGUIEvent focusGuiEvent(true, eventMessage, mGeckoChild);
+ focusGuiEvent.mTime = PR_IntervalNow();
+ mGeckoChild->DispatchEvent(&focusGuiEvent, status);
+}
+
+// We accept key and mouse events, so don't keep passing them up the chain. Allow
+// this to be a 'focused' widget for event dispatch.
+- (BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+// Accept mouse down events on background windows
+- (BOOL)acceptsFirstMouse:(NSEvent*)aEvent
+{
+ if (![[self window] isKindOfClass:[PopupWindow class]]) {
+ // We rely on this function to tell us that the mousedown was on a
+ // background window. Inside mouseDown we can't tell whether we were
+ // inactive because at that point we've already been made active.
+ // Unfortunately, acceptsFirstMouse is called for PopupWindows even when
+ // their parent window is active, so ignore this on them for now.
+ mClickThroughMouseDownEvent = [aEvent retain];
+ }
+ return YES;
+}
+
+- (BOOL)mouseDownCanMoveWindow
+{
+ // Return YES so that parts of this view can be draggable. The non-draggable
+ // parts will be covered by NSViews that return NO from
+ // mouseDownCanMoveWindow and thus override draggability from the inside.
+ // These views are assembled in nsChildView::UpdateWindowDraggingRegion.
+ return YES;
+}
+
+-(void)updateGLContext
+{
+ [mGLContext setView:mPixelHostingView];
+ [mGLContext update];
+}
+
+- (void)_surfaceNeedsUpdate:(NSNotification*)notification
+{
+ if (mGLContext) {
+ CGLLockContext((CGLContextObj)[mGLContext CGLContextObj]);
+ mNeedsGLUpdate = YES;
+ CGLUnlockContext((CGLContextObj)[mGLContext CGLContextObj]);
+ }
+}
+
+- (void)viewDidChangeBackingProperties
+{
+ [super viewDidChangeBackingProperties];
+ if (mGeckoChild) {
+ // actually, it could be the color space that's changed,
+ // but we can't tell the difference here except by retrieving
+ // the backing scale factor and comparing to the old value
+ mGeckoChild->BackingScaleFactorChanged();
+ }
+}
+
+- (BOOL)isCoveringTitlebar
+{
+ return [[self window] isKindOfClass:[BaseWindow class]] &&
+ [(BaseWindow*)[self window] mainChildView] == self &&
+ [(BaseWindow*)[self window] drawsContentsIntoWindowFrame];
+}
+
+- (void)viewWillStartLiveResize
+{
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+
+ if (!observerService) {
+ return;
+ }
+
+ observerService->NotifyObservers(nullptr, "live-resize-start", nullptr);
+}
+
+- (void)viewDidEndLiveResize
+{
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+
+ if (!observerService) {
+ return;
+ }
+
+ observerService->NotifyObservers(nullptr, "live-resize-end", nullptr);
+}
+
+- (NSColor*)vibrancyFillColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType
+{
+ if (!mGeckoChild) {
+ return [NSColor whiteColor];
+ }
+ return mGeckoChild->VibrancyFillColorForThemeGeometryType(aThemeGeometryType);
+}
+
+- (NSColor*)vibrancyFontSmoothingBackgroundColorForThemeGeometryType:(nsITheme::ThemeGeometryType)aThemeGeometryType
+{
+ if (!mGeckoChild) {
+ return [NSColor clearColor];
+ }
+ return mGeckoChild->VibrancyFontSmoothingBackgroundColorForThemeGeometryType(aThemeGeometryType);
+}
+
+- (LayoutDeviceIntRegion)nativeDirtyRegionWithBoundingRect:(NSRect)aRect
+{
+ LayoutDeviceIntRect boundingRect = mGeckoChild->CocoaPointsToDevPixels(aRect);
+ const NSRect *rects;
+ NSInteger count;
+ [mPixelHostingView getRectsBeingDrawn:&rects count:&count];
+
+ if (count > MAX_RECTS_IN_REGION) {
+ return boundingRect;
+ }
+
+ LayoutDeviceIntRegion region;
+ for (NSInteger i = 0; i < count; ++i) {
+ region.Or(region, mGeckoChild->CocoaPointsToDevPixels(rects[i]));
+ }
+ region.And(region, boundingRect);
+ return region;
+}
+
+// The display system has told us that a portion of our view is dirty. Tell
+// gecko to paint it
+// This method is called from mPixelHostingView's drawRect handler.
+- (void)doDrawRect:(NSRect)aRect
+{
+ if (!NS_IsMainThread()) {
+ // In the presence of CoreAnimation, this method can sometimes be called on
+ // a non-main thread. Ignore those calls because Gecko can only react to
+ // them on the main thread.
+ return;
+ }
+
+ if (!mGeckoChild || !mGeckoChild->IsVisible())
+ return;
+ CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+
+ if ([self isUsingOpenGL]) {
+ // Since this view is usually declared as opaque, the window's pixel
+ // buffer may now contain garbage which we need to prevent from reaching
+ // the screen. The only place where garbage can show is in the window
+ // corners and the vibrant regions of the window - the rest of the window
+ // is covered by opaque content in our OpenGL surface.
+ // So we need to clear the pixel buffer contents in these areas.
+ [self clearCorners];
+
+ // Force a sync OMTC composite into the OpenGL context and return.
+ LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
+ LayoutDeviceIntRegion region(geckoBounds);
+
+ mGeckoChild->PaintWindow(region);
+ return;
+ }
+
+ PROFILER_LABEL("ChildView", "drawRect",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ // The CGContext that drawRect supplies us with comes with a transform that
+ // scales one user space unit to one Cocoa point, which can consist of
+ // multiple dev pixels. But Gecko expects its supplied context to be scaled
+ // to device pixels, so we need to reverse the scaling.
+ double scale = mGeckoChild->BackingScaleFactor();
+ CGContextSaveGState(cgContext);
+ CGContextScaleCTM(cgContext, 1.0 / scale, 1.0 / scale);
+
+ NSSize viewSize = [self bounds].size;
+ gfx::IntSize backingSize = gfx::IntSize::Truncate(viewSize.width * scale, viewSize.height * scale);
+ LayoutDeviceIntRegion region = [self nativeDirtyRegionWithBoundingRect:aRect];
+
+ bool painted = mGeckoChild->PaintWindowInContext(cgContext, region, backingSize);
+
+ // Undo the scale transform so that from now on the context is in
+ // CocoaPoints again.
+ CGContextRestoreGState(cgContext);
+
+ if (!painted && [mPixelHostingView isOpaque]) {
+ // Gecko refused to draw, but we've claimed to be opaque, so we have to
+ // draw something--fill with white.
+ CGContextSetRGBFillColor(cgContext, 1, 1, 1, 1);
+ CGContextFillRect(cgContext, NSRectToCGRect(aRect));
+ }
+
+ if ([self isCoveringTitlebar]) {
+ [self drawTitleString];
+ [self drawTitlebarHighlight];
+ [self maskTopCornersInContext:cgContext];
+ }
+}
+
+- (BOOL)isUsingOpenGL
+{
+ if (!mGeckoChild || ![self window])
+ return NO;
+
+ return mGLContext || mUsingOMTCompositor;
+}
+
+
+- (BOOL)hasRoundedBottomCorners
+{
+ return [[self window] respondsToSelector:@selector(bottomCornerRounded)] &&
+ [[self window] bottomCornerRounded];
+}
+
+- (CGFloat)cornerRadius
+{
+ NSView* frameView = [[[self window] contentView] superview];
+ if (!frameView || ![frameView respondsToSelector:@selector(roundedCornerRadius)])
+ return 4.0f;
+ return [frameView roundedCornerRadius];
+}
+
+-(void)setGLOpaque:(BOOL)aOpaque
+{
+ CGLLockContext((CGLContextObj)[mGLContext CGLContextObj]);
+ // Make the context opaque for fullscreen (since it performs better), and transparent
+ // for windowed (since we need it for rounded corners).
+ GLint opaque = aOpaque ? 1 : 0;
+ [mGLContext setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
+ CGLUnlockContext((CGLContextObj)[mGLContext CGLContextObj]);
+}
+
+// Accelerated windows have two NSSurfaces:
+// (1) The window's pixel buffer in the back and
+// (2) the OpenGL view in the front.
+// These two surfaces are composited by the window manager. Drawing into the
+// CGContext which is provided by drawRect ends up in (1).
+// When our window has rounded corners, the OpenGL view has transparent pixels
+// in the corners. In these places the contents of the window's pixel buffer
+// can show through. So we need to make sure that the pixel buffer is
+// transparent in the corners so that no garbage reaches the screen.
+// The contents of the pixel buffer in the rest of the window don't matter
+// because they're covered by opaque pixels of the OpenGL context.
+// Making the corners transparent works even though our window is
+// declared "opaque" (in the NSWindow's isOpaque method).
+- (void)clearCorners
+{
+ CGFloat radius = [self cornerRadius];
+ CGFloat w = [self bounds].size.width, h = [self bounds].size.height;
+ [[NSColor clearColor] set];
+
+ if ([self isCoveringTitlebar]) {
+ NSRectFill(NSMakeRect(0, 0, radius, radius));
+ NSRectFill(NSMakeRect(w - radius, 0, radius, radius));
+ }
+
+ if ([self hasRoundedBottomCorners]) {
+ NSRectFill(NSMakeRect(0, h - radius, radius, radius));
+ NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius));
+ }
+}
+
+// This is the analog of nsChildView::MaybeDrawRoundedCorners for CGContexts.
+// We only need to mask the top corners here because Cocoa does the masking
+// for the window's bottom corners automatically (starting with 10.7).
+- (void)maskTopCornersInContext:(CGContextRef)aContext
+{
+ CGFloat radius = [self cornerRadius];
+ int32_t devPixelCornerRadius = mGeckoChild->CocoaPointsToDevPixels(radius);
+
+ // First make sure that mTopLeftCornerMask is set up.
+ if (!mTopLeftCornerMask ||
+ int32_t(CGImageGetWidth(mTopLeftCornerMask)) != devPixelCornerRadius) {
+ CGImageRelease(mTopLeftCornerMask);
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGContextRef imgCtx = CGBitmapContextCreate(NULL,
+ devPixelCornerRadius,
+ devPixelCornerRadius,
+ 8, devPixelCornerRadius * 4,
+ rgb, kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(rgb);
+ DrawTopLeftCornerMask(imgCtx, devPixelCornerRadius);
+ mTopLeftCornerMask = CGBitmapContextCreateImage(imgCtx);
+ CGContextRelease(imgCtx);
+ }
+
+ // kCGBlendModeDestinationIn is the secret sauce which allows us to erase
+ // already painted pixels. It's defined as R = D * Sa: multiply all channels
+ // of the destination pixel with the alpha of the source pixel. In our case,
+ // the source is mTopLeftCornerMask.
+ CGContextSaveGState(aContext);
+ CGContextSetBlendMode(aContext, kCGBlendModeDestinationIn);
+
+ CGRect destRect = CGRectMake(0, 0, radius, radius);
+
+ // Erase the top left corner...
+ CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
+
+ // ... and the top right corner.
+ CGContextTranslateCTM(aContext, [self bounds].size.width, 0);
+ CGContextScaleCTM(aContext, -1, 1);
+ CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
+
+ CGContextRestoreGState(aContext);
+}
+
+- (void)drawTitleString
+{
+ BaseWindow* window = (BaseWindow*)[self window];
+ if (![window wantsTitleDrawn]) {
+ return;
+ }
+
+ NSView* frameView = [[window contentView] superview];
+ if (![frameView respondsToSelector:@selector(_drawTitleBar:)]) {
+ return;
+ }
+
+ NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
+ CGContextRef ctx = (CGContextRef)[oldContext graphicsPort];
+ CGContextSaveGState(ctx);
+ if ([oldContext isFlipped] != [frameView isFlipped]) {
+ CGContextTranslateCTM(ctx, 0, [self bounds].size.height);
+ CGContextScaleCTM(ctx, 1, -1);
+ }
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:[frameView isFlipped]]];
+ [frameView _drawTitleBar:[frameView bounds]];
+ CGContextRestoreGState(ctx);
+ [NSGraphicsContext setCurrentContext:oldContext];
+}
+
+- (void)drawTitlebarHighlight
+{
+ DrawTitlebarHighlight([self bounds].size, [self cornerRadius],
+ mGeckoChild->DevPixelsToCocoaPoints(1));
+}
+
+- (void)viewWillDraw
+{
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if (mGeckoChild) {
+ // The OS normally *will* draw our NSWindow, no matter what we do here.
+ // But Gecko can delete our parent widget(s) (along with mGeckoChild)
+ // while processing a paint request, which closes our NSWindow and
+ // makes the OS throw an NSInternalInconsistencyException assertion when
+ // it tries to draw it. Sometimes the OS also aborts the browser process.
+ // So we need to retain our parent(s) here and not release it/them until
+ // the next time through the main thread's run loop. When we do this we
+ // also need to retain and release mGeckoChild, which holds a strong
+ // reference to us. See bug 550392.
+ nsIWidget* parent = mGeckoChild->GetParent();
+ if (parent) {
+ nsTArray<nsCOMPtr<nsIWidget>> widgetArray;
+ while (parent) {
+ widgetArray.AppendElement(parent);
+ parent = parent->GetParent();
+ }
+ widgetArray.AppendElement(mGeckoChild);
+ nsCOMPtr<nsIRunnable> releaserRunnable =
+ new WidgetsReleaserRunnable(Move(widgetArray));
+ NS_DispatchToMainThread(releaserRunnable);
+ }
+
+ if ([self isUsingOpenGL]) {
+ if (ShadowLayerForwarder* slf = mGeckoChild->GetLayerManager()->AsShadowForwarder()) {
+ slf->WindowOverlayChanged();
+ }
+ }
+
+ mGeckoChild->WillPaintWindow();
+ }
+ [super viewWillDraw];
+}
+
+#if USE_CLICK_HOLD_CONTEXTMENU
+//
+// -clickHoldCallback:
+//
+// called from a timer two seconds after a mouse down to see if we should display
+// a context menu (click-hold). |anEvent| is the original mouseDown event. If we're
+// still in that mouseDown by this time, put up the context menu, otherwise just
+// fuhgeddaboutit. |anEvent| has been retained by the OS until after this callback
+// fires so we're ok there.
+//
+// This code currently messes in a bunch of edge cases (bugs 234751, 232964, 232314)
+// so removing it until we get it straightened out.
+//
+- (void)clickHoldCallback:(id)theEvent;
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if( theEvent == [NSApp currentEvent] ) {
+ // we're still in the middle of the same mousedown event here, activate
+ // click-hold context menu by triggering the right mouseDown action.
+ NSEvent* clickHoldEvent = [NSEvent mouseEventWithType:NSRightMouseDown
+ location:[theEvent locationInWindow]
+ modifierFlags:[theEvent modifierFlags]
+ timestamp:[theEvent timestamp]
+ windowNumber:[theEvent windowNumber]
+ context:[theEvent context]
+ eventNumber:[theEvent eventNumber]
+ clickCount:[theEvent clickCount]
+ pressure:[theEvent pressure]];
+ [self rightMouseDown:clickHoldEvent];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+#endif
+
+// If we've just created a non-native context menu, we need to mark it as
+// such and let the OS (and other programs) know when it opens and closes
+// (this is how the OS knows to close other programs' context menus when
+// ours open). We send the initial notification here, but others are sent
+// in nsCocoaWindow::Show().
+- (void)maybeInitContextMenuTracking
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE_VOID(rollupListener);
+ nsCOMPtr<nsIWidget> widget = rollupListener->GetRollupWidget();
+ NS_ENSURE_TRUE_VOID(widget);
+
+ NSWindow *popupWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!popupWindow || ![popupWindow isKindOfClass:[PopupWindow class]])
+ return;
+
+ [[NSDistributedNotificationCenter defaultCenter]
+ postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:@"org.mozilla.gecko.PopupWindow"];
+ [(PopupWindow*)popupWindow setIsContextMenu:YES];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Returns true if the event should no longer be processed, false otherwise.
+// This does not return whether or not anything was rolled up.
+- (BOOL)maybeRollup:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ BOOL consumeEvent = NO;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, false);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) {
+ NSWindow* currentPopup = static_cast<NSWindow*>(rollupWidget->GetNativeData(NS_NATIVE_WINDOW));
+ if (!nsCocoaUtils::IsEventOverWindow(theEvent, currentPopup)) {
+ // event is not over the rollup window, default is to roll up
+ bool shouldRollup = true;
+
+ // check to see if scroll events should roll up the popup
+ if ([theEvent type] == NSScrollWheel) {
+ shouldRollup = rollupListener->ShouldRollupOnMouseWheelEvent();
+ // consume scroll events that aren't over the popup
+ // unless the popup is an arrow panel
+ consumeEvent = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ }
+
+ // if we're dealing with menus, we probably have submenus and
+ // we don't want to rollup if the click is in a parent menu of
+ // the current submenu
+ uint32_t popupsToRollup = UINT32_MAX;
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount = rollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (uint32_t i = 0; i < widgetChain.Length(); i++) {
+ nsIWidget* widget = widgetChain[i];
+ NSWindow* currWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (nsCocoaUtils::IsEventOverWindow(theEvent, currWindow)) {
+ // don't roll up if the mouse event occurred within a menu of the
+ // same type. If the mouse event occurred in a menu higher than
+ // that, roll up, but pass the number of popups to Rollup so
+ // that only those of the same type close up.
+ if (i < sameTypeCount) {
+ shouldRollup = false;
+ }
+ else {
+ popupsToRollup = sameTypeCount;
+ }
+ break;
+ }
+ }
+
+ if (shouldRollup) {
+ if ([theEvent type] == NSLeftMouseDown) {
+ NSPoint point = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(point);
+ gfx::IntPoint pos = gfx::IntPoint::Truncate(point.x, point.y);
+ consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, &pos, nullptr);
+ }
+ else {
+ consumeEvent = (BOOL)rollupListener->Rollup(popupsToRollup, true, nullptr, nullptr);
+ }
+ }
+ }
+ }
+
+ return consumeEvent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+/*
+ * In OS X Mountain Lion and above, smart zoom gestures are implemented in
+ * smartMagnifyWithEvent. In OS X Lion, they are implemented in
+ * magnifyWithEvent. See inline comments for more info.
+ *
+ * The prototypes swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
+ * smartMagnifyWithEvent, rotateWithEvent, and endGestureWithEvent were
+ * obtained from the following links:
+ * https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html
+ * https://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKit.html
+ */
+
+- (void)swipeWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ float deltaX = [anEvent deltaX]; // left=1.0, right=-1.0
+ float deltaY = [anEvent deltaY]; // up=1.0, down=-1.0
+
+ // Setup the "swipe" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eSwipeGesture, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+
+ // Record the left/right direction.
+ if (deltaX > 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_LEFT;
+ else if (deltaX < 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_RIGHT;
+
+ // Record the up/down direction.
+ if (deltaY > 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_UP;
+ else if (deltaY < 0.0)
+ geckoEvent.mDirection |= nsIDOMSimpleGestureEvent::DIRECTION_DOWN;
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)beginGestureWithEvent:(NSEvent *)anEvent
+{
+ if (!anEvent)
+ return;
+
+ mGestureState = eGestureState_StartGesture;
+ mCumulativeMagnification = 0;
+ mCumulativeRotation = 0.0;
+}
+
+- (void)magnifyWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ float deltaZ = [anEvent deltaZ];
+
+ EventMessage msg;
+ switch (mGestureState) {
+ case eGestureState_StartGesture:
+ msg = eMagnifyGestureStart;
+ mGestureState = eGestureState_MagnifyGesture;
+ break;
+
+ case eGestureState_MagnifyGesture:
+ msg = eMagnifyGestureUpdate;
+ break;
+
+ case eGestureState_None:
+ case eGestureState_RotateGesture:
+ default:
+ return;
+ }
+
+ // Setup the event.
+ WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild);
+ geckoEvent.mDelta = deltaZ;
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ // Keep track of the cumulative magnification for the final "magnify" event.
+ mCumulativeMagnification += deltaZ;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)smartMagnifyWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild) {
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ // Setup the "double tap" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eTapGesture, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.mClickCount = 1;
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ // Clear the gesture state
+ mGestureState = eGestureState_None;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rotateWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ float rotation = [anEvent rotation];
+
+ EventMessage msg;
+ switch (mGestureState) {
+ case eGestureState_StartGesture:
+ msg = eRotateGestureStart;
+ mGestureState = eGestureState_RotateGesture;
+ break;
+
+ case eGestureState_RotateGesture:
+ msg = eRotateGestureUpdate;
+ break;
+
+ case eGestureState_None:
+ case eGestureState_MagnifyGesture:
+ default:
+ return;
+ }
+
+ // Setup the event.
+ WidgetSimpleGestureEvent geckoEvent(true, msg, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.mDelta = -rotation;
+ if (rotation > 0.0) {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
+ } else {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
+ }
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+
+ // Keep track of the cumulative rotation for the final "rotate" event.
+ mCumulativeRotation += rotation;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)endGestureWithEvent:(NSEvent *)anEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!anEvent || !mGeckoChild) {
+ // Clear the gestures state if we cannot send an event.
+ mGestureState = eGestureState_None;
+ mCumulativeMagnification = 0.0;
+ mCumulativeRotation = 0.0;
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ switch (mGestureState) {
+ case eGestureState_MagnifyGesture:
+ {
+ // Setup the "magnify" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eMagnifyGesture, mGeckoChild);
+ geckoEvent.mDelta = mCumulativeMagnification;
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+ }
+ break;
+
+ case eGestureState_RotateGesture:
+ {
+ // Setup the "rotate" event.
+ WidgetSimpleGestureEvent geckoEvent(true, eRotateGesture, mGeckoChild);
+ [self convertCocoaMouseEvent:anEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.mDelta = -mCumulativeRotation;
+ if (mCumulativeRotation > 0.0) {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_COUNTERCLOCKWISE;
+ } else {
+ geckoEvent.mDirection = nsIDOMSimpleGestureEvent::ROTATION_CLOCKWISE;
+ }
+
+ // Send the event.
+ mGeckoChild->DispatchWindowEvent(geckoEvent);
+ }
+ break;
+
+ case eGestureState_None:
+ case eGestureState_StartGesture:
+ default:
+ break;
+ }
+
+ // Clear the gestures state.
+ mGestureState = eGestureState_None;
+ mCumulativeMagnification = 0.0;
+ mCumulativeRotation = 0.0;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (bool)shouldConsiderStartingSwipeFromEvent:(NSEvent*)anEvent
+{
+ // This method checks whether the AppleEnableSwipeNavigateWithScrolls global
+ // preference is set. If it isn't, fluid swipe tracking is disabled, and a
+ // horizontal two-finger gesture is always a scroll (even in Safari). This
+ // preference can't (currently) be set from the Preferences UI -- only using
+ // 'defaults write'.
+ if (![NSEvent isSwipeTrackingFromScrollEventsEnabled]) {
+ return false;
+ }
+
+ // Only initiate horizontal tracking for gestures that have just begun --
+ // otherwise a scroll to one side of the page can have a swipe tacked on
+ // to it.
+ NSEventPhase eventPhase = nsCocoaUtils::EventPhase(anEvent);
+ if ([anEvent type] != NSScrollWheel ||
+ eventPhase != NSEventPhaseBegan ||
+ ![anEvent hasPreciseScrollingDeltas]) {
+ return false;
+ }
+
+ // Only initiate horizontal tracking for events whose horizontal element is
+ // at least eight times larger than its vertical element. This minimizes
+ // performance problems with vertical scrolls (by minimizing the possibility
+ // that they'll be misinterpreted as horizontal swipes), while still
+ // tolerating a small vertical element to a true horizontal swipe. The number
+ // '8' was arrived at by trial and error.
+ CGFloat deltaX = [anEvent scrollingDeltaX];
+ CGFloat deltaY = [anEvent scrollingDeltaY];
+ return std::abs(deltaX) > std::abs(deltaY) * 8;
+}
+
+- (void)setUsingOMTCompositor:(BOOL)aUseOMTC
+{
+ mUsingOMTCompositor = aUseOMTC;
+}
+
+// Returning NO from this method only disallows ordering on mousedown - in order
+// to prevent it for mouseup too, we need to call [NSApp preventWindowOrdering]
+// when handling the mousedown event.
+- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent*)aEvent
+{
+ // Always using system-provided window ordering for normal windows.
+ if (![[self window] isKindOfClass:[PopupWindow class]])
+ return NO;
+
+ // Don't reorder when we don't have a parent window, like when we're a
+ // context menu or a tooltip.
+ return ![[self window] parentWindow];
+}
+
+- (void)mouseDown:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([self shouldDelayWindowOrderingForEvent:theEvent]) {
+ [NSApp preventWindowOrdering];
+ }
+
+ // If we've already seen this event due to direct dispatch from menuForEvent:
+ // just bail; if not, remember it.
+ if (mLastMouseDownEvent == theEvent) {
+ [mLastMouseDownEvent release];
+ mLastMouseDownEvent = nil;
+ return;
+ }
+ else {
+ [mLastMouseDownEvent release];
+ mLastMouseDownEvent = [theEvent retain];
+ }
+
+ [gLastDragMouseDownEvent release];
+ gLastDragMouseDownEvent = [theEvent retain];
+
+ // We need isClickThrough because at this point the window we're in might
+ // already have become main, so the check for isMainWindow in
+ // WindowAcceptsEvent isn't enough. It also has to check isClickThrough.
+ BOOL isClickThrough = (theEvent == mClickThroughMouseDownEvent);
+ [mClickThroughMouseDownEvent release];
+ mClickThroughMouseDownEvent = nil;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if ([self maybeRollup:theEvent] ||
+ !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self, isClickThrough)) {
+ // Remember blocking because that means we want to block mouseup as well.
+ mBlockedLastMouseDown = YES;
+ return;
+ }
+
+#if USE_CLICK_HOLD_CONTEXTMENU
+ // fire off timer to check for click-hold after two seconds. retains |theEvent|
+ [self performSelector:@selector(clickHoldCallback:) withObject:theEvent afterDelay:2.0];
+#endif
+
+ // in order to send gecko events we'll need a gecko widget
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ NSUInteger modifierFlags = [theEvent modifierFlags];
+
+ WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+
+ NSInteger clickCount = [theEvent clickCount];
+ if (mBlockedLastMouseDown && clickCount > 1) {
+ // Don't send a double click if the first click of the double click was
+ // blocked.
+ clickCount--;
+ }
+ geckoEvent.mClickCount = clickCount;
+
+ if (modifierFlags & NSControlKeyMask)
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ else
+ geckoEvent.button = WidgetMouseEvent::eLeftButton;
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ mBlockedLastMouseDown = NO;
+
+ // XXX maybe call markedTextSelectionChanged:client: here?
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)mouseUp:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild || mBlockedLastMouseDown)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ if ([theEvent modifierFlags] & NSControlKeyMask)
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ else
+ geckoEvent.button = WidgetMouseEvent::eLeftButton;
+
+ // This might destroy our widget (and null out mGeckoChild).
+ bool defaultPrevented =
+ (mGeckoChild->DispatchInputEvent(&geckoEvent) == nsEventStatus_eConsumeNoDefault);
+
+ // Check to see if we are double-clicking in the titlebar.
+ CGFloat locationInTitlebar = [[self window] frame].size.height - [theEvent locationInWindow].y;
+ LayoutDeviceIntPoint pos = geckoEvent.mRefPoint;
+ if (!defaultPrevented && [theEvent clickCount] == 2 &&
+ !mGeckoChild->GetNonDraggableRegion().Contains(pos.x, pos.y) &&
+ [[self window] isKindOfClass:[ToolbarWindow class]] &&
+ (locationInTitlebar < [(ToolbarWindow*)[self window] titlebarHeight] ||
+ locationInTitlebar < [(ToolbarWindow*)[self window] unifiedToolbarHeight])) {
+ if ([self shouldZoomOnDoubleClick]) {
+ [[self window] performZoom:nil];
+ } else if ([self shouldMinimizeOnTitlebarDoubleClick]) {
+ NSButton *minimizeButton = [[self window] standardWindowButton:NSWindowMiniaturizeButton];
+ [minimizeButton performClick:self];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
+ enter:(BOOL)aEnter
+ exitFrom:(WidgetMouseEvent::ExitFrom)aExitFrom
+{
+ if (!mGeckoChild)
+ return;
+
+ NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]);
+ NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil];
+
+ EventMessage msg = aEnter ? eMouseEnterIntoWidget : eMouseExitFromWidget;
+ WidgetMouseEvent event(true, msg, mGeckoChild, WidgetMouseEvent::eReal);
+ event.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(localEventLocation);
+
+ event.mExitFrom = aExitFrom;
+
+ nsEventStatus status; // ignored
+ mGeckoChild->DispatchEvent(&event, status);
+}
+
+- (void)handleMouseMoved:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)mouseDragged:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ gLastDragView = self;
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ // Note, sending the above event might have destroyed our widget since we didn't retain.
+ // Fine so long as we don't access any local variables from here on.
+ gLastDragView = nil;
+
+ // XXX maybe call markedTextSelectionChanged:client: here?
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rightMouseDown:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ [self maybeRollup:theEvent];
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ // The right mouse went down, fire off a right mouse down event to gecko
+ WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild)
+ return;
+
+ // Let the superclass do the context menu stuff.
+ [super rightMouseDown:theEvent];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rightMouseUp:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)rightMouseDragged:(NSEvent*)theEvent
+{
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+
+ // send event into Gecko by going directly to the
+ // the widget.
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+}
+
+- (void)otherMouseDown:(NSEvent *)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if ([self maybeRollup:theEvent] ||
+ !ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent, self))
+ return;
+
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseDown, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eMiddleButton;
+ geckoEvent.mClickCount = [theEvent clickCount];
+
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)otherMouseUp:(NSEvent *)theEvent
+{
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseUp, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eMiddleButton;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+}
+
+- (void)otherMouseDragged:(NSEvent*)theEvent
+{
+ if (!mGeckoChild)
+ return;
+ if (mTextInputHandler->OnHandleEvent(theEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eMouseMove, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eMiddleButton;
+
+ // send event into Gecko by going directly to the
+ // the widget.
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+}
+
+- (void)sendWheelStartOrStop:(EventMessage)msg forEvent:(NSEvent *)theEvent
+{
+ WidgetWheelEvent wheelEvent(true, msg, mGeckoChild);
+ [self convertCocoaMouseWheelEvent:theEvent toGeckoEvent:&wheelEvent];
+ mExpectingWheelStop = (msg == eWheelOperationStart);
+ mGeckoChild->DispatchInputEvent(wheelEvent.AsInputEvent());
+}
+
+- (void)sendWheelCondition:(BOOL)condition
+ first:(EventMessage)first
+ second:(EventMessage)second
+ forEvent:(NSEvent *)theEvent
+{
+ if (mExpectingWheelStop == condition) {
+ [self sendWheelStartOrStop:first forEvent:theEvent];
+ }
+ [self sendWheelStartOrStop:second forEvent:theEvent];
+}
+
+static PanGestureInput::PanGestureType
+PanGestureTypeForEvent(NSEvent* aEvent)
+{
+ switch (nsCocoaUtils::EventPhase(aEvent)) {
+ case NSEventPhaseMayBegin:
+ return PanGestureInput::PANGESTURE_MAYSTART;
+ case NSEventPhaseCancelled:
+ return PanGestureInput::PANGESTURE_CANCELLED;
+ case NSEventPhaseBegan:
+ return PanGestureInput::PANGESTURE_START;
+ case NSEventPhaseChanged:
+ return PanGestureInput::PANGESTURE_PAN;
+ case NSEventPhaseEnded:
+ return PanGestureInput::PANGESTURE_END;
+ case NSEventPhaseNone:
+ switch (nsCocoaUtils::EventMomentumPhase(aEvent)) {
+ case NSEventPhaseBegan:
+ return PanGestureInput::PANGESTURE_MOMENTUMSTART;
+ case NSEventPhaseChanged:
+ return PanGestureInput::PANGESTURE_MOMENTUMPAN;
+ case NSEventPhaseEnded:
+ return PanGestureInput::PANGESTURE_MOMENTUMEND;
+ default:
+ NS_ERROR("unexpected event phase");
+ return PanGestureInput::PANGESTURE_PAN;
+ }
+ default:
+ NS_ERROR("unexpected event phase");
+ return PanGestureInput::PANGESTURE_PAN;
+ }
+}
+
+static int32_t RoundUp(double aDouble)
+{
+ return aDouble < 0 ? static_cast<int32_t>(floor(aDouble)) :
+ static_cast<int32_t>(ceil(aDouble));
+}
+
+static int32_t
+TakeLargestInt(gfx::Float* aFloat)
+{
+ int32_t result(*aFloat); // truncate towards zero
+ *aFloat -= result;
+ return result;
+}
+
+static gfx::IntPoint
+AccumulateIntegerDelta(NSEvent* aEvent)
+{
+ static gfx::Point sAccumulator(0.0f, 0.0f);
+ if (nsCocoaUtils::EventPhase(aEvent) == NSEventPhaseBegan) {
+ sAccumulator = gfx::Point(0.0f, 0.0f);
+ }
+ sAccumulator.x += [aEvent deltaX];
+ sAccumulator.y += [aEvent deltaY];
+ return gfx::IntPoint(TakeLargestInt(&sAccumulator.x),
+ TakeLargestInt(&sAccumulator.y));
+}
+
+static gfx::IntPoint
+GetIntegerDeltaForEvent(NSEvent* aEvent)
+{
+ if (nsCocoaFeatures::OnSierraOrLater() && [aEvent hasPreciseScrollingDeltas]) {
+ // Pixel scroll events (events with hasPreciseScrollingDeltas == YES)
+ // carry pixel deltas in the scrollingDeltaX/Y fields and line scroll
+ // information in the deltaX/Y fields.
+ // Prior to 10.12, these line scroll fields would be zero for most pixel
+ // scroll events and non-zero for some, whenever at least a full line
+ // worth of pixel scrolling had accumulated. That's the behavior we want.
+ // Starting with 10.12 however, pixel scroll events no longer accumulate
+ // deltaX and deltaY; they just report floating point values for every
+ // single event. So we need to do our own accumulation.
+ return AccumulateIntegerDelta(aEvent);
+ }
+
+ // For line scrolls, or pre-10.12, just use the rounded up value of deltaX / deltaY.
+ return gfx::IntPoint(RoundUp([aEvent deltaX]), RoundUp([aEvent deltaY]));
+}
+
+- (void)scrollWheel:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ ChildViewMouseTracker::MouseScrolled(theEvent);
+
+ if ([self maybeRollup:theEvent]) {
+ return;
+ }
+
+ if (!mGeckoChild) {
+ return;
+ }
+
+ NSEventPhase phase = nsCocoaUtils::EventPhase(theEvent);
+ // Fire eWheelOperationStart/End events when 2 fingers touch/release the
+ // touchpad.
+ if (phase & NSEventPhaseMayBegin) {
+ [self sendWheelCondition:YES
+ first:eWheelOperationEnd
+ second:eWheelOperationStart
+ forEvent:theEvent];
+ } else if (phase & (NSEventPhaseEnded | NSEventPhaseCancelled)) {
+ [self sendWheelCondition:NO
+ first:eWheelOperationStart
+ second:eWheelOperationEnd
+ forEvent:theEvent];
+ }
+
+ if (!mGeckoChild) {
+ return;
+ }
+ RefPtr<nsChildView> geckoChildDeathGrip(mGeckoChild);
+
+ NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(theEvent, [self window]);
+
+ // Use convertWindowCoordinatesRoundDown when converting the position to
+ // integer screen pixels in order to ensure that coordinates which are just
+ // inside the right / bottom edges of the window don't end up outside of the
+ // window after rounding.
+ ScreenPoint position = ViewAs<ScreenPixel>(
+ [self convertWindowCoordinatesRoundDown:locationInWindow],
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ bool usePreciseDeltas = nsCocoaUtils::HasPreciseScrollingDeltas(theEvent) &&
+ Preferences::GetBool("mousewheel.enable_pixel_scrolling", true);
+ bool hasPhaseInformation = nsCocoaUtils::EventHasPhaseInformation(theEvent);
+
+ gfx::IntPoint lineOrPageDelta = -GetIntegerDeltaForEvent(theEvent);
+
+ Modifiers modifiers = nsCocoaUtils::ModifiersForEvent(theEvent);
+
+ NSTimeInterval beforeNow = [[NSProcessInfo processInfo] systemUptime] - [theEvent timestamp];
+ PRIntervalTime eventIntervalTime = PR_IntervalNow() - PR_MillisecondsToInterval(beforeNow * 1000);
+ TimeStamp eventTimeStamp = TimeStamp::Now() - TimeDuration::FromSeconds(beforeNow);
+
+ ScreenPoint preciseDelta;
+ if (usePreciseDeltas) {
+ CGFloat pixelDeltaX = 0, pixelDeltaY = 0;
+ nsCocoaUtils::GetScrollingDeltas(theEvent, &pixelDeltaX, &pixelDeltaY);
+ double scale = geckoChildDeathGrip->BackingScaleFactor();
+ preciseDelta = ScreenPoint(-pixelDeltaX * scale, -pixelDeltaY * scale);
+ }
+
+ if (usePreciseDeltas && hasPhaseInformation) {
+ PanGestureInput panEvent(PanGestureTypeForEvent(theEvent),
+ eventIntervalTime, eventTimeStamp,
+ position, preciseDelta, modifiers);
+ panEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
+ panEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
+
+ if (panEvent.mType == PanGestureInput::PANGESTURE_END) {
+ // Check if there's a momentum start event in the event queue, so that we
+ // can annotate this event.
+ NSEvent* nextWheelEvent =
+ [NSApp nextEventMatchingMask:NSScrollWheelMask
+ untilDate:[NSDate distantPast]
+ inMode:NSDefaultRunLoopMode
+ dequeue:NO];
+ if (nextWheelEvent &&
+ PanGestureTypeForEvent(nextWheelEvent) == PanGestureInput::PANGESTURE_MOMENTUMSTART) {
+ panEvent.mFollowedByMomentum = true;
+ }
+ }
+
+ bool canTriggerSwipe = [self shouldConsiderStartingSwipeFromEvent:theEvent];
+ panEvent.mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection = canTriggerSwipe;
+ geckoChildDeathGrip->DispatchAPZWheelInputEvent(panEvent, canTriggerSwipe);
+ } else if (usePreciseDeltas) {
+ // This is on 10.6 or old touchpads that don't have any phase information.
+ ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers,
+ ScrollWheelInput::SCROLLMODE_INSTANT,
+ ScrollWheelInput::SCROLLDELTA_PIXEL,
+ position,
+ preciseDelta.x,
+ preciseDelta.y,
+ false);
+ wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
+ wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
+ wheelEvent.mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(theEvent);
+ geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false);
+ } else {
+ ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT;
+ if (gfxPrefs::SmoothScrollEnabled() && gfxPrefs::WheelSmoothScrollEnabled()) {
+ scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
+ }
+ ScrollWheelInput wheelEvent(eventIntervalTime, eventTimeStamp, modifiers,
+ scrollMode,
+ ScrollWheelInput::SCROLLDELTA_LINE,
+ position,
+ lineOrPageDelta.x,
+ lineOrPageDelta.y,
+ false);
+ wheelEvent.mLineOrPageDeltaX = lineOrPageDelta.x;
+ wheelEvent.mLineOrPageDeltaY = lineOrPageDelta.y;
+ geckoChildDeathGrip->DispatchAPZWheelInputEvent(wheelEvent, false);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+-(NSMenu*)menuForEvent:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mGeckoChild)
+ return nil;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ [self maybeRollup:theEvent];
+ if (!mGeckoChild)
+ return nil;
+
+ // Cocoa doesn't always dispatch a mouseDown: for a control-click event,
+ // depends on what we return from menuForEvent:. Gecko always expects one
+ // and expects the mouse down event before the context menu event, so
+ // get that event sent first if this is a left mouse click.
+ if ([theEvent type] == NSLeftMouseDown) {
+ [self mouseDown:theEvent];
+ if (!mGeckoChild)
+ return nil;
+ }
+
+ WidgetMouseEvent geckoEvent(true, eContextMenu, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
+ geckoEvent.button = WidgetMouseEvent::eRightButton;
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild)
+ return nil;
+
+ [self maybeInitContextMenuTracking];
+
+ // Go up our view chain to fetch the correct menu to return.
+ return [self contextMenu];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSMenu*)contextMenu
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSView* superView = [self superview];
+ if ([superView respondsToSelector:@selector(contextMenu)])
+ return [(NSView<mozView>*)superView contextMenu];
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) convertCocoaMouseWheelEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetWheelEvent*)outWheelEvent
+{
+ [self convertCocoaMouseEvent:aMouseEvent toGeckoEvent:outWheelEvent];
+
+ bool usePreciseDeltas = nsCocoaUtils::HasPreciseScrollingDeltas(aMouseEvent) &&
+ Preferences::GetBool("mousewheel.enable_pixel_scrolling", true);
+
+ outWheelEvent->mDeltaMode =
+ usePreciseDeltas ? nsIDOMWheelEvent::DOM_DELTA_PIXEL
+ : nsIDOMWheelEvent::DOM_DELTA_LINE;
+ outWheelEvent->mIsMomentum = nsCocoaUtils::IsMomentumScrollEvent(aMouseEvent);
+}
+
+- (void) convertCocoaMouseEvent:(NSEvent*)aMouseEvent
+ toGeckoEvent:(WidgetInputEvent*)outGeckoEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ASSERTION(outGeckoEvent, "convertCocoaMouseEvent:toGeckoEvent: requires non-null aoutGeckoEvent");
+ if (!outGeckoEvent)
+ return;
+
+ nsCocoaUtils::InitInputEvent(*outGeckoEvent, aMouseEvent);
+
+ // convert point to view coordinate system
+ NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(aMouseEvent, [self window]);
+
+ outGeckoEvent->mRefPoint = [self convertWindowCoordinates:locationInWindow];
+
+ WidgetMouseEventBase* mouseEvent = outGeckoEvent->AsMouseEventBase();
+ mouseEvent->buttons = 0;
+ NSUInteger mouseButtons = [NSEvent pressedMouseButtons];
+
+ if (mouseButtons & 0x01) {
+ mouseEvent->buttons |= WidgetMouseEvent::eLeftButtonFlag;
+ }
+ if (mouseButtons & 0x02) {
+ mouseEvent->buttons |= WidgetMouseEvent::eRightButtonFlag;
+ }
+ if (mouseButtons & 0x04) {
+ mouseEvent->buttons |= WidgetMouseEvent::eMiddleButtonFlag;
+ }
+ if (mouseButtons & 0x08) {
+ mouseEvent->buttons |= WidgetMouseEvent::e4thButtonFlag;
+ }
+ if (mouseButtons & 0x10) {
+ mouseEvent->buttons |= WidgetMouseEvent::e5thButtonFlag;
+ }
+
+ switch ([aMouseEvent type]) {
+ case NSLeftMouseDown:
+ case NSLeftMouseUp:
+ case NSLeftMouseDragged:
+ case NSRightMouseDown:
+ case NSRightMouseUp:
+ case NSRightMouseDragged:
+ case NSOtherMouseDown:
+ case NSOtherMouseUp:
+ case NSOtherMouseDragged:
+ if ([aMouseEvent subtype] == NSTabletPointEventSubtype) {
+ mouseEvent->pressure = [aMouseEvent pressure];
+ MOZ_ASSERT(mouseEvent->pressure >= 0.0 && mouseEvent->pressure <= 1.0);
+ }
+ break;
+
+ default:
+ // Don't check other NSEvents for pressure.
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)shouldZoomOnDoubleClick
+{
+ if ([NSWindow respondsToSelector:@selector(_shouldZoomOnDoubleClick)]) {
+ return [NSWindow _shouldZoomOnDoubleClick];
+ }
+ return nsCocoaFeatures::OnYosemiteOrLater();
+}
+
+- (BOOL)shouldMinimizeOnTitlebarDoubleClick
+{
+ NSString *MDAppleMiniaturizeOnDoubleClickKey =
+ @"AppleMiniaturizeOnDoubleClick";
+ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+ bool shouldMinimize = [[userDefaults
+ objectForKey:MDAppleMiniaturizeOnDoubleClickKey] boolValue];
+
+ return shouldMinimize;
+}
+
+#pragma mark -
+// NSTextInputClient implementation
+
+- (NSRange)markedRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
+ return mTextInputHandler->MarkedRange();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
+}
+
+- (NSRange)selectedRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRange(NSNotFound, 0));
+ return mTextInputHandler->SelectedRange();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRange(0, 0));
+}
+
+- (BOOL)drawsVerticallyForCharacterAtIndex:(NSUInteger)charIndex
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NO);
+ if (charIndex == NSNotFound) {
+ return NO;
+ }
+ return mTextInputHandler->DrawsVerticallyForCharacterAtIndex(charIndex);
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
+{
+ NS_ENSURE_TRUE(mTextInputHandler, 0);
+ return mTextInputHandler->CharacterIndexForPoint(thePoint);
+}
+
+- (NSArray*)validAttributesForMarkedText
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NS_ENSURE_TRUE(mTextInputHandler, [NSArray array]);
+ return mTextInputHandler->GetValidAttributesForMarkedText();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE_VOID(mGeckoChild);
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ NSAttributedString* attrStr;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ attrStr = static_cast<NSAttributedString*>(aString);
+ } else {
+ attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+ }
+
+ mTextInputHandler->InsertText(attrStr, &replacementRange);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild || !mTextInputHandler) {
+ return;
+ }
+
+ const char* sel = reinterpret_cast<const char*>(aSelector);
+ if (!mTextInputHandler->DoCommandBySelector(sel)) {
+ [super doCommandBySelector:aSelector];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)unmarkText
+{
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+ mTextInputHandler->CommitIMEComposition();
+}
+
+- (BOOL) hasMarkedText
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NO);
+ return mTextInputHandler->HasMarkedText();
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange
+ replacementRange:(NSRange)replacementRange
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE_VOID(mTextInputHandler);
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ NSAttributedString* attrStr;
+ if ([aString isKindOfClass:[NSAttributedString class]]) {
+ attrStr = static_cast<NSAttributedString*>(aString);
+ } else {
+ attrStr = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+ }
+
+ mTextInputHandler->SetMarkedText(attrStr, selectedRange, &replacementRange);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)aRange
+ actualRange:(NSRangePointer)actualRange
+{
+ NS_ENSURE_TRUE(mTextInputHandler, nil);
+ return mTextInputHandler->GetAttributedSubstringFromRange(aRange,
+ actualRange);
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange
+ actualRange:(NSRangePointer)actualRange
+{
+ NS_ENSURE_TRUE(mTextInputHandler, NSMakeRect(0.0, 0.0, 0.0, 0.0));
+ return mTextInputHandler->FirstRectForCharacterRange(aRange, actualRange);
+}
+
+- (void)quickLookWithEvent:(NSEvent*)event
+{
+ // Show dictionary by current point
+ WidgetContentCommandEvent
+ contentCommandEvent(true, eContentCommandLookUpDictionary, mGeckoChild);
+ NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
+ contentCommandEvent.mRefPoint = mGeckoChild->CocoaPointsToDevPixels(point);
+ mGeckoChild->DispatchWindowEvent(contentCommandEvent);
+ // The widget might have been destroyed.
+}
+
+- (NSInteger)windowLevel
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NS_ENSURE_TRUE(mTextInputHandler, [[self window] level]);
+ return mTextInputHandler->GetWindowLevel();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSNormalWindowLevel);
+}
+
+#pragma mark -
+
+// This is a private API that Cocoa uses.
+// Cocoa will call this after the menu system returns "NO" for "performKeyEquivalent:".
+// We want all they key events we can get so just return YES. In particular, this fixes
+// ctrl-tab - we don't get a "keyDown:" call for that without this.
+- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event
+{
+ return YES;
+}
+
+- (NSEvent*)lastKeyDownEvent
+{
+ return mLastKeyDownEvent;
+}
+
+- (void)keyDown:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mLastKeyDownEvent release];
+ mLastKeyDownEvent = [theEvent retain];
+
+ // Weird things can happen on keyboard input if the key window isn't in the
+ // current space. For example see bug 1056251. To get around this, always
+ // make sure that, if our window is key, it's also made frontmost. Doing
+ // this automatically switches to whatever space our window is in. Safari
+ // does something similar. Our window should normally always be key --
+ // otherwise why is the OS sending us a key down event? But it's just
+ // possible we're in Gecko's hidden window, so we check first.
+ NSWindow *viewWindow = [self window];
+ if (viewWindow && [viewWindow isKeyWindow]) {
+ [viewWindow orderWindow:NSWindowAbove relativeTo:0];
+ }
+
+#if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+ if (!Preferences::GetBool("intl.allow-insecure-text-input", false) &&
+ mGeckoChild && mTextInputHandler && mTextInputHandler->IsFocused()) {
+ if (mGeckoChild->GetInputContext().IsPasswordEditor() &&
+ !TextInputHandler::IsSecureEventInputEnabled()) {
+ #define CRASH_MESSAGE "A password editor has focus, but not in secure input mode"
+ MOZ_CRASH(CRASH_MESSAGE);
+ #undef CRASH_MESSAGE
+ } else if (!mGeckoChild->GetInputContext().IsPasswordEditor() &&
+ TextInputHandler::IsSecureEventInputEnabled()) {
+ #define CRASH_MESSAGE "A non-password editor has focus, but in secure input mode"
+ MOZ_CRASH(CRASH_MESSAGE);
+ #undef CRASH_MESSAGE
+ }
+ }
+#endif // #if !defined(RELEASE_OR_BETA) || defined(DEBUG)
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ bool handled = false;
+ if (mGeckoChild && mTextInputHandler) {
+ handled = mTextInputHandler->HandleKeyDownEvent(theEvent);
+ }
+
+ // We always allow keyboard events to propagate to keyDown: but if they are not
+ // handled we give special Application menu items a chance to act.
+ if (!handled && sApplicationMenu) {
+ [sApplicationMenu performKeyEquivalent:theEvent];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)keyUp:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(mGeckoChild, );
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ mTextInputHandler->HandleKeyUpEvent(theEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)insertNewline:(id)sender
+{
+ if (mTextInputHandler) {
+ NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"\n"];
+ mTextInputHandler->InsertText(attrStr);
+ [attrStr release];
+ }
+}
+
+- (void)flagsChanged:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NS_ENSURE_TRUE(mGeckoChild, );
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mTextInputHandler->HandleFlagsChanged(theEvent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL) isFirstResponder
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSResponder* resp = [[self window] firstResponder];
+ return (resp == (NSResponder*)self);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (BOOL)isDragInProgress
+{
+ if (!mDragService)
+ return NO;
+
+ nsCOMPtr<nsIDragSession> dragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(dragSession));
+ return dragSession != nullptr;
+}
+
+- (BOOL)inactiveWindowAcceptsMouseEvent:(NSEvent*)aEvent
+{
+ // If we're being destroyed assume the default -- return YES.
+ if (!mGeckoChild)
+ return YES;
+
+ WidgetMouseEvent geckoEvent(true, eMouseActivate, mGeckoChild,
+ WidgetMouseEvent::eReal);
+ [self convertCocoaMouseEvent:aEvent toGeckoEvent:&geckoEvent];
+ return (mGeckoChild->DispatchInputEvent(&geckoEvent) != nsEventStatus_eConsumeNoDefault);
+}
+
+// We must always call through to our superclass, even when mGeckoChild is
+// nil -- otherwise the keyboard focus can end up in the wrong NSView.
+- (BOOL)becomeFirstResponder
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [super becomeFirstResponder];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(YES);
+}
+
+- (void)viewsWindowDidBecomeKey
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ // check to see if the window implements the mozWindow protocol. This
+ // allows embedders to avoid re-entrant calls to -makeKeyAndOrderFront,
+ // which can happen because these activate calls propagate out
+ // to the embedder via nsIEmbeddingSiteWindow::SetFocus().
+ BOOL isMozWindow = [[self window] respondsToSelector:@selector(setSuppressMakeKeyFront:)];
+ if (isMozWindow)
+ [[self window] setSuppressMakeKeyFront:YES];
+
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener)
+ listener->WindowActivated();
+
+ if (isMozWindow)
+ [[self window] setSuppressMakeKeyFront:NO];
+
+ if (mGeckoChild->GetInputContext().IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)viewsWindowDidResignKey
+{
+ if (!mGeckoChild)
+ return;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ nsIWidgetListener* listener = mGeckoChild->GetWidgetListener();
+ if (listener)
+ listener->WindowDeactivated();
+
+ TextInputHandler::EnsureSecureEventInputDisabled();
+}
+
+// If the call to removeFromSuperview isn't delayed from nsChildView::
+// TearDownView(), the NSView hierarchy might get changed during calls to
+// [ChildView drawRect:], which leads to "beyond bounds" exceptions in
+// NSCFArray. For more info see bmo bug 373122. Apple's docs claim that
+// removeFromSuperviewWithoutNeedingDisplay "can be safely invoked during
+// display" (whatever "display" means). But it's _not_ true that it can be
+// safely invoked during calls to [NSView drawRect:]. We use
+// removeFromSuperview here because there's no longer any danger of being
+// "invoked during display", and because doing do clears up bmo bug 384343.
+- (void)delayedTearDown
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self removeFromSuperview];
+ [self release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark -
+
+// drag'n'drop stuff
+#define kDragServiceContractID "@mozilla.org/widget/dragservice;1"
+
+- (NSDragOperation)dragOperationFromDragAction:(int32_t)aDragAction
+{
+ if (nsIDragService::DRAGDROP_ACTION_LINK & aDragAction)
+ return NSDragOperationLink;
+ if (nsIDragService::DRAGDROP_ACTION_COPY & aDragAction)
+ return NSDragOperationCopy;
+ if (nsIDragService::DRAGDROP_ACTION_MOVE & aDragAction)
+ return NSDragOperationGeneric;
+ return NSDragOperationNone;
+}
+
+- (LayoutDeviceIntPoint)convertWindowCoordinates:(NSPoint)aPoint
+{
+ if (!mGeckoChild) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
+ return mGeckoChild->CocoaPointsToDevPixels(localPoint);
+}
+
+- (LayoutDeviceIntPoint)convertWindowCoordinatesRoundDown:(NSPoint)aPoint
+{
+ if (!mGeckoChild) {
+ return LayoutDeviceIntPoint(0, 0);
+ }
+
+ NSPoint localPoint = [self convertPoint:aPoint fromView:nil];
+ return mGeckoChild->CocoaPointsToDevPixelsRoundDown(localPoint);
+}
+
+// This is a utility function used by NSView drag event methods
+// to send events. It contains all of the logic needed for Gecko
+// dragging to work. Returns the appropriate cocoa drag operation code.
+- (NSDragOperation)doDragAction:(EventMessage)aMessage sender:(id)aSender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mGeckoChild)
+ return NSDragOperationNone;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView doDragAction: entered\n"));
+
+ if (!mDragService) {
+ CallGetService(kDragServiceContractID, &mDragService);
+ NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
+ if (!mDragService)
+ return NSDragOperationNone;
+ }
+
+ if (aMessage == eDragEnter) {
+ mDragService->StartDragSession();
+ }
+
+ nsCOMPtr<nsIDragSession> dragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(dragSession));
+ if (dragSession) {
+ if (aMessage == eDragOver) {
+ // fire the drag event at the source. Just ignore whether it was
+ // cancelled or not as there isn't actually a means to stop the drag
+ mDragService->FireDragEventAtSource(eDrag);
+ dragSession->SetCanDrop(false);
+ } else if (aMessage == eDrop) {
+ // We make the assumption that the dragOver handlers have correctly set
+ // the |canDrop| property of the Drag Session.
+ bool canDrop = false;
+ if (!NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)) || !canDrop) {
+ [self doDragAction:eDragExit sender:aSender];
+
+ nsCOMPtr<nsIDOMNode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ if (!sourceNode) {
+ mDragService->EndDragSession(false);
+ }
+ return NSDragOperationNone;
+ }
+ }
+
+ unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags];
+ uint32_t action = nsIDragService::DRAGDROP_ACTION_MOVE;
+ // force copy = option, alias = cmd-option, default is move
+ if (modifierFlags & NSAlternateKeyMask) {
+ if (modifierFlags & NSCommandKeyMask)
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+ else
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+ }
+ dragSession->SetDragAction(action);
+ }
+
+ // set up gecko event
+ WidgetDragEvent geckoEvent(true, aMessage, mGeckoChild);
+ nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]);
+
+ // Use our own coordinates in the gecko event.
+ // Convert event from gecko global coords to gecko view coords.
+ NSPoint draggingLoc = [aSender draggingLocation];
+
+ geckoEvent.mRefPoint = [self convertWindowCoordinates:draggingLoc];
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ mGeckoChild->DispatchInputEvent(&geckoEvent);
+ if (!mGeckoChild)
+ return NSDragOperationNone;
+
+ if (dragSession) {
+ switch (aMessage) {
+ case eDragEnter:
+ case eDragOver: {
+ uint32_t dragAction;
+ dragSession->GetDragAction(&dragAction);
+
+ // If TakeChildProcessDragAction returns something other than
+ // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent
+ // to the child process and this event is also being sent to the child
+ // process. In this case, use the last event's action instead.
+ nsDragService* dragService = static_cast<nsDragService *>(mDragService);
+ int32_t childDragAction = dragService->TakeChildProcessDragAction();
+ if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
+ dragAction = childDragAction;
+ }
+
+ return [self dragOperationFromDragAction:dragAction];
+ }
+ case eDragExit:
+ case eDrop: {
+ nsCOMPtr<nsIDOMNode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ if (!sourceNode) {
+ // We're leaving a window while doing a drag that was
+ // initiated in a different app. End the drag session,
+ // since we're done with it for now (until the user
+ // drags back into mozilla).
+ mDragService->EndDragSession(false);
+ }
+ }
+ default:
+ break;
+ }
+ }
+
+ return NSDragOperationGeneric;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
+}
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingEntered: entered\n"));
+
+ // there should never be a globalDragPboard when "draggingEntered:" is
+ // called, but just in case we'll take care of it here.
+ [globalDragPboard release];
+
+ // Set the global drag pasteboard that will be used for this drag session.
+ // This will be set back to nil when the drag session ends (mouse exits
+ // the view or a drop happens within the view).
+ globalDragPboard = [[sender draggingPasteboard] retain];
+
+ return [self doDragAction:eDragEnter sender:sender];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSDragOperationNone);
+}
+
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingUpdated: entered\n"));
+
+ return [self doDragAction:eDragOver sender:sender];
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingExited: entered\n"));
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ [self doDragAction:eDragExit sender:sender];
+ NS_IF_RELEASE(mDragService);
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ BOOL handled = [self doDragAction:eDrop sender:sender] != NSDragOperationNone;
+ NS_IF_RELEASE(mDragService);
+ return handled;
+}
+
+// NSDraggingSource
+- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint
+{
+ // Get the drag service if it isn't already cached. The drag service
+ // isn't cached when dragging over a different application.
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ if (!dragService) {
+ dragService = do_GetService(kDragServiceContractID);
+ }
+
+ if (dragService) {
+ NSPoint pnt = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(pnt);
+
+ LayoutDeviceIntPoint devPoint = mGeckoChild->CocoaPointsToDevPixels(pnt);
+ dragService->DragMoved(devPoint.x, devPoint.y);
+ }
+}
+
+// NSDraggingSource
+- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ gDraggedTransferables = nullptr;
+
+ NSEvent *currentEvent = [NSApp currentEvent];
+ gUserCancelledDrag = ([currentEvent type] == NSKeyDown &&
+ [currentEvent keyCode] == kVK_Escape);
+
+ if (!mDragService) {
+ CallGetService(kDragServiceContractID, &mDragService);
+ NS_ASSERTION(mDragService, "Couldn't get a drag service - big problem!");
+ }
+
+ if (mDragService) {
+ // set the dragend point from the current mouse location
+ nsDragService* dragService = static_cast<nsDragService *>(mDragService);
+ NSPoint pnt = [NSEvent mouseLocation];
+ FlipCocoaScreenCoordinate(pnt);
+ dragService->SetDragEndPoint(gfx::IntPoint::Round(pnt.x, pnt.y));
+
+ // XXX: dropEffect should be updated per |operation|.
+ // As things stand though, |operation| isn't well handled within "our"
+ // events, that is, when the drop happens within the window: it is set
+ // either to NSDragOperationGeneric or to NSDragOperationNone.
+ // For that reason, it's not yet possible to override dropEffect per the
+ // given OS value, and it's also unclear what's the correct dropEffect
+ // value for NSDragOperationGeneric that is passed by other applications.
+ // All that said, NSDragOperationNone is still reliable.
+ if (operation == NSDragOperationNone) {
+ nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
+ dragService->GetDataTransfer(getter_AddRefs(dataTransfer));
+ if (dataTransfer)
+ dataTransfer->SetDropEffectInt(nsIDragService::DRAGDROP_ACTION_NONE);
+ }
+
+ mDragService->EndDragSession(true);
+ NS_RELEASE(mDragService);
+ }
+
+ [globalDragPboard release];
+ globalDragPboard = nil;
+ [gLastDragMouseDownEvent release];
+ gLastDragMouseDownEvent = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// NSDraggingSource
+// this is just implemented so we comply with the NSDraggingSource informal protocol
+- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
+{
+ return UINT_MAX;
+}
+
+// This method is a callback typically invoked in response to a drag ending on the desktop
+// or a Findow folder window; the argument passed is a path to the drop location, to be used
+// in constructing a complete pathname for the file(s) we want to create as a result of
+// the drag.
+- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsresult rv;
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView namesOfPromisedFilesDroppedAtDestination: entering callback for promised files\n"));
+
+ nsCOMPtr<nsIFile> targFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(targFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(targFile);
+ if (!macLocalFile) {
+ NS_ERROR("No Mac local file");
+ return nil;
+ }
+
+ if (!NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)dropDestination))) {
+ NS_ERROR("failed InitWithCFURL");
+ return nil;
+ }
+
+ if (!gDraggedTransferables)
+ return nil;
+
+ uint32_t transferableCount;
+ rv = gDraggedTransferables->GetLength(&transferableCount);
+ if (NS_FAILED(rv))
+ return nil;
+
+ for (uint32_t i = 0; i < transferableCount; i++) {
+ nsCOMPtr<nsITransferable> item = do_QueryElementAt(gDraggedTransferables, i);
+ if (!item) {
+ NS_ERROR("no transferable");
+ return nil;
+ }
+
+ item->SetTransferData(kFilePromiseDirectoryMime, macLocalFile, sizeof(nsIFile*));
+
+ // now request the kFilePromiseMime data, which will invoke the data provider
+ // If successful, the returned data is a reference to the resulting file.
+ nsCOMPtr<nsISupports> fileDataPrimitive;
+ uint32_t dataSize = 0;
+ item->GetTransferData(kFilePromiseMime, getter_AddRefs(fileDataPrimitive), &dataSize);
+ }
+
+ NSPasteboard* generalPboard = [NSPasteboard pasteboardWithName:NSDragPboard];
+ NSData* data = [generalPboard dataForType:@"application/x-moz-file-promise-dest-filename"];
+ NSString* name = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ NSArray* rslt = [NSArray arrayWithObject:name];
+
+ [name release];
+
+ return rslt;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+#pragma mark -
+
+// Support for the "Services" menu. We currently only support sending strings
+// and HTML to system services.
+
+- (id)validRequestorForSendType:(NSString *)sendType
+ returnType:(NSString *)returnType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // sendType contains the type of data that the service would like this
+ // application to send to it. sendType is nil if the service is not
+ // requesting any data.
+ //
+ // returnType contains the type of data the the service would like to
+ // return to this application (e.g., to overwrite the selection).
+ // returnType is nil if the service will not return any data.
+ //
+ // The following condition thus triggers when the service expects a string
+ // or HTML from us or no data at all AND when the service will either not
+ // send back any data to us or will send a string or HTML back to us.
+
+#define IsSupportedType(typeStr) ([typeStr isEqual:NSStringPboardType] || [typeStr isEqual:NSHTMLPboardType])
+
+ id result = nil;
+
+ if ((!sendType || IsSupportedType(sendType)) &&
+ (!returnType || IsSupportedType(returnType))) {
+ if (mGeckoChild) {
+ // Assume that this object will be able to handle this request.
+ result = self;
+
+ // Keep the ChildView alive during this operation.
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ if (sendType) {
+ // Determine if there is a current selection (chrome/content).
+ if (!nsClipboard::sSelectionCache) {
+ result = nil;
+ }
+ }
+
+ // Determine if we can paste (if receiving data from the service).
+ if (mGeckoChild && returnType) {
+ WidgetContentCommandEvent command(true,
+ eContentCommandPasteTransferable,
+ mGeckoChild, true);
+ // This might possibly destroy our widget (and null out mGeckoChild).
+ mGeckoChild->DispatchWindowEvent(command);
+ if (!mGeckoChild || !command.mSucceeded || !command.mIsEnabled)
+ result = nil;
+ }
+ }
+ }
+
+#undef IsSupportedType
+
+ // Give the superclass a chance if this object will not handle this request.
+ if (!result)
+ result = [super validRequestorForSendType:sendType returnType:returnType];
+
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
+ types:(NSArray *)types
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+
+ // Make sure that the service will accept strings or HTML.
+ if ([types containsObject:NSStringPboardType] == NO &&
+ [types containsObject:NSHTMLPboardType] == NO)
+ return NO;
+
+ // Bail out if there is no Gecko object.
+ if (!mGeckoChild)
+ return NO;
+
+ // Transform the transferable to an NSDictionary.
+ NSDictionary* pasteboardOutputDict = nullptr;
+
+ pasteboardOutputDict = nsClipboard::
+ PasteboardDictFromTransferable(nsClipboard::sSelectionCache);
+
+ if (!pasteboardOutputDict)
+ return NO;
+
+ // Declare the pasteboard types.
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount];
+ [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ [pboard declareTypes:declaredTypes owner:nil];
+
+ // Write the data to the pasteboard.
+ for (unsigned int i = 0; i < typeCount; i++) {
+ NSString* currentKey = [declaredTypes objectAtIndex:i];
+ id currentValue = [pasteboardOutputDict valueForKey:currentKey];
+
+ if (currentKey == NSStringPboardType ||
+ currentKey == kCorePboardType_url ||
+ currentKey == kCorePboardType_urld ||
+ currentKey == kCorePboardType_urln) {
+ [pboard setString:currentValue forType:currentKey];
+ } else if (currentKey == NSHTMLPboardType) {
+ [pboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue)) forType:currentKey];
+ } else if (currentKey == NSTIFFPboardType) {
+ [pboard setData:currentValue forType:currentKey];
+ } else if (currentKey == NSFilesPromisePboardType) {
+ [pboard setPropertyList:currentValue forType:currentKey];
+ }
+ }
+
+ return YES;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Called if the service wants us to replace the current selection.
+- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
+{
+ nsresult rv;
+ nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
+ if (NS_FAILED(rv))
+ return NO;
+ trans->Init(nullptr);
+
+ trans->AddDataFlavor(kUnicodeMime);
+ trans->AddDataFlavor(kHTMLMime);
+
+ rv = nsClipboard::TransferableFromPasteboard(trans, pboard);
+ if (NS_FAILED(rv))
+ return NO;
+
+ NS_ENSURE_TRUE(mGeckoChild, false);
+
+ WidgetContentCommandEvent command(true,
+ eContentCommandPasteTransferable,
+ mGeckoChild);
+ command.mTransferable = trans;
+ mGeckoChild->DispatchWindowEvent(command);
+
+ return command.mSucceeded && command.mIsEnabled;
+}
+
+NS_IMETHODIMP
+nsChildView::GetSelectionAsPlaintext(nsAString& aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!nsClipboard::sSelectionCache) {
+ MOZ_ASSERT(aResult.IsEmpty());
+ return NS_OK;
+ }
+
+ // Get the current chrome or content selection.
+ NSDictionary* pasteboardOutputDict = nullptr;
+ pasteboardOutputDict = nsClipboard::
+ PasteboardDictFromTransferable(nsClipboard::sSelectionCache);
+
+ if (NS_WARN_IF(!pasteboardOutputDict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Declare the pasteboard types.
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* declaredTypes = [NSMutableArray arrayWithCapacity:typeCount];
+ [declaredTypes addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ NSString* currentKey = [declaredTypes objectAtIndex:0];
+ NSString* currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ const char* textSelection = [currentValue UTF8String];
+ aResult = NS_ConvertUTF8toUTF16(textSelection);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+#pragma mark -
+
+#ifdef ACCESSIBILITY
+
+/* Every ChildView has a corresponding mozDocAccessible object that is doing all
+ the heavy lifting. The topmost ChildView corresponds to a mozRootAccessible
+ object.
+
+ All ChildView needs to do is to route all accessibility calls (from the NSAccessibility APIs)
+ down to its object, pretending that they are the same.
+*/
+- (id<mozAccessible>)accessible
+{
+ if (!mGeckoChild)
+ return nil;
+
+ id<mozAccessible> nativeAccessible = nil;
+
+ nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ RefPtr<nsChildView> geckoChild(mGeckoChild);
+ RefPtr<a11y::Accessible> accessible = geckoChild->GetDocumentAccessible();
+ if (!accessible)
+ return nil;
+
+ accessible->GetNativeInterface((void**)&nativeAccessible);
+
+#ifdef DEBUG_hakan
+ NSAssert(![nativeAccessible isExpired], @"native acc is expired!!!");
+#endif
+
+ return nativeAccessible;
+}
+
+/* Implementation of formal mozAccessible formal protocol (enabling mozViews
+ to talk to mozAccessible objects in the accessibility module). */
+
+- (BOOL)hasRepresentedView
+{
+ return YES;
+}
+
+- (id)representedView
+{
+ return self;
+}
+
+- (BOOL)isRoot
+{
+ return [[self accessible] isRoot];
+}
+
+#ifdef DEBUG
+- (void)printHierarchy
+{
+ [[self accessible] printHierarchy];
+}
+#endif
+
+#pragma mark -
+
+// general
+
+- (BOOL)accessibilityIsIgnored
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityIsIgnored];
+
+ return [[self accessible] accessibilityIsIgnored];
+}
+
+- (id)accessibilityHitTest:(NSPoint)point
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityHitTest:point];
+
+ return [[self accessible] accessibilityHitTest:point];
+}
+
+- (id)accessibilityFocusedUIElement
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityFocusedUIElement];
+
+ return [[self accessible] accessibilityFocusedUIElement];
+}
+
+// actions
+
+- (NSArray*)accessibilityActionNames
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityActionNames];
+
+ return [[self accessible] accessibilityActionNames];
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityActionDescription:action];
+
+ return [[self accessible] accessibilityActionDescription:action];
+}
+
+- (void)accessibilityPerformAction:(NSString*)action
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityPerformAction:action];
+
+ return [[self accessible] accessibilityPerformAction:action];
+}
+
+// attributes
+
+- (NSArray*)accessibilityAttributeNames
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityAttributeNames];
+
+ return [[self accessible] accessibilityAttributeNames];
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
+{
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityIsAttributeSettable:attribute];
+
+ return [[self accessible] accessibilityIsAttributeSettable:attribute];
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mozilla::a11y::ShouldA11yBeEnabled())
+ return [super accessibilityAttributeValue:attribute];
+
+ id<mozAccessible> accessible = [self accessible];
+
+ // if we're the root (topmost) accessible, we need to return our native AXParent as we
+ // traverse outside to the hierarchy of whoever embeds us. thus, fall back on NSView's
+ // default implementation for this attribute.
+ if ([attribute isEqualToString:NSAccessibilityParentAttribute] && [accessible isRoot]) {
+ id parentAccessible = [super accessibilityAttributeValue:attribute];
+ return parentAccessible;
+ }
+
+ return [accessible accessibilityAttributeValue:attribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+#endif /* ACCESSIBILITY */
+
+@end
+
+@implementation PixelHostingView
+
+- (BOOL)isFlipped {
+ return YES;
+}
+
+- (NSView*)hitTest:(NSPoint)aPoint {
+ return nil;
+}
+
+- (void)drawRect:(NSRect)aRect {
+ [(ChildView*)[self superview] doDrawRect:aRect];
+}
+
+- (BOOL)wantsBestResolutionOpenGLSurface {
+ return nsCocoaUtils::HiDPIEnabled() ? YES : NO;
+}
+
+@end
+
+#pragma mark -
+
+void
+ChildViewMouseTracker::OnDestroyView(ChildView* aView)
+{
+ if (sLastMouseEventView == aView) {
+ sLastMouseEventView = nil;
+ [sLastMouseMoveEvent release];
+ sLastMouseMoveEvent = nil;
+ }
+}
+
+void
+ChildViewMouseTracker::OnDestroyWindow(NSWindow* aWindow)
+{
+ if (sWindowUnderMouse == aWindow) {
+ sWindowUnderMouse = nil;
+ }
+}
+
+void
+ChildViewMouseTracker::MouseEnteredWindow(NSEvent* aEvent)
+{
+ sWindowUnderMouse = [aEvent window];
+ ReEvaluateMouseEnterState(aEvent);
+}
+
+void
+ChildViewMouseTracker::MouseExitedWindow(NSEvent* aEvent)
+{
+ if (sWindowUnderMouse == [aEvent window]) {
+ sWindowUnderMouse = nil;
+ ReEvaluateMouseEnterState(aEvent);
+ }
+}
+
+void
+ChildViewMouseTracker::ReEvaluateMouseEnterState(NSEvent* aEvent, ChildView* aOldView)
+{
+ ChildView* oldView = aOldView ? aOldView : sLastMouseEventView;
+ sLastMouseEventView = ViewForEvent(aEvent);
+ if (sLastMouseEventView != oldView) {
+ // Send enter and / or exit events.
+ WidgetMouseEvent::ExitFrom exitFrom =
+ [sLastMouseEventView window] == [oldView window] ?
+ WidgetMouseEvent::eChild : WidgetMouseEvent::eTopLevel;
+ [oldView sendMouseEnterOrExitEvent:aEvent
+ enter:NO
+ exitFrom:exitFrom];
+ // After the cursor exits the window set it to a visible regular arrow cursor.
+ if (exitFrom == WidgetMouseEvent::eTopLevel) {
+ [[nsCursorManager sharedInstance] setCursor:eCursor_standard];
+ }
+ [sLastMouseEventView sendMouseEnterOrExitEvent:aEvent
+ enter:YES
+ exitFrom:exitFrom];
+ }
+}
+
+void
+ChildViewMouseTracker::ResendLastMouseMoveEvent()
+{
+ if (sLastMouseMoveEvent) {
+ MouseMoved(sLastMouseMoveEvent);
+ }
+}
+
+void
+ChildViewMouseTracker::MouseMoved(NSEvent* aEvent)
+{
+ MouseEnteredWindow(aEvent);
+ [sLastMouseEventView handleMouseMoved:aEvent];
+ if (sLastMouseMoveEvent != aEvent) {
+ [sLastMouseMoveEvent release];
+ sLastMouseMoveEvent = [aEvent retain];
+ }
+}
+
+void
+ChildViewMouseTracker::MouseScrolled(NSEvent* aEvent)
+{
+ if (!nsCocoaUtils::IsMomentumScrollEvent(aEvent)) {
+ // Store the position so we can pin future momentum scroll events.
+ sLastScrollEventScreenLocation = nsCocoaUtils::ScreenLocationForEvent(aEvent);
+ }
+}
+
+ChildView*
+ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
+{
+ NSWindow* window = sWindowUnderMouse;
+ if (!window)
+ return nil;
+
+ NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window);
+ NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
+
+ if (![view isKindOfClass:[ChildView class]])
+ return nil;
+
+ ChildView* childView = (ChildView*)view;
+ // If childView is being destroyed return nil.
+ if (![childView widget])
+ return nil;
+ return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil;
+}
+
+BOOL
+ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent,
+ ChildView* aView, BOOL aIsClickThrough)
+{
+ // Right mouse down events may get through to all windows, even to a top level
+ // window with an open sheet.
+ if (!aWindow || [aEvent type] == NSRightMouseDown)
+ return YES;
+
+ id delegate = [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return YES;
+
+ nsIWidget *windowWidget = [(WindowDelegate *)delegate geckoWidget];
+ if (!windowWidget)
+ return YES;
+
+ NSWindow* topLevelWindow = nil;
+
+ switch (windowWidget->WindowType()) {
+ case eWindowType_popup:
+ // If this is a context menu, it won't have a parent. So we'll always
+ // accept mouse move events on context menus even when none of our windows
+ // is active, which is the right thing to do.
+ // For panels, the parent window is the XUL window that owns the panel.
+ return WindowAcceptsEvent([aWindow parentWindow], aEvent, aView, aIsClickThrough);
+
+ case eWindowType_toplevel:
+ case eWindowType_dialog:
+ if ([aWindow attachedSheet])
+ return NO;
+
+ topLevelWindow = aWindow;
+ break;
+ case eWindowType_sheet: {
+ nsIWidget* parentWidget = windowWidget->GetSheetWindowParent();
+ if (!parentWidget)
+ return YES;
+
+ topLevelWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW);
+ break;
+ }
+
+ default:
+ return YES;
+ }
+
+ if (!topLevelWindow ||
+ ([topLevelWindow isMainWindow] && !aIsClickThrough) ||
+ [aEvent type] == NSOtherMouseDown ||
+ (([aEvent modifierFlags] & NSCommandKeyMask) != 0 &&
+ [aEvent type] != NSMouseMoved))
+ return YES;
+
+ // If we're here then we're dealing with a left click or mouse move on an
+ // inactive window or something similar. Ask Gecko what to do.
+ return [aView inactiveWindowAcceptsMouseEvent:aEvent];
+}
+
+#pragma mark -
+
+@interface NSView (MethodSwizzling)
+- (BOOL)nsChildView_NSView_mouseDownCanMoveWindow;
+@end
+
+@implementation NSView (MethodSwizzling)
+
+// All top-level browser windows belong to the ToolbarWindow class and have
+// NSTexturedBackgroundWindowMask turned on in their "style" (see particularly
+// [ToolbarWindow initWithContentRect:...] in nsCocoaWindow.mm). This style
+// normally means the window "may be moved by clicking and dragging anywhere
+// in the window background", but we've suppressed this by giving the
+// ChildView class a mouseDownCanMoveWindow method that always returns NO.
+// Normally a ToolbarWindow's contentView (not a ChildView) returns YES when
+// NSTexturedBackgroundWindowMask is turned on. But normally this makes no
+// difference. However, under some (probably very unusual) circumstances
+// (and only on Leopard) it *does* make a difference -- for example it
+// triggers bmo bugs 431902 and 476393. So here we make sure that a
+// ToolbarWindow's contentView always returns NO from the
+// mouseDownCanMoveWindow method.
+- (BOOL)nsChildView_NSView_mouseDownCanMoveWindow
+{
+ NSWindow *ourWindow = [self window];
+ NSView *contentView = [ourWindow contentView];
+ if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView))
+ return [ourWindow isMovableByWindowBackground];
+ return [self nsChildView_NSView_mouseDownCanMoveWindow];
+}
+
+@end
diff --git a/widget/cocoa/nsClipboard.h b/widget/cocoa/nsClipboard.h
new file mode 100644
index 0000000000..45871efe10
--- /dev/null
+++ b/widget/cocoa/nsClipboard.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsClipboard_h_
+#define nsClipboard_h_
+
+#include "nsIClipboard.h"
+#include "nsXPIDLString.h"
+#include "mozilla/StaticPtr.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsITransferable;
+
+class nsClipboard : public nsIClipboard
+{
+
+public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ // On macOS, cache the transferable of the current selection (chrome/content)
+ // in the parent process. This is needed for the services menu which
+ // requires synchronous access to the current selection.
+ static mozilla::StaticRefPtr<nsITransferable> sSelectionCache;
+
+ // Helper methods, used also by nsDragService
+ static NSDictionary* PasteboardDictFromTransferable(nsITransferable *aTransferable);
+ static bool IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType);
+ static NSString* WrapHtmlForSystemPasteboard(NSString* aString);
+ static nsresult TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteboard *pboard);
+
+protected:
+
+ // impelement the native clipboard behavior
+ NS_IMETHOD SetNativeClipboardData(int32_t aWhichClipboard);
+ NS_IMETHOD GetNativeClipboardData(nsITransferable * aTransferable, int32_t aWhichClipboard);
+ void ClearSelectionCache();
+ void SetSelectionCache(nsITransferable* aTransferable);
+
+private:
+ virtual ~nsClipboard();
+ int32_t mCachedClipboard;
+ int32_t mChangeCount; // Set to the native change count after any modification of the clipboard.
+
+ bool mIgnoreEmptyNotification;
+ nsCOMPtr<nsIClipboardOwner> mClipboardOwner;
+ nsCOMPtr<nsITransferable> mTransferable;
+};
+
+#endif // nsClipboard_h_
diff --git a/widget/cocoa/nsClipboard.mm b/widget/cocoa/nsClipboard.mm
new file mode 100644
index 0000000000..4146f17851
--- /dev/null
+++ b/widget/cocoa/nsClipboard.mm
@@ -0,0 +1,775 @@
+/* -*- Mode: C++; tab-width: 2; 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 "mozilla/Logging.h"
+
+#include "mozilla/Unused.h"
+
+#include "gfxPlatform.h"
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsClipboard.h"
+#include "nsString.h"
+#include "nsISupportsPrimitives.h"
+#include "nsXPIDLString.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsMemory.h"
+#include "nsIFile.h"
+#include "nsStringStream.h"
+#include "nsDragService.h"
+#include "nsEscape.h"
+#include "nsPrintfCString.h"
+#include "nsObjCExceptions.h"
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SourceSurface;
+using mozilla::LogLevel;
+
+// Screenshots use the (undocumented) png pasteboard type.
+#define IMAGE_PASTEBOARD_TYPES NSTIFFPboardType, @"Apple PNG pasteboard type", nil
+
+extern PRLogModuleInfo* sCocoaLog;
+
+extern void EnsureLogInitialized();
+
+mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache;
+
+nsClipboard::nsClipboard()
+ : mCachedClipboard(-1)
+ , mChangeCount(0)
+ , mIgnoreEmptyNotification(false)
+{
+ EnsureLogInitialized();
+}
+
+nsClipboard::~nsClipboard()
+{
+ EmptyClipboard(kGlobalClipboard);
+ EmptyClipboard(kFindClipboard);
+ ClearSelectionCache();
+}
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+// We separate this into its own function because after an @try, all local
+// variables within that function get marked as volatile, and our C++ type
+// system doesn't like volatile things.
+static NSData*
+GetDataFromPasteboard(NSPasteboard* aPasteboard, NSString* aType)
+{
+ NSData *data = nil;
+ @try {
+ data = [aPasteboard dataForType:aType];
+ } @catch (NSException* e) {
+ NS_WARNING(nsPrintfCString("Exception raised while getting data from the pasteboard: \"%s - %s\"",
+ [[e name] UTF8String], [[e reason] UTF8String]).get());
+ mozilla::Unused << e;
+ }
+ return data;
+}
+
+void
+nsClipboard::SetSelectionCache(nsITransferable *aTransferable)
+{
+ sSelectionCache = aTransferable;
+}
+
+void
+nsClipboard::ClearSelectionCache()
+{
+ sSelectionCache = nullptr;
+}
+
+NS_IMETHODIMP
+nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !mTransferable)
+ return NS_ERROR_FAILURE;
+
+ mIgnoreEmptyNotification = true;
+
+ NSDictionary* pasteboardOutputDict = PasteboardDictFromTransferable(mTransferable);
+ if (!pasteboardOutputDict)
+ return NS_ERROR_FAILURE;
+
+ unsigned int outputCount = [pasteboardOutputDict count];
+ NSArray* outputKeys = [pasteboardOutputDict allKeys];
+ NSPasteboard* cocoaPasteboard;
+ if (aWhichClipboard == kFindClipboard) {
+ cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
+ [cocoaPasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
+ } else {
+ // Write everything else out to the general pasteboard.
+ cocoaPasteboard = [NSPasteboard generalPasteboard];
+ [cocoaPasteboard declareTypes:outputKeys owner:nil];
+ }
+
+ for (unsigned int i = 0; i < outputCount; i++) {
+ NSString* currentKey = [outputKeys objectAtIndex:i];
+ id currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ if (aWhichClipboard == kFindClipboard) {
+ if (currentKey == NSStringPboardType)
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ } else {
+ if (currentKey == NSStringPboardType ||
+ currentKey == kCorePboardType_url ||
+ currentKey == kCorePboardType_urld ||
+ currentKey == kCorePboardType_urln) {
+ [cocoaPasteboard setString:currentValue forType:currentKey];
+ } else if (currentKey == NSHTMLPboardType) {
+ [cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
+ forType:currentKey];
+ } else {
+ [cocoaPasteboard setData:currentValue forType:currentKey];
+ }
+ }
+ }
+
+ mCachedClipboard = aWhichClipboard;
+ mChangeCount = [cocoaPasteboard changeCount];
+
+ mIgnoreEmptyNotification = false;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsClipboard::TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteboard *cocoaPasteboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr)); // i has a flavr
+
+ // printf("looking for clipboard data of type %s\n", flavorStr.get());
+
+ NSString *pboardType = nil;
+ if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
+ NSString* pString = [cocoaPasteboard stringForType:pboardType];
+ if (!pString)
+ continue;
+
+ NSData* stringData;
+ if ([pboardType isEqualToString:NSRTFPboardType]) {
+ stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
+ } else {
+ stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
+ }
+ unsigned int dataLength = [stringData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ [stringData getBytes:clipboardDataPtr];
+
+ // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
+ int32_t signedDataLength = dataLength;
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
+ dataLength = signedDataLength;
+
+ // skip BOM (Byte Order Mark to distinguish little or big endian)
+ char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
+ if ((dataLength > 2) &&
+ ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
+ (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
+ dataLength -= sizeof(char16_t);
+ clipboardDataPtrNoBOM += 1;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ break;
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ NSString* type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (!type) {
+ continue;
+ }
+
+ NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
+ if (!pasteboardData) {
+ continue;
+ }
+
+ unsigned int dataLength = [pasteboardData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ [pasteboardData getBytes:clipboardDataPtr];
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
+ getter_AddRefs(genericDataWrapper));
+
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ }
+ else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Figure out if there's data on the pasteboard we can grab (sanity check)
+ NSString *type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
+ if (!type)
+ continue;
+
+ // Read data off the clipboard
+ NSData *pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
+ if (!pasteboardData)
+ continue;
+
+ // Figure out what type we're converting to
+ CFStringRef outputType = NULL;
+ if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime))
+ outputType = CFSTR("public.jpeg");
+ else if (flavorStr.EqualsLiteral(kPNGImageMime))
+ outputType = CFSTR("public.png");
+ else if (flavorStr.EqualsLiteral(kGIFImageMime))
+ outputType = CFSTR("com.compuserve.gif");
+ else
+ continue;
+
+ // Use ImageIO to interpret the data on the clipboard and transcode.
+ // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
+ // and safely in most cases (like ObjC). A notable exception is CFRelease.
+ NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
+ (NSNumber*)kCFBooleanTrue, kCGImageSourceShouldAllowFloat,
+ (type == NSTIFFPboardType ? @"public.tiff" : @"public.png"),
+ kCGImageSourceTypeIdentifierHint, nil];
+
+ CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pasteboardData,
+ (CFDictionaryRef)options);
+ NSMutableData *encodedData = [NSMutableData data];
+ CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)encodedData,
+ outputType,
+ 1, NULL);
+ CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
+ bool successfullyConverted = CGImageDestinationFinalize(dest);
+
+ if (successfullyConverted) {
+ // Put the converted data in a form Gecko can understand
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream), (const char*)[encodedData bytes],
+ [encodedData length], NS_ASSIGNMENT_COPY);
+
+ aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
+ }
+
+ if (dest)
+ CFRelease(dest);
+ if (source)
+ CFRelease(source);
+
+ if (successfullyConverted)
+ break;
+ else
+ continue;
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhichClipboard)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !aTransferable)
+ return NS_ERROR_FAILURE;
+
+ NSPasteboard* cocoaPasteboard;
+ if (aWhichClipboard == kFindClipboard) {
+ cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
+ } else {
+ cocoaPasteboard = [NSPasteboard generalPasteboard];
+ }
+ if (!cocoaPasteboard)
+ return NS_ERROR_FAILURE;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ // If we were the last ones to put something on the pasteboard, then just use the cached
+ // transferable. Otherwise clear it because it isn't relevant any more.
+ if (mCachedClipboard == aWhichClipboard &&
+ mChangeCount == [cocoaPasteboard changeCount]) {
+ if (mTransferable) {
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ nsCOMPtr<nsISupports> dataSupports;
+ uint32_t dataSize = 0;
+ rv = mTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
+ return NS_OK; // maybe try to fill in more types? Is there a point?
+ }
+ }
+ }
+ } else {
+ EmptyClipboard(aWhichClipboard);
+ }
+
+ // at this point we can't satisfy the request from cache data so let's look
+ // for things other people put on the system clipboard
+
+ return nsClipboard::TransferableFromPasteboard(aTransferable, cocoaPasteboard);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// returns true if we have *any* of the passed in flavors available for pasting
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
+ int32_t aWhichClipboard, bool* outResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *outResult = false;
+
+ if ((aWhichClipboard != kGlobalClipboard) || !aFlavorList)
+ return NS_OK;
+
+ // first see if we have data for this in our cached transferable
+ if (mTransferable) {
+ nsCOMPtr<nsIArray> transferableFlavorList;
+ nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t transferableFlavorCount;
+ transferableFlavorList->GetLength(&transferableFlavorCount);
+ for (uint32_t j = 0; j < transferableFlavorCount; j++) {
+ nsCOMPtr<nsISupportsCString> currentTransferableFlavor =
+ do_QueryElementAt(transferableFlavorList, j);
+ if (!currentTransferableFlavor)
+ continue;
+ nsXPIDLCString transferableFlavorStr;
+ currentTransferableFlavor->ToString(getter_Copies(transferableFlavorStr));
+
+ for (uint32_t k = 0; k < aLength; k++) {
+ if (transferableFlavorStr.Equals(aFlavorList[k])) {
+ *outResult = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+
+ NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
+
+ for (uint32_t i = 0; i < aLength; i++) {
+ nsDependentCString mimeType(aFlavorList[i]);
+ NSString *pboardType = nil;
+
+ if (nsClipboard::IsStringType(mimeType, &pboardType)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
+ if (availableType && [availableType isEqualToString:pboardType]) {
+ *outResult = true;
+ break;
+ }
+ } else if (!strcmp(aFlavorList[i], kCustomTypesMime)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (availableType) {
+ *outResult = true;
+ break;
+ }
+ } else if (!strcmp(aFlavorList[i], kJPEGImageMime) ||
+ !strcmp(aFlavorList[i], kJPGImageMime) ||
+ !strcmp(aFlavorList[i], kPNGImageMime) ||
+ !strcmp(aFlavorList[i], kGIFImageMime)) {
+ NSString* availableType = [generalPBoard availableTypeFromArray:
+ [NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
+ if (availableType) {
+ *outResult = true;
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool *_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+// This function converts anything that other applications might understand into the system format
+// and puts it into a dictionary which it returns.
+// static
+NSDictionary*
+nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!aTransferable)
+ return nil;
+
+ NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
+
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return nil;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+ for (uint32_t i = 0; i < flavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("writing out clipboard data of type %s (%d)\n", flavorStr.get(), i));
+
+ NSString *pboardType = nil;
+
+ if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
+ void* data = nullptr;
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
+ nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
+
+ NSString* nativeString;
+ if (data)
+ nativeString = [NSString stringWithCharacters:(const unichar*)data length:(dataSize / sizeof(char16_t))];
+ else
+ nativeString = [NSString string];
+
+ // be nice to Carbon apps, normalize the receiver's contents using Form C.
+ nativeString = [nativeString precomposedStringWithCanonicalMapping];
+
+ [pasteboardOutputDict setObject:nativeString forKey:pboardType];
+
+ free(data);
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ void* data = nullptr;
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
+ nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
+
+ if (data) {
+ NSData* nativeData = [NSData dataWithBytes:data length:dataSize];
+
+ [pasteboardOutputDict setObject:nativeData forKey:kCustomTypesPboardType];
+ free(data);
+ }
+ }
+ else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime) ||
+ flavorStr.EqualsLiteral(kNativeImageMime)) {
+ uint32_t dataSize = 0;
+ nsCOMPtr<nsISupports> transferSupports;
+ aTransferable->GetTransferData(flavorStr, getter_AddRefs(transferSupports), &dataSize);
+ nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive(do_QueryInterface(transferSupports));
+ if (!ptrPrimitive)
+ continue;
+
+ nsCOMPtr<nsISupports> primitiveData;
+ ptrPrimitive->GetData(getter_AddRefs(primitiveData));
+
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
+ if (!image) {
+ NS_WARNING("Image isn't an imgIContainer in transferable");
+ continue;
+ }
+
+ RefPtr<SourceSurface> surface =
+ image->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ continue;
+ }
+ CGImageRef imageRef = NULL;
+ rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
+ if (NS_FAILED(rv) || !imageRef) {
+ continue;
+ }
+
+ // Convert the CGImageRef to TIFF data.
+ CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ CGImageDestinationRef destRef = CGImageDestinationCreateWithData(tiffData,
+ CFSTR("public.tiff"),
+ 1,
+ NULL);
+ CGImageDestinationAddImage(destRef, imageRef, NULL);
+ bool successfullyConverted = CGImageDestinationFinalize(destRef);
+
+ CGImageRelease(imageRef);
+ if (destRef)
+ CFRelease(destRef);
+
+ if (!successfullyConverted) {
+ if (tiffData)
+ CFRelease(tiffData);
+ continue;
+ }
+
+ [pasteboardOutputDict setObject:(NSMutableData*)tiffData forKey:NSTIFFPboardType];
+ if (tiffData)
+ CFRelease(tiffData);
+ }
+ else if (flavorStr.EqualsLiteral(kFileMime)) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericFile;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericFile), &len);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile));
+ if (!file) {
+ nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericFile));
+
+ if (ptr) {
+ ptr->GetData(getter_AddRefs(genericFile));
+ file = do_QueryInterface(genericFile);
+ }
+ }
+
+ if (!file) {
+ continue;
+ }
+
+ nsAutoString fileURI;
+ rv = file->GetPath(fileURI);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ NSString* str = nsCocoaUtils::ToNSString(fileURI);
+ NSArray* fileList = [NSArray arrayWithObjects:str, nil];
+ [pasteboardOutputDict setObject:fileList forKey:NSFilenamesPboardType];
+ }
+ else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:NSFilesPromisePboardType];
+ }
+ else if (flavorStr.EqualsLiteral(kURLMime)) {
+ uint32_t len = 0;
+ nsCOMPtr<nsISupports> genericURL;
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericURL), &len);
+ nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
+
+ nsAutoString url;
+ urlObject->GetData(url);
+
+ // A newline embedded in the URL means that the form is actually URL + title.
+ int32_t newlinePos = url.FindChar(char16_t('\n'));
+ if (newlinePos >= 0) {
+ url.Truncate(newlinePos);
+
+ nsAutoString urlTitle;
+ urlObject->GetData(urlTitle);
+ urlTitle.Mid(urlTitle, newlinePos + 1, len - (newlinePos + 1));
+
+ NSString *nativeTitle = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(urlTitle.get())
+ length:urlTitle.Length()];
+ // be nice to Carbon apps, normalize the receiver's contents using Form C.
+ [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urln];
+ // Also put the title out as 'urld', since some recipients will look for that.
+ [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urld];
+ [nativeTitle release];
+ }
+
+ // The Finder doesn't like getting random binary data aka
+ // Unicode, so change it into an escaped URL containing only
+ // ASCII.
+ nsAutoCString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
+ nsAutoCString escData;
+ NS_EscapeURL(utf8Data.get(), utf8Data.Length(), esc_OnlyNonASCII|esc_AlwaysCopy, escData);
+
+ // printf("Escaped url is %s, length %d\n", escData.get(), escData.Length());
+
+ NSString *nativeURL = [NSString stringWithUTF8String:escData.get()];
+ [pasteboardOutputDict setObject:nativeURL forKey:kCorePboardType_url];
+ }
+ // If it wasn't a type that we recognize as exportable we don't put it on the system
+ // clipboard. We'll just access it from our cached transferable when we need it.
+ }
+
+ return pasteboardOutputDict;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsClipboard::IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType)
+{
+ if (aMIMEType.EqualsLiteral(kUnicodeMime)) {
+ *aPasteboardType = NSStringPboardType;
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kRTFMime)) {
+ *aPasteboardType = NSRTFPboardType;
+ return true;
+ } else if (aMIMEType.EqualsLiteral(kHTMLMime)) {
+ *aPasteboardType = NSHTMLPboardType;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString)
+{
+ NSString* wrapped =
+ [NSString stringWithFormat:
+ @"<html>"
+ "<head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
+ "</head>"
+ "<body>"
+ "%@"
+ "</body>"
+ "</html>", aString];
+ return wrapped;
+}
+
+/**
+ * Sets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
+ int32_t aWhichClipboard)
+{
+ NS_ASSERTION (aTransferable, "clipboard given a null transferable");
+
+ if (aWhichClipboard == kSelectionCache) {
+ if (aTransferable) {
+ SetSelectionCache(aTransferable);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aTransferable == mTransferable && anOwner == mClipboardOwner) {
+ return NS_OK;
+ }
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ EmptyClipboard(aWhichClipboard);
+
+ mClipboardOwner = anOwner;
+ mTransferable = aTransferable;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (mTransferable) {
+ rv = SetNativeClipboardData(aWhichClipboard);
+ }
+
+ return rv;
+}
+
+/**
+ * Gets the transferable object
+ *
+ */
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard)
+{
+ NS_ASSERTION (aTransferable, "clipboard given a null transferable");
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard)
+ return NS_ERROR_FAILURE;
+
+ if (aTransferable) {
+ return GetNativeClipboardData(aTransferable, aWhichClipboard);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard == kSelectionCache) {
+ ClearSelectionCache();
+ return NS_OK;
+ }
+
+ bool selectClipPresent;
+ SupportsSelectionClipboard(&selectClipPresent);
+ bool findClipPresent;
+ SupportsFindClipboard(&findClipPresent);
+ if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mIgnoreEmptyNotification) {
+ return NS_OK;
+ }
+
+ if (mClipboardOwner) {
+ mClipboardOwner->LosingOwnership(mTransferable);
+ mClipboardOwner = nullptr;
+ }
+
+ mTransferable = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool* _retval)
+{
+ *_retval = false; // we don't support the selection clipboard by default.
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsCocoaDebugUtils.h b/widget/cocoa/nsCocoaDebugUtils.h
new file mode 100644
index 0000000000..814f060878
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.h
@@ -0,0 +1,136 @@
+/* -*- 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/. */
+
+#ifndef nsCocoaDebugUtils_h_
+#define nsCocoaDebugUtils_h_
+
+#include <CoreServices/CoreServices.h>
+
+// Definitions and declarations of stuff used by us from the CoreSymbolication
+// framework. This is an undocumented, private framework available on OS X
+// 10.6 and up. It's used by Apple utilities like dtrace, atos, ReportCrash
+// and crashreporterd.
+
+typedef struct _CSTypeRef {
+ unsigned long type;
+ void* contents;
+} CSTypeRef;
+
+typedef CSTypeRef CSSymbolicatorRef;
+typedef CSTypeRef CSSymbolOwnerRef;
+typedef CSTypeRef CSSymbolRef;
+typedef CSTypeRef CSSourceInfoRef;
+
+typedef struct _CSRange {
+ unsigned long long location;
+ unsigned long long length;
+} CSRange;
+
+typedef unsigned long long CSArchitecture;
+
+#define kCSNow LONG_MAX
+
+extern "C" {
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPid(pid_t pid);
+
+CSSymbolicatorRef
+CSSymbolicatorCreateWithPidFlagsAndNotification(pid_t pid,
+ uint32_t flags,
+ uint32_t notification);
+
+CSArchitecture
+CSSymbolicatorGetArchitecture(CSSymbolicatorRef symbolicator);
+
+CSSymbolOwnerRef
+CSSymbolicatorGetSymbolOwnerWithAddressAtTime(CSSymbolicatorRef symbolicator,
+ unsigned long long address,
+ long time);
+
+const char*
+CSSymbolOwnerGetName(CSSymbolOwnerRef owner);
+
+unsigned long long
+CSSymbolOwnerGetBaseAddress(CSSymbolOwnerRef owner);
+
+CSSymbolRef
+CSSymbolOwnerGetSymbolWithAddress(CSSymbolOwnerRef owner,
+ unsigned long long address);
+
+CSSourceInfoRef
+CSSymbolOwnerGetSourceInfoWithAddress(CSSymbolOwnerRef owner,
+ unsigned long long address);
+
+const char*
+CSSymbolGetName(CSSymbolRef symbol);
+
+CSRange
+CSSymbolGetRange(CSSymbolRef symbol);
+
+const char*
+CSSourceInfoGetFilename(CSSourceInfoRef info);
+
+uint32_t
+CSSourceInfoGetLineNumber(CSSourceInfoRef info);
+
+CSTypeRef
+CSRetain(CSTypeRef);
+
+void
+CSRelease(CSTypeRef);
+
+bool
+CSIsNull(CSTypeRef);
+
+void
+CSShow(CSTypeRef);
+
+const char*
+CSArchitectureGetFamilyName(CSArchitecture);
+
+} // extern "C"
+
+class nsCocoaDebugUtils
+{
+public:
+ // Like NSLog() but records more information (for example the full path to
+ // the executable and the "thread name"). Like NSLog(), writes to both
+ // stdout and the system log.
+ static void DebugLog(const char* aFormat, ...);
+
+ // Logs a stack trace of the current point of execution, to both stdout and
+ // the system log.
+ static void PrintStackTrace();
+
+ // Returns the name of the module that "owns" aAddress. This must be
+ // free()ed by the caller.
+ static char* GetOwnerName(void* aAddress);
+
+ // Returns a symbolicated representation of aAddress. This must be
+ // free()ed by the caller.
+ static char* GetAddressString(void* aAddress);
+
+private:
+ static void DebugLogInt(bool aDecorate, const char* aFormat, ...);
+ static void DebugLogV(bool aDecorate, CFStringRef aFormat, va_list aArgs);
+
+ static void PrintAddress(void* aAddress);
+
+ // The values returned by GetOwnerNameInt() and GetAddressStringInt() must
+ // be free()ed by the caller.
+ static char* GetOwnerNameInt(void* aAddress,
+ CSTypeRef aOwner = sInitializer);
+ static char* GetAddressStringInt(void* aAddress,
+ CSTypeRef aOwner = sInitializer);
+
+ static CSSymbolicatorRef GetSymbolicatorRef();
+ static void ReleaseSymbolicator();
+
+ static CSTypeRef sInitializer;
+ static CSSymbolicatorRef sSymbolicator;
+};
+
+#endif // nsCocoaDebugUtils_h_
diff --git a/widget/cocoa/nsCocoaDebugUtils.mm b/widget/cocoa/nsCocoaDebugUtils.mm
new file mode 100644
index 0000000000..35896dc401
--- /dev/null
+++ b/widget/cocoa/nsCocoaDebugUtils.mm
@@ -0,0 +1,284 @@
+/* -*- 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 "nsCocoaDebugUtils.h"
+
+#include <pthread.h>
+#include <libproc.h>
+#include <stdarg.h>
+#include <time.h>
+#include <execinfo.h>
+#include <asl.h>
+
+static char gProcPath[PROC_PIDPATHINFO_MAXSIZE] = {0};
+static char gBundleID[MAXPATHLEN] = {0};
+
+static void MaybeGetPathAndID()
+{
+ if (!gProcPath[0]) {
+ proc_pidpath(getpid(), gProcPath, sizeof(gProcPath));
+ }
+ if (!gBundleID[0]) {
+ // Apple's CFLog() uses "com.apple.console" (in its call to asl_open()) if
+ // it can't find the bundle id.
+ CFStringRef bundleID = NULL;
+ CFBundleRef mainBundle = CFBundleGetMainBundle();
+ if (mainBundle) {
+ bundleID = CFBundleGetIdentifier(mainBundle);
+ }
+ if (!bundleID) {
+ strcpy(gBundleID, "com.apple.console");
+ } else {
+ CFStringGetCString(bundleID, gBundleID, sizeof(gBundleID),
+ kCFStringEncodingUTF8);
+ }
+ }
+}
+
+static void GetThreadName(char* aName, size_t aSize)
+{
+ pthread_getname_np(pthread_self(), aName, aSize);
+}
+
+void
+nsCocoaDebugUtils::DebugLog(const char* aFormat, ...)
+{
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+ kCFStringEncodingUTF8);
+ DebugLogV(true, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogInt(bool aDecorate, const char* aFormat, ...)
+{
+ va_list args;
+ va_start(args, aFormat);
+ CFStringRef formatCFSTR =
+ CFStringCreateWithCString(kCFAllocatorDefault, aFormat,
+ kCFStringEncodingUTF8);
+ DebugLogV(aDecorate, formatCFSTR, args);
+ CFRelease(formatCFSTR);
+ va_end(args);
+}
+
+void
+nsCocoaDebugUtils::DebugLogV(bool aDecorate, CFStringRef aFormat,
+ va_list aArgs)
+{
+ MaybeGetPathAndID();
+
+ CFStringRef message =
+ CFStringCreateWithFormatAndArguments(kCFAllocatorDefault, NULL,
+ aFormat, aArgs);
+
+ int msgLength =
+ CFStringGetMaximumSizeForEncoding(CFStringGetLength(message),
+ kCFStringEncodingUTF8);
+ char* msgUTF8 = (char*) calloc(msgLength, 1);
+ CFStringGetCString(message, msgUTF8, msgLength, kCFStringEncodingUTF8);
+ CFRelease(message);
+
+ int finishedLength = msgLength + PROC_PIDPATHINFO_MAXSIZE;
+ char* finished = (char*) calloc(finishedLength, 1);
+ const time_t currentTime = time(NULL);
+ char timestamp[30] = {0};
+ ctime_r(&currentTime, timestamp);
+ if (aDecorate) {
+ char threadName[MAXPATHLEN] = {0};
+ GetThreadName(threadName, sizeof(threadName));
+ snprintf(finished, finishedLength, "(%s) %s[%u] %s[%p] %s\n",
+ timestamp, gProcPath, getpid(), threadName, pthread_self(), msgUTF8);
+ } else {
+ snprintf(finished, finishedLength, "%s\n", msgUTF8);
+ }
+ free(msgUTF8);
+
+ fputs(finished, stdout);
+
+ // Use the Apple System Log facility, as NSLog and CFLog do.
+ aslclient asl = asl_open(NULL, gBundleID, ASL_OPT_NO_DELAY);
+ aslmsg msg = asl_new(ASL_TYPE_MSG);
+ asl_set(msg, ASL_KEY_LEVEL, "4"); // kCFLogLevelWarning, used by NSLog()
+ asl_set(msg, ASL_KEY_MSG, finished);
+ asl_send(asl, msg);
+ asl_free(msg);
+ asl_close(asl);
+
+ free(finished);
+}
+
+CSTypeRef
+nsCocoaDebugUtils::sInitializer = {0};
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::sSymbolicator = {0};
+
+#define STACK_MAX 256
+
+void
+nsCocoaDebugUtils::PrintStackTrace()
+{
+ void** addresses = (void**) calloc(STACK_MAX, sizeof(void*));
+ if (!addresses) {
+ return;
+ }
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ if (CSIsNull(symbolicator)) {
+ free(addresses);
+ return;
+ }
+
+ uint32_t count = backtrace(addresses, STACK_MAX);
+ for (uint32_t i = 0; i < count; ++i) {
+ PrintAddress(addresses[i]);
+ }
+
+ ReleaseSymbolicator();
+ free(addresses);
+}
+
+void
+nsCocoaDebugUtils::PrintAddress(void* aAddress)
+{
+ const char* ownerName = "unknown";
+ const char* addressString = "unknown + 0";
+
+ char* allocatedOwnerName = nullptr;
+ char* allocatedAddressString = nullptr;
+
+ CSSymbolOwnerRef owner = {0};
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+
+ if (!CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+ if (!CSIsNull(owner)) {
+ ownerName = allocatedOwnerName = GetOwnerNameInt(aAddress, owner);
+ addressString = allocatedAddressString = GetAddressStringInt(aAddress, owner);
+ }
+ DebugLogInt(false, " (%s) %s", ownerName, addressString);
+
+ free(allocatedOwnerName);
+ free(allocatedAddressString);
+
+ ReleaseSymbolicator();
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerName(void* aAddress)
+{
+ return GetOwnerNameInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetOwnerNameInt(void* aAddress, CSTypeRef aOwner)
+{
+ char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+ const char* ownerName = "unknown";
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ ownerName = CSSymbolOwnerGetName(owner);
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s", ownerName);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+char*
+nsCocoaDebugUtils::GetAddressString(void* aAddress)
+{
+ return GetAddressStringInt(aAddress);
+}
+
+char*
+nsCocoaDebugUtils::GetAddressStringInt(void* aAddress, CSTypeRef aOwner)
+{
+ char* retval = (char*) calloc(MAXPATHLEN, 1);
+
+ const char* addressName = "unknown";
+ unsigned long long addressOffset = 0;
+
+ CSSymbolicatorRef symbolicator = GetSymbolicatorRef();
+ CSTypeRef owner = aOwner;
+
+ if (CSIsNull(owner) && !CSIsNull(symbolicator)) {
+ owner =
+ CSSymbolicatorGetSymbolOwnerWithAddressAtTime(symbolicator,
+ (unsigned long long) aAddress,
+ kCSNow);
+ }
+
+ if (!CSIsNull(owner)) {
+ CSSymbolRef symbol =
+ CSSymbolOwnerGetSymbolWithAddress(owner,
+ (unsigned long long) aAddress);
+ if (!CSIsNull(symbol)) {
+ addressName = CSSymbolGetName(symbol);
+ CSRange range = CSSymbolGetRange(symbol);
+ addressOffset = (unsigned long long) aAddress - range.location;
+ } else {
+ addressOffset = (unsigned long long)
+ aAddress - CSSymbolOwnerGetBaseAddress(owner);
+ }
+ }
+
+ snprintf(retval, MAXPATHLEN, "%s + 0x%llx",
+ addressName, addressOffset);
+ ReleaseSymbolicator();
+
+ return retval;
+}
+
+CSSymbolicatorRef
+nsCocoaDebugUtils::GetSymbolicatorRef()
+{
+ if (CSIsNull(sSymbolicator)) {
+ // 0x40e0000 is the value returned by
+ // uint32_t CSSymbolicatorGetFlagsForNListOnlyData(void). We don't use
+ // this method directly because it doesn't exist on OS X 10.6. Unless
+ // we limit ourselves to NList data, it will take too long to get a
+ // stack trace where Dwarf debugging info is available (about 15 seconds
+ // with Firefox). This means we won't be able to get a CSSourceInfoRef,
+ // or line number information. Oh well.
+ sSymbolicator =
+ CSSymbolicatorCreateWithPidFlagsAndNotification(getpid(),
+ 0x40e0000, 0);
+ }
+ // Retaining just after creation prevents crashes when calling symbolicator
+ // code (for example from PrintStackTrace()) as Firefox is quitting. Not
+ // sure why. Doing this may mean that we leak sSymbolicator on quitting
+ // (if we ever created it). No particular harm in that, though.
+ return CSRetain(sSymbolicator);
+}
+
+void
+nsCocoaDebugUtils::ReleaseSymbolicator()
+{
+ if (!CSIsNull(sSymbolicator)) {
+ CSRelease(sSymbolicator);
+ }
+}
diff --git a/widget/cocoa/nsCocoaFeatures.h b/widget/cocoa/nsCocoaFeatures.h
new file mode 100644
index 0000000000..a9cab95d56
--- /dev/null
+++ b/widget/cocoa/nsCocoaFeatures.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#ifndef nsCocoaFeatures_h_
+#define nsCocoaFeatures_h_
+
+#include <stdint.h>
+
+/// Note that this class assumes we support the platform we are running on.
+/// For better or worse, if the version is unknown or less than what we
+/// support, we set it to the minimum supported version. GetSystemVersion
+/// is the only call that returns the unadjusted values.
+class nsCocoaFeatures {
+public:
+ static int32_t macOSVersion();
+ static int32_t macOSVersionMajor();
+ static int32_t macOSVersionMinor();
+ static int32_t macOSVersionBugFix();
+ static bool OnYosemiteOrLater();
+ static bool OnElCapitanOrLater();
+ static bool OnSierraOrLater();
+ static bool OnHighSierraOrLater();
+ static bool OnMojaveOrLater();
+ static bool OnCatalinaOrLater();
+ static bool OnBigSurOrLater();
+
+ static bool IsAtLeastVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix=0);
+
+ // These are utilities that do not change or depend on the value of mOSXVersion
+ // and instead just encapsulate the encoding algorithm. Note that GetVersion
+ // actually adjusts to the lowest supported OS, so it will always return
+ // a "supported" version. GetSystemVersion does not make any modifications.
+ static void GetSystemVersion(int &aMajor, int &aMinor, int &aBugFix);
+ static int32_t GetVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix);
+ static int32_t ExtractMajorVersion(int32_t aVersion);
+ static int32_t ExtractMinorVersion(int32_t aVersion);
+ static int32_t ExtractBugFixVersion(int32_t aVersion);
+
+private:
+ static void InitializeVersionNumbers();
+
+ static int32_t mOSVersion;
+};
+#endif // nsCocoaFeatures_h_
diff --git a/widget/cocoa/nsCocoaFeatures.mm b/widget/cocoa/nsCocoaFeatures.mm
new file mode 100644
index 0000000000..e0fafb7d96
--- /dev/null
+++ b/widget/cocoa/nsCocoaFeatures.mm
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+// This file makes some assumptions about the versions of macOS.
+// We are assuming that the major, minor and bugfix versions are each less than
+// 256.
+// There are MOZ_ASSERTs for that.
+
+// The formula for the version integer is (major << 16) + (minor << 8) + bugfix.
+
+#define MACOS_VERSION_MASK 0x00FFFFFF
+#define MACOS_MAJOR_VERSION_MASK 0x00FFFFFF
+#define MACOS_MINOR_VERSION_MASK 0x00FFFFFF
+#define MACOS_BUGFIX_VERSION_MASK 0x00FFFFFF
+#define MACOS_VERSION_10_0_HEX 0x000A0000
+#define MACOS_VERSION_10_7_HEX 0x000A0700
+#define MACOS_VERSION_10_8_HEX 0x000A0800
+#define MACOS_VERSION_10_9_HEX 0x000A0900
+#define MACOS_VERSION_10_10_HEX 0x000A0A00
+#define MACOS_VERSION_10_11_HEX 0x000A0B00
+#define MACOS_VERSION_10_12_HEX 0x000A0C00
+#define MACOS_VERSION_10_13_HEX 0x000A0D00
+#define MACOS_VERSION_10_14_HEX 0x000A0E00
+#define MACOS_VERSION_10_15_HEX 0x000A0F00
+#define MACOS_VERSION_10_16_HEX 0x000A1000
+#define MACOS_VERSION_11_0_HEX 0x000B0000
+
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsDebug.h"
+#include "nsObjCExceptions.h"
+
+#import <Cocoa/Cocoa.h>
+
+int32_t nsCocoaFeatures::mOSVersion = 0;
+
+// This should not be called with unchecked aMajor, which should be >= 10.
+inline int32_t AssembleVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix)
+{
+ MOZ_ASSERT(aMajor >= 10);
+ return (aMajor << 16) + (aMinor << 8) + aBugFix;
+}
+
+int32_t nsCocoaFeatures::ExtractMajorVersion(int32_t aVersion)
+{
+ MOZ_ASSERT((aVersion & MACOS_VERSION_MASK) == aVersion);
+ return (aVersion & 0xFF0000) >> 16;
+}
+
+int32_t nsCocoaFeatures::ExtractMinorVersion(int32_t aVersion)
+{
+ MOZ_ASSERT((aVersion & MACOS_VERSION_MASK) == aVersion);
+ return (aVersion & 0xFF00) >> 8;
+}
+
+int32_t nsCocoaFeatures::ExtractBugFixVersion(int32_t aVersion)
+{
+ MOZ_ASSERT((aVersion & MACOS_VERSION_MASK) == aVersion);
+ return aVersion & 0xFF;
+}
+
+static int intAtStringIndex(NSArray *array, int index)
+{
+ return [(NSString*)[array objectAtIndex:index] integerValue];
+}
+
+void nsCocoaFeatures::GetSystemVersion(int &major, int &minor, int &bugfix)
+{
+ major = minor = bugfix = 0;
+
+ NSString* versionString = [[NSDictionary dictionaryWithContentsOfFile:
+ @"/System/Library/CoreServices/SystemVersion.plist"] objectForKey:@"ProductVersion"];
+ NSArray* versions = [versionString componentsSeparatedByString:@"."];
+ NSUInteger count = [versions count];
+ if (count > 0) {
+ major = intAtStringIndex(versions, 0);
+ if (count > 1) {
+ minor = intAtStringIndex(versions, 1);
+ if (count > 2) {
+ bugfix = intAtStringIndex(versions, 2);
+ }
+ }
+ }
+}
+
+int32_t nsCocoaFeatures::GetVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix)
+{
+ int32_t macOSVersion;
+ if (aMajor < 10) {
+ aMajor = 10;
+ NS_ERROR("Couldn't determine macOS version, assuming 10.7");
+ macOSVersion = MACOS_VERSION_10_7_HEX;
+ } else if (aMajor == 10 && aMinor < 7) {
+ aMinor = 7;
+ NS_ERROR("macOS version too old, assuming 10.7");
+ macOSVersion = MACOS_VERSION_10_7_HEX;
+ } else {
+ MOZ_ASSERT(aMajor >= 10);
+ MOZ_ASSERT(aMajor < 256);
+ MOZ_ASSERT(aMinor >= 0);
+ MOZ_ASSERT(aMinor < 256);
+ MOZ_ASSERT(aBugFix >= 0);
+ MOZ_ASSERT(aBugFix < 256);
+ macOSVersion = AssembleVersion(aMajor, aMinor, aBugFix);
+ }
+ MOZ_ASSERT(aMajor == ExtractMajorVersion(macOSVersion));
+ MOZ_ASSERT(aMinor == ExtractMinorVersion(macOSVersion));
+ MOZ_ASSERT(aBugFix == ExtractBugFixVersion(macOSVersion));
+ return macOSVersion;
+}
+
+/*static*/ void
+nsCocoaFeatures::InitializeVersionNumbers()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Provide an autorelease pool to avoid leaking Cocoa objects,
+ // as this gets called before the main autorelease pool is in place.
+ nsAutoreleasePool localPool;
+
+ int major, minor, bugfix;
+ GetSystemVersion(major, minor, bugfix);
+ mOSVersion = GetVersion(major, minor, bugfix);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+/* static */ int32_t
+nsCocoaFeatures::macOSVersion()
+{
+ // Don't let this be called while we're first setting the value...
+ MOZ_ASSERT((mOSVersion & MACOS_VERSION_MASK) >= 0);
+ if (!mOSVersion) {
+ mOSVersion = -1;
+ InitializeVersionNumbers();
+ }
+ return mOSVersion;
+}
+
+/* static */ int32_t
+nsCocoaFeatures::macOSVersionMajor()
+{
+ return ExtractMajorVersion(macOSVersion());
+}
+
+/* static */ int32_t
+nsCocoaFeatures::macOSVersionMinor()
+{
+ return ExtractMinorVersion(macOSVersion());
+}
+
+/* static */ int32_t
+nsCocoaFeatures::macOSVersionBugFix()
+{
+ return ExtractBugFixVersion(macOSVersion());
+}
+
+/* static */ bool
+nsCocoaFeatures::OnYosemiteOrLater()
+{
+ return (macOSVersion() >= MACOS_VERSION_10_10_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnElCapitanOrLater()
+{
+ return (macOSVersion() >= MACOS_VERSION_10_11_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnSierraOrLater()
+{
+ return (macOSVersion() >= MACOS_VERSION_10_12_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnHighSierraOrLater()
+{
+ return (macOSVersion() >= MACOS_VERSION_10_13_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnMojaveOrLater()
+{
+ return (macOSVersion() >= MACOS_VERSION_10_14_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnCatalinaOrLater()
+{
+ return (macOSVersion() >= MACOS_VERSION_10_15_HEX);
+}
+
+/* static */ bool
+nsCocoaFeatures::OnBigSurOrLater()
+{
+ // Account for the version being 10.16 (which occurs when the
+ // application is linked with an older SDK) or 11.0 on Big Sur.
+ return ((macOSVersion() >= MACOS_VERSION_10_16_HEX) ||
+ (macOSVersion() >= MACOS_VERSION_11_0_HEX));
+}
+
+/* static */ bool
+nsCocoaFeatures::IsAtLeastVersion(int32_t aMajor, int32_t aMinor, int32_t aBugFix)
+{
+ return macOSVersion() >= GetVersion(aMajor, aMinor, aBugFix);
+}
diff --git a/widget/cocoa/nsCocoaUtils.h b/widget/cocoa/nsCocoaUtils.h
new file mode 100644
index 0000000000..139e76b4ad
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -0,0 +1,389 @@
+/* -*- 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/. */
+
+#ifndef nsCocoaUtils_h_
+#define nsCocoaUtils_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsRect.h"
+#include "imgIContainer.h"
+#include "npapi.h"
+#include "nsTArray.h"
+#include "Units.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+#include "mozilla/EventForwards.h"
+
+// Declare the backingScaleFactor method that we want to call
+// on NSView/Window/Screen objects, if they recognize it.
+@interface NSObject (BackingScaleFactorCategory)
+- (CGFloat)backingScaleFactor;
+@end
+
+#if !defined(MAC_OS_X_VERSION_10_8) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
+enum {
+ NSEventPhaseMayBegin = 0x1 << 5
+};
+#endif
+
+class nsIWidget;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+} // namespace mozilla
+
+// Used to retain a Cocoa object for the remainder of a method's execution.
+class nsAutoRetainCocoaObject {
+public:
+explicit nsAutoRetainCocoaObject(id anObject)
+{
+ mObject = NS_OBJC_TRY_EXPR_ABORT([anObject retain]);
+}
+~nsAutoRetainCocoaObject()
+{
+ NS_OBJC_TRY_ABORT([mObject release]);
+}
+private:
+ id mObject; // [STRONG]
+};
+
+// Provide a local autorelease pool for the remainder of a method's execution.
+class nsAutoreleasePool {
+public:
+ nsAutoreleasePool()
+ {
+ mLocalPool = [[NSAutoreleasePool alloc] init];
+ }
+ ~nsAutoreleasePool()
+ {
+ [mLocalPool release];
+ }
+private:
+ NSAutoreleasePool *mLocalPool;
+};
+
+@interface NSApplication (Undocumented)
+
+// Present in all versions of OS X from (at least) 10.2.8 through 10.5.
+- (BOOL)_isRunningModal;
+- (BOOL)_isRunningAppModal;
+
+// It's sometimes necessary to explicitly remove a window from the "window
+// cache" in order to deactivate it. The "window cache" is an undocumented
+// subsystem, all of whose methods are included in the NSWindowCache category
+// of the NSApplication class (in header files generated using class-dump).
+// Present in all versions of OS X from (at least) 10.2.8 through 10.5.
+- (void)_removeWindowFromCache:(NSWindow *)aWindow;
+
+// Send an event to the current Cocoa app-modal session. Present in all
+// versions of OS X from (at least) 10.2.8 through 10.5.
+- (void)_modalSession:(NSModalSession)aSession sendEvent:(NSEvent *)theEvent;
+
+@end
+
+struct KeyBindingsCommand
+{
+ SEL selector;
+ id data;
+};
+
+@interface NativeKeyBindingsRecorder : NSResponder
+{
+@private
+ nsTArray<KeyBindingsCommand>* mCommands;
+}
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands;
+
+- (void)doCommandBySelector:(SEL)aSelector;
+
+- (void)insertText:(id)aString;
+
+@end // NativeKeyBindingsRecorder
+
+class nsCocoaUtils
+{
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+
+public:
+
+ // Get the backing scale factor from an object that supports this selector
+ // (NSView/Window/Screen, on 10.7 or later), returning 1.0 if not supported
+ static CGFloat
+ GetBackingScaleFactor(id aObject)
+ {
+ if (HiDPIEnabled() &&
+ [aObject respondsToSelector:@selector(backingScaleFactor)]) {
+ return [aObject backingScaleFactor];
+ }
+ return 1.0;
+ }
+
+ // Conversions between Cocoa points and device pixels, given the backing
+ // scale factor from a view/window/screen.
+ static int32_t
+ CocoaPointsToDevPixels(CGFloat aPts, CGFloat aBackingScale)
+ {
+ return NSToIntRound(aPts * aBackingScale);
+ }
+
+ static LayoutDeviceIntPoint
+ CocoaPointsToDevPixels(const NSPoint& aPt, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntPoint(NSToIntRound(aPt.x * aBackingScale),
+ NSToIntRound(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntPoint
+ CocoaPointsToDevPixelsRoundDown(const NSPoint& aPt, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntPoint(NSToIntFloor(aPt.x * aBackingScale),
+ NSToIntFloor(aPt.y * aBackingScale));
+ }
+
+ static LayoutDeviceIntRect
+ CocoaPointsToDevPixels(const NSRect& aRect, CGFloat aBackingScale)
+ {
+ return LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * aBackingScale),
+ NSToIntRound(aRect.origin.y * aBackingScale),
+ NSToIntRound(aRect.size.width * aBackingScale),
+ NSToIntRound(aRect.size.height * aBackingScale));
+ }
+
+ static CGFloat
+ DevPixelsToCocoaPoints(int32_t aPixels, CGFloat aBackingScale)
+ {
+ return (CGFloat)aPixels / aBackingScale;
+ }
+
+ static NSPoint
+ DevPixelsToCocoaPoints(const mozilla::LayoutDeviceIntPoint& aPt,
+ CGFloat aBackingScale)
+ {
+ return NSMakePoint((CGFloat)aPt.x / aBackingScale,
+ (CGFloat)aPt.y / aBackingScale);
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectFromScreen:].
+ static NSPoint
+ ConvertPointFromScreen(NSWindow* aWindow, const NSPoint& aPt)
+ {
+ return [aWindow convertRectFromScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ // Implements an NSPoint equivalent of -[NSWindow convertRectToScreen:].
+ static NSPoint
+ ConvertPointToScreen(NSWindow* aWindow, const NSPoint& aPt)
+ {
+ return [aWindow convertRectToScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin;
+ }
+
+ static NSRect
+ DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect,
+ CGFloat aBackingScale)
+ {
+ return NSMakeRect((CGFloat)aRect.x / aBackingScale,
+ (CGFloat)aRect.y / aBackingScale,
+ (CGFloat)aRect.width / aBackingScale,
+ (CGFloat)aRect.height / aBackingScale);
+ }
+
+ // Returns the given y coordinate, which must be in screen coordinates,
+ // flipped from Gecko to Cocoa or Cocoa to Gecko.
+ static float FlippedScreenY(float y);
+
+ // The following functions come in "DevPix" variants that work with
+ // backing-store (device pixel) coordinates, as well as the original
+ // versions that expect coordinates in Cocoa points/CSS pixels.
+ // The difference becomes important in HiDPI display modes, where Cocoa
+ // points and backing-store pixels are no longer 1:1.
+
+ // Gecko rects (nsRect) contain an origin (x,y) in a coordinate
+ // system with (0,0) in the top-left of the primary screen. Cocoa rects
+ // (NSRect) contain an origin (x,y) in a coordinate system with (0,0)
+ // in the bottom-left of the primary screen. Both nsRect and NSRect
+ // contain width/height info, with no difference in their use.
+ // This function does no scaling, so the Gecko coordinates are
+ // expected to be desktop pixels, which are equal to Cocoa points
+ // (by definition).
+ static NSRect GeckoRectToCocoaRect(const mozilla::DesktopIntRect &geckoRect);
+
+ // Converts aGeckoRect in dev pixels to points in Cocoa coordinates
+ static NSRect
+ GeckoRectToCocoaRectDevPix(const mozilla::LayoutDeviceIntRect &aGeckoRect,
+ CGFloat aBackingScale);
+
+ // See explanation for geckoRectToCocoaRect, guess what this does...
+ static mozilla::DesktopIntRect CocoaRectToGeckoRect(const NSRect &cocoaRect);
+
+ static mozilla::LayoutDeviceIntRect CocoaRectToGeckoRectDevPix(
+ const NSRect& aCocoaRect, CGFloat aBackingScale);
+
+ // Gives the location for the event in screen coordinates. Do not call this
+ // unless the window the event was originally targeted at is still alive!
+ // anEvent may be nil -- in that case the current mouse location is returned.
+ static NSPoint ScreenLocationForEvent(NSEvent* anEvent);
+
+ // Determines if an event happened over a window, whether or not the event
+ // is for the window. Does not take window z-order into account.
+ static BOOL IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Events are set up so that their coordinates refer to the window to which they
+ // were originally sent. If we reroute the event somewhere else, we'll have
+ // to get the window coordinates this way. Do not call this unless the window
+ // the event was originally targeted at is still alive!
+ static NSPoint EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow);
+
+ // Compatibility wrappers for the -[NSEvent phase], -[NSEvent momentumPhase],
+ // -[NSEvent hasPreciseScrollingDeltas] and -[NSEvent scrollingDeltaX/Y] APIs
+ // that became availaible starting with the 10.7 SDK.
+ // All of these can be removed once we drop support for 10.6.
+ static NSEventPhase EventPhase(NSEvent* aEvent);
+ static NSEventPhase EventMomentumPhase(NSEvent* aEvent);
+ static BOOL IsMomentumScrollEvent(NSEvent* aEvent);
+ static BOOL HasPreciseScrollingDeltas(NSEvent* aEvent);
+ static void GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY);
+ static BOOL EventHasPhaseInformation(NSEvent* aEvent);
+
+ // Hides the Menu bar and the Dock. Multiple hide/show requests can be nested.
+ static void HideOSChromeOnScreen(bool aShouldHide);
+
+ static nsIWidget* GetHiddenWindowWidget();
+
+ static void PrepareForNativeAppModalDialog();
+ static void CleanUpAfterNativeAppModalDialog();
+
+ // 3 utility functions to go from a frame of imgIContainer to CGImage and then to NSImage
+ // Convert imgIContainer -> CGImageRef, caller owns result
+
+ /** Creates a <code>CGImageRef</code> from a frame contained in an <code>imgIContainer</code>.
+ Copies the pixel data from the indicated frame of the <code>imgIContainer</code> into a new <code>CGImageRef</code>.
+ The caller owns the <code>CGImageRef</code>.
+ @param aFrame the frame to convert
+ @param aResult the resulting CGImageRef
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateCGImageFromSurface(SourceSurface* aSurface,
+ CGImageRef* aResult);
+
+ /** Creates a Cocoa <code>NSImage</code> from a <code>CGImageRef</code>.
+ Copies the pixel data from the <code>CGImageRef</code> into a new <code>NSImage</code>.
+ The caller owns the <code>NSImage</code>.
+ @param aInputImage the image to convert
+ @param aResult the resulting NSImage
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult);
+
+ /** Creates a Cocoa <code>NSImage</code> from a frame of an <code>imgIContainer</code>.
+ Combines the two methods above. The caller owns the <code>NSImage</code>.
+ @param aImage the image to extract a frame from
+ @param aWhichFrame the frame to extract (see imgIContainer FRAME_*)
+ @param aResult the resulting NSImage
+ @param scaleFactor the desired scale factor of the NSImage (2 for a retina display)
+ @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise
+ */
+ static nsresult CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor);
+
+ /**
+ * Returns nsAString for aSrc.
+ */
+ static void GetStringForNSString(const NSString *aSrc, nsAString& aDist);
+
+ /**
+ * Makes NSString instance for aString.
+ */
+ static NSString* ToNSString(const nsAString& aString);
+
+ /**
+ * Returns NSRect for aGeckoRect.
+ * Just copies values between the two types; it does no coordinate-system
+ * conversion, so both rects must have the same coordinate origin/direction.
+ */
+ static void GeckoRectToNSRect(const nsIntRect& aGeckoRect,
+ NSRect& aOutCocoaRect);
+
+ /**
+ * Returns Gecko rect for aCocoaRect.
+ * Just copies values between the two types; it does no coordinate-system
+ * conversion, so both rects must have the same coordinate origin/direction.
+ */
+ static void NSRectToGeckoRect(const NSRect& aCocoaRect,
+ nsIntRect& aOutGeckoRect);
+
+ /**
+ * Makes NSEvent instance for aEventTytpe and aEvent.
+ */
+ static NSEvent* MakeNewCocoaEventWithType(NSEventType aEventType,
+ NSEvent *aEvent);
+
+ /**
+ * Initializes aNPCocoaEvent.
+ */
+ static void InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent);
+
+ /**
+ * Initializes WidgetInputEvent for aNativeEvent or aModifiers.
+ */
+ static void InitInputEvent(mozilla::WidgetInputEvent &aInputEvent,
+ NSEvent* aNativeEvent);
+
+ /**
+ * Converts the native modifiers from aNativeEvent into WidgetMouseEvent
+ * Modifiers. aNativeEvent can be null.
+ */
+ static mozilla::Modifiers ModifiersForEvent(NSEvent* aNativeEvent);
+
+ /**
+ * ConvertToCarbonModifier() returns carbon modifier flags for the cocoa
+ * modifier flags.
+ * NOTE: The result never includes right*Key.
+ */
+ static UInt32 ConvertToCarbonModifier(NSUInteger aCocoaModifier);
+
+ /**
+ * Whether to support HiDPI rendering. For testing purposes, to be removed
+ * once we're comfortable with the HiDPI behavior.
+ */
+ static bool HiDPIEnabled();
+
+ /**
+ * Keys can optionally be bound by system or user key bindings to one or more
+ * commands based on selectors. This collects any such commands in the
+ * provided array.
+ */
+ static void GetCommandsFromKeyEvent(NSEvent* aEvent,
+ nsTArray<KeyBindingsCommand>& aCommands);
+
+ /**
+ * Converts the string name of a Gecko key (like "VK_HOME") to the
+ * corresponding Cocoa Unicode character.
+ */
+ static uint32_t ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName);
+
+ /**
+ * Converts a Gecko key code (like NS_VK_HOME) to the corresponding Cocoa
+ * Unicode character.
+ */
+ static uint32_t ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode);
+
+ /**
+ * Convert string with font attribute to NSMutableAttributedString
+ */
+ static NSMutableAttributedString* GetNSMutableAttributedString(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRanges,
+ const bool aIsVertical,
+ const CGFloat aBackingScaleFactor);
+};
+
+#endif // nsCocoaUtils_h_
diff --git a/widget/cocoa/nsCocoaUtils.mm b/widget/cocoa/nsCocoaUtils.mm
new file mode 100644
index 0000000000..3138245aa7
--- /dev/null
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -0,0 +1,1022 @@
+/* -*- 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 <cmath>
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "gfxUtils.h"
+#include "ImageRegion.h"
+#include "nsCocoaUtils.h"
+#include "nsChildView.h"
+#include "nsMenuBarX.h"
+#include "nsCocoaWindow.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIAppShellService.h"
+#include "nsIXULWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIServiceManager.h"
+#include "nsMenuUtilsX.h"
+#include "nsToolkit.h"
+#include "nsCRT.h"
+#include "SVGImageContext.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+using mozilla::gfx::BackendType;
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::DrawTarget;
+using mozilla::gfx::Factory;
+using mozilla::gfx::SamplingFilter;
+using mozilla::gfx::IntPoint;
+using mozilla::gfx::IntRect;
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SurfaceFormat;
+using mozilla::gfx::SourceSurface;
+using mozilla::image::ImageRegion;
+using std::ceil;
+
+static float
+MenuBarScreenHeight()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSArray* allScreens = [NSScreen screens];
+ if ([allScreens count]) {
+ return [[allScreens objectAtIndex:0] frame].size.height;
+ }
+
+ return 0.0;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0);
+}
+
+float
+nsCocoaUtils::FlippedScreenY(float y)
+{
+ return MenuBarScreenHeight() - y;
+}
+
+NSRect nsCocoaUtils::GeckoRectToCocoaRect(const DesktopIntRect &geckoRect)
+{
+ // We only need to change the Y coordinate by starting with the primary screen
+ // height and subtracting the gecko Y coordinate of the bottom of the rect.
+ return NSMakeRect(geckoRect.x,
+ MenuBarScreenHeight() - geckoRect.YMost(),
+ geckoRect.width,
+ geckoRect.height);
+}
+
+NSRect
+nsCocoaUtils::GeckoRectToCocoaRectDevPix(const LayoutDeviceIntRect &aGeckoRect,
+ CGFloat aBackingScale)
+{
+ return NSMakeRect(aGeckoRect.x / aBackingScale,
+ MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale,
+ aGeckoRect.width / aBackingScale,
+ aGeckoRect.height / aBackingScale);
+}
+
+DesktopIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect)
+{
+ // We only need to change the Y coordinate by starting with the primary screen
+ // height and subtracting both the cocoa y origin and the height of the
+ // cocoa rect.
+ DesktopIntRect rect;
+ rect.x = NSToIntRound(cocoaRect.origin.x);
+ rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height));
+ rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x;
+ rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y;
+ return rect;
+}
+
+LayoutDeviceIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(
+ const NSRect& aCocoaRect, CGFloat aBackingScale)
+{
+ LayoutDeviceIntRect rect;
+ rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale);
+ rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale);
+ rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x;
+ rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y;
+ return rect;
+}
+
+NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Don't trust mouse locations of mouse move events, see bug 443178.
+ if (!anEvent || [anEvent type] == NSMouseMoved)
+ return [NSEvent mouseLocation];
+
+ // Pin momentum scroll events to the location of the last user-controlled
+ // scroll event.
+ if (IsMomentumScrollEvent(anEvent))
+ return ChildViewMouseTracker::sLastScrollEventScreenLocation;
+
+ return nsCocoaUtils::ConvertPointToScreen([anEvent window], [anEvent locationInWindow]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
+}
+
+BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return nsCocoaUtils::ConvertPointFromScreen(aWindow, ScreenLocationForEvent(anEvent));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
+}
+
+@interface NSEvent (ScrollPhase)
+// 10.5 and 10.6
+- (long long)_scrollPhase;
+// 10.7 and above
+- (NSEventPhase)phase;
+- (NSEventPhase)momentumPhase;
+@end
+
+NSEventPhase nsCocoaUtils::EventPhase(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(phase)]) {
+ return [aEvent phase];
+ }
+ return NSEventPhaseNone;
+}
+
+NSEventPhase nsCocoaUtils::EventMomentumPhase(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(momentumPhase)]) {
+ return [aEvent momentumPhase];
+ }
+ if ([aEvent respondsToSelector:@selector(_scrollPhase)]) {
+ switch ([aEvent _scrollPhase]) {
+ case 1: return NSEventPhaseBegan;
+ case 2: return NSEventPhaseChanged;
+ case 3: return NSEventPhaseEnded;
+ default: return NSEventPhaseNone;
+ }
+ }
+ return NSEventPhaseNone;
+}
+
+BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent)
+{
+ return [aEvent type] == NSScrollWheel &&
+ EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+@interface NSEvent (HasPreciseScrollingDeltas)
+// 10.7 and above
+- (BOOL)hasPreciseScrollingDeltas;
+// For 10.6 and below, see the comment in nsChildView.h about _eventRef
+- (EventRef)_eventRef;
+@end
+
+BOOL nsCocoaUtils::HasPreciseScrollingDeltas(NSEvent* aEvent)
+{
+ if ([aEvent respondsToSelector:@selector(hasPreciseScrollingDeltas)]) {
+ return [aEvent hasPreciseScrollingDeltas];
+ }
+
+ // For events that don't contain pixel scrolling information, the event
+ // kind of their underlaying carbon event is kEventMouseWheelMoved instead
+ // of kEventMouseScroll.
+ EventRef carbonEvent = [aEvent _eventRef];
+ return carbonEvent && ::GetEventKind(carbonEvent) == kEventMouseScroll;
+}
+
+@interface NSEvent (ScrollingDeltas)
+// 10.6 and below
+- (CGFloat)deviceDeltaX;
+- (CGFloat)deviceDeltaY;
+// 10.7 and above
+- (CGFloat)scrollingDeltaX;
+- (CGFloat)scrollingDeltaY;
+@end
+
+void nsCocoaUtils::GetScrollingDeltas(NSEvent* aEvent, CGFloat* aOutDeltaX, CGFloat* aOutDeltaY)
+{
+ if ([aEvent respondsToSelector:@selector(scrollingDeltaX)]) {
+ *aOutDeltaX = [aEvent scrollingDeltaX];
+ *aOutDeltaY = [aEvent scrollingDeltaY];
+ return;
+ }
+ if ([aEvent respondsToSelector:@selector(deviceDeltaX)] &&
+ HasPreciseScrollingDeltas(aEvent)) {
+ // Calling deviceDeltaX/Y on those events that do not contain pixel
+ // scrolling information triggers a Cocoa assertion and an
+ // Objective-C NSInternalInconsistencyException.
+ *aOutDeltaX = [aEvent deviceDeltaX];
+ *aOutDeltaY = [aEvent deviceDeltaY];
+ return;
+ }
+
+ // This is only hit pre-10.7 when we are called on a scroll event that does
+ // not contain pixel scrolling information.
+ CGFloat lineDeltaPixels = 12;
+ *aOutDeltaX = [aEvent deltaX] * lineDeltaPixels;
+ *aOutDeltaY = [aEvent deltaY] * lineDeltaPixels;
+}
+
+BOOL nsCocoaUtils::EventHasPhaseInformation(NSEvent* aEvent)
+{
+ if (![aEvent respondsToSelector:@selector(phase)]) {
+ return NO;
+ }
+ return EventPhase(aEvent) != NSEventPhaseNone ||
+ EventMomentumPhase(aEvent) != NSEventPhaseNone;
+}
+
+void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Keep track of how many hiding requests have been made, so that they can
+ // be nested.
+ static int sHiddenCount = 0;
+
+ sHiddenCount += aShouldHide ? 1 : -1;
+ NS_ASSERTION(sHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls");
+
+ NSApplicationPresentationOptions options =
+ sHiddenCount <= 0 ? NSApplicationPresentationDefault :
+ NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
+ [NSApp setPresentationOptions:options];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+nsIWidget* nsCocoaUtils::GetHiddenWindowWidget()
+{
+ nsCOMPtr<nsIAppShellService> appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ if (!appShell) {
+ NS_WARNING("Couldn't get AppShellService in order to get hidden window ref");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIXULWindow> hiddenWindow;
+ appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
+ if (!hiddenWindow) {
+ // Don't warn, this happens during shutdown, bug 358607.
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseHiddenWindow;
+ baseHiddenWindow = do_GetInterface(hiddenWindow);
+ if (!baseHiddenWindow) {
+ NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> hiddenWindowWidget;
+ if (NS_FAILED(baseHiddenWindow->GetMainWidget(getter_AddRefs(hiddenWindowWidget)))) {
+ NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)");
+ return nullptr;
+ }
+
+ return hiddenWindowWidget;
+}
+
+void nsCocoaUtils::PrepareForNativeAppModalDialog()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar)
+ return;
+
+ // First put up the hidden window menu bar so that app menu event handling is correct.
+ hiddenWindowMenuBar->Paint();
+
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ // Create new menu bar for use with modal dialog
+ NSMenu* newMenuBar = [[NSMenu alloc] initWithTitle:@""];
+
+ // Swap in our app menu. Note that the event target is whatever window is up when
+ // the app modal dialog goes up.
+ NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
+ [mainMenu removeItemAtIndex:0];
+ [newMenuBar insertItem:firstMenuItem atIndex:0];
+ [firstMenuItem release];
+
+ // Add standard edit menu
+ [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()];
+
+ // Show the new menu bar
+ [NSApp setMainMenu:newMenuBar];
+ [newMenuBar release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaUtils::CleanUpAfterNativeAppModalDialog()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Don't do anything if this is embedding. We'll assume that if there is no hidden
+ // window we shouldn't do anything, and that should cover the embedding case.
+ nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (!hiddenWindowMenuBar)
+ return;
+
+ NSWindow* mainWindow = [NSApp mainWindow];
+ if (!mainWindow)
+ hiddenWindowMenuBar->Paint();
+ else
+ [WindowDelegate paintMenubarForWindow:mainWindow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void data_ss_release_callback(void *aDataSourceSurface,
+ const void *data,
+ size_t size)
+{
+ if (aDataSourceSurface) {
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap();
+ static_cast<DataSourceSurface*>(aDataSourceSurface)->Release();
+ }
+}
+
+nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface,
+ CGImageRef* aResult)
+{
+ RefPtr<DataSourceSurface> dataSurface;
+
+ if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ dataSurface = aSurface->GetDataSurface();
+ } else {
+ // CGImageCreate only supports 16- and 32-bit bit-depth
+ // Convert format to SurfaceFormat::B8G8R8A8
+ dataSurface = gfxUtils::
+ CopySurfaceToDataSourceSurfaceWithFormat(aSurface,
+ SurfaceFormat::B8G8R8A8);
+ }
+
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ if (height < 1 || width < 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+ // The Unmap() call happens in data_ss_release_callback
+
+ // Create a CGImageRef with the bits from the image, taking into account
+ // the alpha ordering and endianness of the machine so we don't have to
+ // touch the bits ourselves.
+ CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(dataSurface.forget().take(),
+ map.mData,
+ map.mStride * height,
+ data_ss_release_callback);
+ CGColorSpaceRef colorSpace = ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+ *aResult = ::CGImageCreate(width,
+ height,
+ 8,
+ 32,
+ map.mStride,
+ colorSpace,
+ kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst,
+ dataProvider,
+ NULL,
+ 0,
+ kCGRenderingIntentDefault);
+ ::CGColorSpaceRelease(colorSpace);
+ ::CGDataProviderRelease(dataProvider);
+ return *aResult ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage **aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Be very careful when creating the NSImage that the backing NSImageRep is
+ // exactly 1:1 with the input image. On a retina display, both [NSImage
+ // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a
+ // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina
+ // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the
+ // size of the NSImage.
+ //
+ // For example, if a 32x32 SVG cursor is rendered on a retina display, then
+ // aInputImage will be 64x64. The resulting NSImage will be scaled back down
+ // to 32x32 so it stays the correct size on the screen by changing its size
+ // (resizing a NSImage only scales the image and doesn't resample the data).
+ // If aInputImage is converted using [NSImage initWithCGImage:size:] then the
+ // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since
+ // it will expect a 64x64 bitmap.
+
+ int32_t width = ::CGImageGetWidth(aInputImage);
+ int32_t height = ::CGImageGetHeight(aInputImage);
+ NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height);
+
+ NSBitmapImageRep *offscreenRep = [[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bitmapFormat:NSAlphaFirstBitmapFormat
+ bytesPerRow:0
+ bitsPerPixel:0];
+
+ NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:context];
+
+ // Get the Quartz context and draw.
+ CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage);
+
+ [NSGraphicsContext restoreGraphicsState];
+
+ *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
+ [*aResult addRepresentation:offscreenRep];
+ [offscreenRep release];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsCocoaUtils::CreateNSImageFromImageContainer(imgIContainer *aImage, uint32_t aWhichFrame, NSImage **aResult, CGFloat scaleFactor)
+{
+ RefPtr<SourceSurface> surface;
+ int32_t width = 0, height = 0;
+ aImage->GetWidth(&width);
+ aImage->GetHeight(&height);
+
+ // Render a vector image at the correct resolution on a retina display
+ if (aImage->GetType() == imgIContainer::TYPE_VECTOR && scaleFactor != 1.0f) {
+ IntSize scaledSize = IntSize::Ceil(width * scaleFactor, height * scaleFactor);
+
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->
+ CreateOffscreenContentDrawTarget(scaledSize, SurfaceFormat::B8G8R8A8);
+ if (!drawTarget || !drawTarget->IsValid()) {
+ NS_ERROR("Failed to create valid DrawTarget");
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<gfxContext> context = gfxContext::CreateOrNull(drawTarget);
+ MOZ_ASSERT(context);
+
+ mozilla::image::DrawResult res =
+ aImage->Draw(context, scaledSize, ImageRegion::Create(scaledSize),
+ aWhichFrame, SamplingFilter::POINT,
+ /* no SVGImageContext */ Nothing(),
+ imgIContainer::FLAG_SYNC_DECODE);
+
+ if (res != mozilla::image::DrawResult::SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ surface = drawTarget->Snapshot();
+ } else {
+ surface = aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE);
+ }
+
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ CGImageRef imageRef = NULL;
+ nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
+ if (NS_FAILED(rv) || !imageRef) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult);
+ if (NS_FAILED(rv) || !aResult) {
+ return NS_ERROR_FAILURE;
+ }
+ ::CGImageRelease(imageRef);
+
+ // Ensure the image will be rendered the correct size on a retina display
+ NSSize size = NSMakeSize(width, height);
+ [*aResult setSize:size];
+ [[[*aResult representations] objectAtIndex:0] setSize:size];
+ return NS_OK;
+}
+
+// static
+void
+nsCocoaUtils::GetStringForNSString(const NSString *aSrc, nsAString& aDist)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aSrc) {
+ aDist.Truncate();
+ return;
+ }
+
+ aDist.SetLength([aSrc length]);
+ [aSrc getCharacters: reinterpret_cast<unichar*>(aDist.BeginWriting())];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+NSString*
+nsCocoaUtils::ToNSString(const nsAString& aString)
+{
+ if (aString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aString.BeginReading())
+ length:aString.Length()];
+}
+
+// static
+void
+nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect,
+ NSRect& aOutCocoaRect)
+{
+ aOutCocoaRect.origin.x = aGeckoRect.x;
+ aOutCocoaRect.origin.y = aGeckoRect.y;
+ aOutCocoaRect.size.width = aGeckoRect.width;
+ aOutCocoaRect.size.height = aGeckoRect.height;
+}
+
+// static
+void
+nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect,
+ nsIntRect& aOutGeckoRect)
+{
+ aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x);
+ aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y);
+ aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x;
+ aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y;
+}
+
+// static
+NSEvent*
+nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSEvent* newEvent =
+ [NSEvent keyEventWithType:aEventType
+ location:[aEvent locationInWindow]
+ modifierFlags:[aEvent modifierFlags]
+ timestamp:[aEvent timestamp]
+ windowNumber:[aEvent windowNumber]
+ context:[aEvent context]
+ characters:[aEvent characters]
+ charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers]
+ isARepeat:[aEvent isARepeat]
+ keyCode:[aEvent keyCode]];
+ return newEvent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// static
+void
+nsCocoaUtils::InitNPCocoaEvent(NPCocoaEvent* aNPCocoaEvent)
+{
+ memset(aNPCocoaEvent, 0, sizeof(NPCocoaEvent));
+}
+
+// static
+void
+nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent,
+ NSEvent* aNativeEvent)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ aInputEvent.mModifiers = ModifiersForEvent(aNativeEvent);
+ aInputEvent.mTime = PR_IntervalNow();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// static
+Modifiers
+nsCocoaUtils::ModifiersForEvent(NSEvent* aNativeEvent)
+{
+ NSUInteger modifiers =
+ aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags];
+ Modifiers result = 0;
+ if (modifiers & NSShiftKeyMask) {
+ result |= MODIFIER_SHIFT;
+ }
+ if (modifiers & NSControlKeyMask) {
+ result |= MODIFIER_CONTROL;
+ }
+ if (modifiers & NSAlternateKeyMask) {
+ result |= MODIFIER_ALT;
+ // Mac's option key is similar to other platforms' AltGr key.
+ // Let's set AltGr flag when option key is pressed for consistency with
+ // other platforms.
+ result |= MODIFIER_ALTGRAPH;
+ }
+ if (modifiers & NSCommandKeyMask) {
+ result |= MODIFIER_META;
+ }
+
+ if (modifiers & NSAlphaShiftKeyMask) {
+ result |= MODIFIER_CAPSLOCK;
+ }
+ // Mac doesn't have NumLock key. We can assume that NumLock is always locked
+ // if user is using a keyboard which has numpad. Otherwise, if user is using
+ // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can
+ // assume that NumLock is always unlocked.
+ // Unfortunately, we cannot know whether current keyboard has numpad or not.
+ // We should notify locked state only when keys in numpad are pressed.
+ // By this, web applications may not be confused by unexpected numpad key's
+ // key event with unlocked state.
+ if (modifiers & NSNumericPadKeyMask) {
+ result |= MODIFIER_NUMLOCK;
+ }
+
+ // Be aware, NSFunctionKeyMask is included when arrow keys, home key or some
+ // other keys are pressed. We cannot check whether 'fn' key is pressed or
+ // not by the flag.
+
+ return result;
+}
+
+// static
+UInt32
+nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier)
+{
+ UInt32 carbonModifier = 0;
+ if (aCocoaModifier & NSAlphaShiftKeyMask) {
+ carbonModifier |= alphaLock;
+ }
+ if (aCocoaModifier & NSControlKeyMask) {
+ carbonModifier |= controlKey;
+ }
+ if (aCocoaModifier & NSAlternateKeyMask) {
+ carbonModifier |= optionKey;
+ }
+ if (aCocoaModifier & NSShiftKeyMask) {
+ carbonModifier |= shiftKey;
+ }
+ if (aCocoaModifier & NSCommandKeyMask) {
+ carbonModifier |= cmdKey;
+ }
+ if (aCocoaModifier & NSNumericPadKeyMask) {
+ carbonModifier |= kEventKeyModifierNumLockMask;
+ }
+ if (aCocoaModifier & NSFunctionKeyMask) {
+ carbonModifier |= kEventKeyModifierFnMask;
+ }
+ return carbonModifier;
+}
+
+// While HiDPI support is not 100% complete and tested, we'll have a pref
+// to allow it to be turned off in case of problems (or for testing purposes).
+
+// gfx.hidpi.enabled is an integer with the meaning:
+// <= 0 : HiDPI support is disabled
+// 1 : HiDPI enabled provided all screens have the same backing resolution
+// > 1 : HiDPI enabled even if there are a mixture of screen modes
+
+// All the following code is to be removed once HiDPI work is more complete.
+
+static bool sHiDPIEnabled = false;
+static bool sHiDPIPrefInitialized = false;
+
+// static
+bool
+nsCocoaUtils::HiDPIEnabled()
+{
+ if (!sHiDPIPrefInitialized) {
+ sHiDPIPrefInitialized = true;
+
+ int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1);
+ if (prefSetting <= 0) {
+ return false;
+ }
+
+ // prefSetting is at least 1, need to check attached screens...
+
+ int scaleFactors = 0; // used as a bitset to track the screen types found
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ NSDictionary *desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil) {
+ continue;
+ }
+ CGFloat scale =
+ [screen respondsToSelector:@selector(backingScaleFactor)] ?
+ [screen backingScaleFactor] : 1.0;
+ // Currently, we only care about differentiating "1.0" and "2.0",
+ // so we set one of the two low bits to record which.
+ if (scale > 1.0) {
+ scaleFactors |= 2;
+ } else {
+ scaleFactors |= 1;
+ }
+ }
+
+ // Now scaleFactors will be:
+ // 0 if no screens (supporting backingScaleFactor) found
+ // 1 if only lo-DPI screens
+ // 2 if only hi-DPI screens
+ // 3 if both lo- and hi-DPI screens
+ // We'll enable HiDPI support if there's only a single screen type,
+ // OR if the pref setting is explicitly greater than 1.
+ sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1);
+ }
+
+ return sHiDPIEnabled;
+}
+
+void
+nsCocoaUtils::GetCommandsFromKeyEvent(NSEvent* aEvent,
+ nsTArray<KeyBindingsCommand>& aCommands)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aEvent);
+
+ static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder;
+ if (!sNativeKeyBindingsRecorder) {
+ sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new];
+ }
+
+ [sNativeKeyBindingsRecorder startRecording:aCommands];
+
+ // This will trigger 0 - N calls to doCommandBySelector: and insertText:
+ [sNativeKeyBindingsRecorder
+ interpretKeyEvents:[NSArray arrayWithObject:aEvent]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@implementation NativeKeyBindingsRecorder
+
+- (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands
+{
+ mCommands = &aCommands;
+ mCommands->Clear();
+}
+
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ KeyBindingsCommand command = {
+ aSelector,
+ nil
+ };
+
+ mCommands->AppendElement(command);
+}
+
+- (void)insertText:(id)aString
+{
+ KeyBindingsCommand command = {
+ @selector(insertText:),
+ aString
+ };
+
+ mCommands->AppendElement(command);
+}
+
+@end // NativeKeyBindingsRecorder
+
+struct KeyConversionData
+{
+ const char* str;
+ size_t strLength;
+ uint32_t geckoKeyCode;
+ uint32_t charCode;
+};
+
+static const KeyConversionData gKeyConversions[] = {
+
+#define KEYCODE_ENTRY(aStr, aCode) \
+ {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode}
+
+// Some keycodes may have different name in nsIDOMKeyEvent from its key name.
+#define KEYCODE_ENTRY2(aStr, aNSName, aCode) \
+ {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode}
+
+ KEYCODE_ENTRY(VK_CANCEL, 0x001B),
+ KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey),
+ KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter),
+ KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter),
+ KEYCODE_ENTRY(VK_TAB, NSTabCharacter),
+ KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey),
+ KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter),
+ KEYCODE_ENTRY(VK_SHIFT, 0),
+ KEYCODE_ENTRY(VK_CONTROL, 0),
+ KEYCODE_ENTRY(VK_ALT, 0),
+ KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey),
+ KEYCODE_ENTRY(VK_CAPS_LOCK, 0),
+ KEYCODE_ENTRY(VK_ESCAPE, 0),
+ KEYCODE_ENTRY(VK_SPACE, ' '),
+ KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey),
+ KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey),
+ KEYCODE_ENTRY(VK_END, NSEndFunctionKey),
+ KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey),
+ KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey),
+ KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey),
+ KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey),
+ KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey),
+ KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey),
+ KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey),
+ KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey),
+ KEYCODE_ENTRY(VK_0, '0'),
+ KEYCODE_ENTRY(VK_1, '1'),
+ KEYCODE_ENTRY(VK_2, '2'),
+ KEYCODE_ENTRY(VK_3, '3'),
+ KEYCODE_ENTRY(VK_4, '4'),
+ KEYCODE_ENTRY(VK_5, '5'),
+ KEYCODE_ENTRY(VK_6, '6'),
+ KEYCODE_ENTRY(VK_7, '7'),
+ KEYCODE_ENTRY(VK_8, '8'),
+ KEYCODE_ENTRY(VK_9, '9'),
+ KEYCODE_ENTRY(VK_SEMICOLON, ':'),
+ KEYCODE_ENTRY(VK_EQUALS, '='),
+ KEYCODE_ENTRY(VK_A, 'A'),
+ KEYCODE_ENTRY(VK_B, 'B'),
+ KEYCODE_ENTRY(VK_C, 'C'),
+ KEYCODE_ENTRY(VK_D, 'D'),
+ KEYCODE_ENTRY(VK_E, 'E'),
+ KEYCODE_ENTRY(VK_F, 'F'),
+ KEYCODE_ENTRY(VK_G, 'G'),
+ KEYCODE_ENTRY(VK_H, 'H'),
+ KEYCODE_ENTRY(VK_I, 'I'),
+ KEYCODE_ENTRY(VK_J, 'J'),
+ KEYCODE_ENTRY(VK_K, 'K'),
+ KEYCODE_ENTRY(VK_L, 'L'),
+ KEYCODE_ENTRY(VK_M, 'M'),
+ KEYCODE_ENTRY(VK_N, 'N'),
+ KEYCODE_ENTRY(VK_O, 'O'),
+ KEYCODE_ENTRY(VK_P, 'P'),
+ KEYCODE_ENTRY(VK_Q, 'Q'),
+ KEYCODE_ENTRY(VK_R, 'R'),
+ KEYCODE_ENTRY(VK_S, 'S'),
+ KEYCODE_ENTRY(VK_T, 'T'),
+ KEYCODE_ENTRY(VK_U, 'U'),
+ KEYCODE_ENTRY(VK_V, 'V'),
+ KEYCODE_ENTRY(VK_W, 'W'),
+ KEYCODE_ENTRY(VK_X, 'X'),
+ KEYCODE_ENTRY(VK_Y, 'Y'),
+ KEYCODE_ENTRY(VK_Z, 'Z'),
+ KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey),
+ KEYCODE_ENTRY(VK_NUMPAD0, '0'),
+ KEYCODE_ENTRY(VK_NUMPAD1, '1'),
+ KEYCODE_ENTRY(VK_NUMPAD2, '2'),
+ KEYCODE_ENTRY(VK_NUMPAD3, '3'),
+ KEYCODE_ENTRY(VK_NUMPAD4, '4'),
+ KEYCODE_ENTRY(VK_NUMPAD5, '5'),
+ KEYCODE_ENTRY(VK_NUMPAD6, '6'),
+ KEYCODE_ENTRY(VK_NUMPAD7, '7'),
+ KEYCODE_ENTRY(VK_NUMPAD8, '8'),
+ KEYCODE_ENTRY(VK_NUMPAD9, '9'),
+ KEYCODE_ENTRY(VK_MULTIPLY, '*'),
+ KEYCODE_ENTRY(VK_ADD, '+'),
+ KEYCODE_ENTRY(VK_SEPARATOR, 0),
+ KEYCODE_ENTRY(VK_SUBTRACT, '-'),
+ KEYCODE_ENTRY(VK_DECIMAL, '.'),
+ KEYCODE_ENTRY(VK_DIVIDE, '/'),
+ KEYCODE_ENTRY(VK_F1, NSF1FunctionKey),
+ KEYCODE_ENTRY(VK_F2, NSF2FunctionKey),
+ KEYCODE_ENTRY(VK_F3, NSF3FunctionKey),
+ KEYCODE_ENTRY(VK_F4, NSF4FunctionKey),
+ KEYCODE_ENTRY(VK_F5, NSF5FunctionKey),
+ KEYCODE_ENTRY(VK_F6, NSF6FunctionKey),
+ KEYCODE_ENTRY(VK_F7, NSF7FunctionKey),
+ KEYCODE_ENTRY(VK_F8, NSF8FunctionKey),
+ KEYCODE_ENTRY(VK_F9, NSF9FunctionKey),
+ KEYCODE_ENTRY(VK_F10, NSF10FunctionKey),
+ KEYCODE_ENTRY(VK_F11, NSF11FunctionKey),
+ KEYCODE_ENTRY(VK_F12, NSF12FunctionKey),
+ KEYCODE_ENTRY(VK_F13, NSF13FunctionKey),
+ KEYCODE_ENTRY(VK_F14, NSF14FunctionKey),
+ KEYCODE_ENTRY(VK_F15, NSF15FunctionKey),
+ KEYCODE_ENTRY(VK_F16, NSF16FunctionKey),
+ KEYCODE_ENTRY(VK_F17, NSF17FunctionKey),
+ KEYCODE_ENTRY(VK_F18, NSF18FunctionKey),
+ KEYCODE_ENTRY(VK_F19, NSF19FunctionKey),
+ KEYCODE_ENTRY(VK_F20, NSF20FunctionKey),
+ KEYCODE_ENTRY(VK_F21, NSF21FunctionKey),
+ KEYCODE_ENTRY(VK_F22, NSF22FunctionKey),
+ KEYCODE_ENTRY(VK_F23, NSF23FunctionKey),
+ KEYCODE_ENTRY(VK_F24, NSF24FunctionKey),
+ KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey),
+ KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey),
+ KEYCODE_ENTRY(VK_COMMA, ','),
+ KEYCODE_ENTRY(VK_PERIOD, '.'),
+ KEYCODE_ENTRY(VK_SLASH, '/'),
+ KEYCODE_ENTRY(VK_BACK_QUOTE, '`'),
+ KEYCODE_ENTRY(VK_OPEN_BRACKET, '['),
+ KEYCODE_ENTRY(VK_BACK_SLASH, '\\'),
+ KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'),
+ KEYCODE_ENTRY(VK_QUOTE, '\'')
+
+#undef KEYCODE_ENTRY
+
+};
+
+uint32_t
+nsCocoaUtils::ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName)
+{
+ if (aKeyCodeName.IsEmpty()) {
+ return 0;
+ }
+
+ nsAutoCString keyCodeName;
+ keyCodeName.AssignWithConversion(aKeyCodeName);
+ // We want case-insensitive comparison with data stored as uppercase.
+ ToUpperCase(keyCodeName);
+
+ uint32_t keyCodeNameLength = keyCodeName.Length();
+ const char* keyCodeNameStr = keyCodeName.get();
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (keyCodeNameLength == gKeyConversions[i].strLength &&
+ nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+uint32_t
+nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode)
+{
+ if (!aKeyCode) {
+ return 0;
+ }
+
+ for (uint16_t i = 0; i < ArrayLength(gKeyConversions); ++i) {
+ if (gKeyConversions[i].geckoKeyCode == aKeyCode) {
+ return gKeyConversions[i].charCode;
+ }
+ }
+
+ return 0;
+}
+
+NSMutableAttributedString*
+nsCocoaUtils::GetNSMutableAttributedString(
+ const nsAString& aText,
+ const nsTArray<mozilla::FontRange>& aFontRanges,
+ const bool aIsVertical,
+ const CGFloat aBackingScaleFactor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL
+
+ NSString* nsstr = nsCocoaUtils::ToNSString(aText);
+ NSMutableAttributedString* attrStr =
+ [[[NSMutableAttributedString alloc] initWithString:nsstr
+ attributes:nil] autorelease];
+
+ int32_t lastOffset = aText.Length();
+ for (auto i = aFontRanges.Length(); i > 0; --i) {
+ const FontRange& fontRange = aFontRanges[i - 1];
+ NSString* fontName = nsCocoaUtils::ToNSString(fontRange.mFontName);
+ CGFloat fontSize = fontRange.mFontSize / aBackingScaleFactor;
+ NSFont* font = [NSFont fontWithName:fontName size:fontSize];
+ if (!font) {
+ font = [NSFont systemFontOfSize:fontSize];
+ }
+
+ NSDictionary* attrs = @{ NSFontAttributeName: font };
+ NSRange range = NSMakeRange(fontRange.mStartOffset,
+ lastOffset - fontRange.mStartOffset);
+ [attrStr setAttributes:attrs range:range];
+ lastOffset = fontRange.mStartOffset;
+ }
+
+ if (aIsVertical) {
+ [attrStr addAttribute:NSVerticalGlyphFormAttributeName
+ value:[NSNumber numberWithInt: 1]
+ range:NSMakeRange(0, [attrStr length])];
+ }
+
+ return attrStr;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL
+}
diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
new file mode 100644
index 0000000000..1913696b8c
--- /dev/null
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -0,0 +1,426 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#ifndef nsCocoaWindow_h_
+#define nsCocoaWindow_h_
+
+#undef DARWIN
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseWidget.h"
+#include "nsPIWidgetCocoa.h"
+#include "nsCocoaUtils.h"
+
+class nsCocoaWindow;
+class nsChildView;
+class nsMenuBarX;
+@class ChildView;
+
+typedef struct _nsCocoaWindowList {
+ _nsCocoaWindowList() : prev(nullptr), window(nullptr) {}
+ struct _nsCocoaWindowList *prev;
+ nsCocoaWindow *window; // Weak
+} nsCocoaWindowList;
+
+// NSWindow subclass that is the base class for all of our own window classes.
+// Among other things, this class handles the storage of those settings that
+// need to be persisted across window destruction and reconstruction, i.e. when
+// switching to and from fullscreen mode.
+// We don't save shadow, transparency mode or background color because it's not
+// worth the hassle - Gecko will reset them anyway as soon as the window is
+// resized.
+@interface BaseWindow : NSWindow
+{
+ // Data Storage
+ NSMutableDictionary* mState;
+ BOOL mDrawsIntoWindowFrame;
+ NSColor* mActiveTitlebarColor;
+ NSColor* mInactiveTitlebarColor;
+
+ // Shadow
+ BOOL mScheduledShadowInvalidation;
+
+ // Invalidation disabling
+ BOOL mDisabledNeedsDisplay;
+
+ // DPI cache. Getting the physical screen size (CGDisplayScreenSize)
+ // is ridiculously slow, so we cache it in the toplevel window for all
+ // descendants to use.
+ float mDPI;
+
+ NSTrackingArea* mTrackingArea;
+
+ NSRect mDirtyRect;
+
+ BOOL mBeingShown;
+ BOOL mDrawTitle;
+ BOOL mBrightTitlebarForeground;
+ BOOL mUseMenuStyle;
+}
+
+- (void)importState:(NSDictionary*)aState;
+- (NSMutableDictionary*)exportState;
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
+- (BOOL)drawsContentsIntoWindowFrame;
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
+- (NSColor*)titlebarColorForActiveWindow:(BOOL)aActive;
+
+- (void)deferredInvalidateShadow;
+- (void)invalidateShadow;
+- (float)getDPI;
+
+- (void)mouseEntered:(NSEvent*)aEvent;
+- (void)mouseExited:(NSEvent*)aEvent;
+- (void)mouseMoved:(NSEvent*)aEvent;
+- (void)updateTrackingArea;
+- (NSView*)trackingAreaView;
+
+- (void)setBeingShown:(BOOL)aValue;
+- (BOOL)isBeingShown;
+- (BOOL)isVisibleOrBeingShown;
+
+- (ChildView*)mainChildView;
+
+- (NSArray*)titlebarControls;
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle;
+- (BOOL)wantsTitleDrawn;
+
+- (void)setUseBrightTitlebarForeground:(BOOL)aBrightForeground;
+- (BOOL)useBrightTitlebarForeground;
+
+- (void)disableSetNeedsDisplay;
+- (void)enableSetNeedsDisplay;
+
+- (NSRect)getAndResetNativeDirtyRect;
+
+- (void)setUseMenuStyle:(BOOL)aValue;
+
+@end
+
+@interface NSWindow (Undocumented)
+
+// If a window has been explicitly removed from the "window cache" (to
+// deactivate it), it's sometimes necessary to "reset" it to reactivate it
+// (and put it back in the "window cache"). One way to do this, which Apple
+// often uses, is to set the "window number" to '-1' and then back to its
+// original value.
+- (void)_setWindowNumber:(NSInteger)aNumber;
+
+// If we set the window's stylemask to be textured, the corners on the bottom of
+// the window are rounded by default. We use this private method to make
+// the corners square again, a la Safari. Starting with 10.7, all windows have
+// rounded bottom corners, so this call doesn't have any effect there.
+- (void)setBottomCornerRounded:(BOOL)rounded;
+- (BOOL)bottomCornerRounded;
+
+// Present in the same form on OS X since at least OS X 10.5.
+- (NSRect)contentRectForFrameRect:(NSRect)windowFrame styleMask:(NSUInteger)windowStyle;
+- (NSRect)frameRectForContentRect:(NSRect)windowContentRect styleMask:(NSUInteger)windowStyle;
+
+// Present since at least OS X 10.5. The OS calls this method on NSWindow
+// (and its subclasses) to find out which NSFrameView subclass to instantiate
+// to create its "frame view".
++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask;
+
+@end
+
+@interface PopupWindow : BaseWindow
+{
+@private
+ BOOL mIsContextMenu;
+}
+
+- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
+ backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation;
+- (BOOL)isContextMenu;
+- (void)setIsContextMenu:(BOOL)flag;
+- (BOOL)canBecomeMainWindow;
+
+@end
+
+@interface BorderlessWindow : BaseWindow
+{
+}
+
+- (BOOL)canBecomeKeyWindow;
+- (BOOL)canBecomeMainWindow;
+
+@end
+
+@interface WindowDelegate : NSObject <NSWindowDelegate>
+{
+ nsCocoaWindow* mGeckoWindow; // [WEAK] (we are owned by the window)
+ // Used to avoid duplication when we send NS_ACTIVATE and
+ // NS_DEACTIVATE to Gecko for toplevel widgets. Starts out
+ // false.
+ bool mToplevelActiveState;
+ BOOL mHasEverBeenZoomed;
+}
++ (void)paintMenubarForWindow:(NSWindow*)aWindow;
+- (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind;
+- (void)windowDidResize:(NSNotification*)aNotification;
+- (nsCocoaWindow*)geckoWidget;
+- (bool)toplevelActiveState;
+- (void)sendToplevelActivateEvents;
+- (void)sendToplevelDeactivateEvents;
+@end
+
+@class ToolbarWindow;
+
+// NSColor subclass that allows us to draw separate colors both in the titlebar
+// and for background of the window.
+@interface TitlebarAndBackgroundColor : NSColor
+{
+ ToolbarWindow *mWindow; // [WEAK] (we are owned by the window)
+}
+
+- (id)initWithWindow:(ToolbarWindow*)aWindow;
+
+@end
+
+// NSWindow subclass for handling windows with toolbars.
+@interface ToolbarWindow : BaseWindow
+{
+ TitlebarAndBackgroundColor *mColor; // strong
+ CGFloat mUnifiedToolbarHeight;
+ NSColor *mBackgroundColor; // strong
+ NSView *mTitlebarView; // strong
+ NSRect mWindowButtonsRect;
+ NSRect mFullScreenButtonRect;
+}
+// Pass nil here to get the default appearance.
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
+- (void)setUnifiedToolbarHeight:(CGFloat)aHeight;
+- (CGFloat)unifiedToolbarHeight;
+- (CGFloat)titlebarHeight;
+- (NSRect)titlebarRect;
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync;
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect;
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
+- (void)setSheetAttachmentPosition:(CGFloat)aY;
+- (void)placeWindowButtons:(NSRect)aRect;
+- (void)placeFullScreenButton:(NSRect)aRect;
+- (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
+- (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition;
+- (void)setTemporaryBackgroundColor;
+- (void)restoreBackgroundColor;
+@end
+
+class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa
+{
+private:
+ typedef nsBaseWidget Inherited;
+
+public:
+
+ nsCocoaWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSPIWIDGETCOCOA
+
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+
+ virtual void Destroy() override;
+
+ NS_IMETHOD Show(bool aState) override;
+ virtual nsIWidget* GetSheetWindowParent(void) override;
+ NS_IMETHOD Enable(bool aState) override;
+ virtual bool IsEnabled() const override;
+ virtual void SetModal(bool aState) override;
+ virtual void SetFakeModal(bool aState) override;
+ virtual bool IsRunningAppModal() override;
+ virtual bool IsVisible() const override;
+ NS_IMETHOD SetFocus(bool aState=false) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ virtual LayoutDeviceIntSize
+ ClientToWindowSize(const LayoutDeviceIntSize& aClientSize) override;
+
+ virtual void* GetNativeData(uint32_t aDataType) override;
+
+ virtual void ConstrainPosition(bool aAllowSlop,
+ int32_t *aX, int32_t *aY) override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ NS_IMETHOD Move(double aX, double aY) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ NS_IMETHOD HideWindowChrome(bool aShouldHide) override;
+
+ void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true);
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual nsresult MakeFullScreen(
+ bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override final;
+ NS_IMETHOD MakeFullScreenWithNativeTransition(
+ bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override final;
+ NSAnimation* FullscreenTransitionAnimation() const { return mFullscreenTransitionAnimation; }
+ void ReleaseFullscreenTransitionAnimation()
+ {
+ MOZ_ASSERT(mFullscreenTransitionAnimation,
+ "Should only be called when there is animation");
+ [mFullscreenTransitionAnimation release];
+ mFullscreenTransitionAnimation = nil;
+ }
+
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+ NS_IMETHOD SetCursor(nsCursor aCursor) override;
+ NS_IMETHOD SetCursor(imgIContainer* aCursor, uint32_t aHotspotX, uint32_t aHotspotY) override;
+
+ CGFloat BackingScaleFactor();
+ void BackingScaleFactorChanged();
+ virtual double GetDefaultScaleInternal() override;
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() final {
+ return mozilla::DesktopToLayoutDeviceScale(BackingScaleFactor());
+ }
+
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override;
+
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+ virtual void CaptureRollupEvents(nsIRollupListener * aListener,
+ bool aDoCapture) override;
+ NS_IMETHOD GetAttention(int32_t aCycleCount) override;
+ virtual bool HasPendingInputEvent() override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual void SetWindowShadowStyle(int32_t aStyle) override;
+ virtual void SetShowsToolbarButton(bool aShow) override;
+ virtual void SetShowsFullScreenButton(bool aShow) override;
+ virtual void SetWindowAnimationType(WindowAnimationType aType) override;
+ virtual void SetDrawsTitle(bool aDrawTitle) override;
+ virtual void SetUseBrightTitlebarForeground(bool aBrightForeground) override;
+ NS_IMETHOD SetNonClientMargins(LayoutDeviceIntMargin& aMargins) override;
+ virtual void SetWindowTitlebarColor(nscolor aColor, bool aActive) override;
+ virtual void SetDrawsInTitlebar(bool aState) override;
+ virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) override;
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ void DispatchSizeModeEvent();
+
+ // be notified that a some form of drag event needs to go into Gecko
+ virtual bool DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal, UInt16 aKeyModifiers);
+
+ bool HasModalDescendents() { return mNumModalDescendents > 0; }
+ NSWindow *GetCocoaWindow() { return mWindow; }
+
+ void SetMenuBar(nsMenuBarX* aMenuBar);
+ nsMenuBarX *GetMenuBar();
+
+ NS_IMETHOD_(void) SetInputContext(
+ const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ NS_IMETHOD_(InputContext) GetInputContext() override
+ {
+ return mInputContext;
+ }
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+
+ void SetPopupWindowLevel();
+
+protected:
+ virtual ~nsCocoaWindow();
+
+ nsresult CreateNativeWindow(const NSRect &aRect,
+ nsBorderStyle aBorderStyle,
+ bool aRectIsFrameRect);
+ nsresult CreatePopupContentView(const LayoutDeviceIntRect &aRect,
+ nsWidgetInitData* aInitData);
+ void DestroyNativeWindow();
+ void AdjustWindowShadow();
+ void SetWindowBackgroundBlur();
+ void UpdateBounds();
+
+ nsresult DoResize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint, bool aConstrainToCurrentScreen);
+
+ inline bool ShouldToggleNativeFullscreen(bool aFullScreen,
+ bool aUseSystemTransition);
+ nsresult DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition);
+
+ virtual already_AddRefed<nsIWidget>
+ AllocateChildPopupWidget() override
+ {
+ static NS_DEFINE_IID(kCPopUpCID, NS_POPUP_CID);
+ nsCOMPtr<nsIWidget> widget = do_CreateInstance(kCPopUpCID);
+ return widget.forget();
+ }
+
+ nsIWidget* mParent; // if we're a popup, this is our parent [WEAK]
+ nsIWidget* mAncestorLink; // link to traverse ancestors [WEAK]
+ BaseWindow* mWindow; // our cocoa window [STRONG]
+ WindowDelegate* mDelegate; // our delegate for processing window msgs [STRONG]
+ RefPtr<nsMenuBarX> mMenuBar;
+ NSWindow* mSheetWindowParent; // if this is a sheet, this is the NSWindow it's attached to
+ nsChildView* mPopupContentView; // if this is a popup, this is its content widget
+ // if this is a toplevel window, and there is any ongoing fullscreen
+ // transition, it is the animation object.
+ NSAnimation* mFullscreenTransitionAnimation;
+ int32_t mShadowStyle;
+
+ CGFloat mBackingScaleFactor;
+
+ WindowAnimationType mAnimationType;
+
+ bool mWindowMadeHere; // true if we created the window, false for embedding
+ bool mSheetNeedsShow; // if this is a sheet, are we waiting to be shown?
+ // this is used for sibling sheet contention only
+ bool mInFullScreenMode;
+ bool mInFullScreenTransition; // true from the request to enter/exit fullscreen
+ // (MakeFullScreen() call) to EnteredFullScreen()
+ bool mModal;
+ bool mFakeModal;
+
+ // Only true on 10.7+ if SetShowsFullScreenButton(true) is called.
+ bool mSupportsNativeFullScreen;
+ // Whether we are currently using native fullscreen. It could be false because
+ // we are in the DOM fullscreen where we do not use the native fullscreen.
+ bool mInNativeFullScreenMode;
+
+ bool mIsAnimationSuppressed;
+
+ bool mInReportMoveEvent; // true if in a call to ReportMoveEvent().
+ bool mInResize; // true if in a call to DoResize().
+
+ bool mAlwaysOnTop;
+
+ int32_t mNumModalDescendents;
+ InputContext mInputContext;
+};
+
+#endif // nsCocoaWindow_h_
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
new file mode 100644
index 0000000000..a437504fd1
--- /dev/null
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -0,0 +1,3881 @@
+/* -*- Mode: Objective-C++; tab-width: 2; 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 "nsCocoaWindow.h"
+
+#include "NativeKeyBindings.h"
+#include "TextInputHandler.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsWidgetsCID.h"
+#include "nsIRollupListener.h"
+#include "nsChildView.h"
+#include "nsWindowMap.h"
+#include "nsAppShell.h"
+#include "nsIAppShellService.h"
+#include "nsIBaseWindow.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIXULWindow.h"
+#include "nsToolkit.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDOMElement.h"
+#include "nsThreadUtils.h"
+#include "nsMenuBarX.h"
+#include "nsMenuUtilsX.h"
+#include "nsStyleConsts.h"
+#include "nsNativeThemeColors.h"
+#include "nsNativeThemeCocoa.h"
+#include "nsChildView.h"
+#include "nsCocoaFeatures.h"
+#include "nsIScreenManager.h"
+#include "nsIWidgetListener.h"
+#include "nsIPresShell.h"
+#include "nsScreenCocoa.h"
+#include "VibrancyManager.h"
+
+#include "gfxPlatform.h"
+#include "qcms.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Preferences.h"
+#include <algorithm>
+
+namespace mozilla {
+namespace layers {
+class LayerManager;
+} // namespace layers
+} // namespace mozilla
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+using namespace mozilla;
+
+int32_t gXULModalLevel = 0;
+
+// In principle there should be only one app-modal window at any given time.
+// But sometimes, despite our best efforts, another window appears above the
+// current app-modal window. So we need to keep a linked list of app-modal
+// windows. (A non-sheet window that appears above an app-modal window is
+// also made app-modal.) See nsCocoaWindow::SetModal().
+nsCocoaWindowList *gGeckoAppModalWindowList = NULL;
+
+// defined in nsMenuBarX.mm
+extern NSMenu* sApplicationMenu; // Application menu shared by all menubars
+
+// defined in nsChildView.mm
+extern BOOL gSomeMenuBarPainted;
+
+#if !defined(MAC_OS_X_VERSION_10_12) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
+
+@interface NSWindow(AutomaticWindowTabbing)
++ (void)setAllowsAutomaticWindowTabbing:(BOOL)allow;
+@end
+
+#endif
+
+extern "C" {
+ // CGSPrivate.h
+ typedef NSInteger CGSConnection;
+ typedef NSInteger CGSWindow;
+ typedef NSUInteger CGSWindowFilterRef;
+ extern CGSConnection _CGSDefaultConnection(void);
+ extern CGError CGSSetWindowShadowAndRimParameters(const CGSConnection cid, CGSWindow wid, float standardDeviation, float density, int offsetX, int offsetY, unsigned int flags);
+ extern CGError CGSSetWindowBackgroundBlurRadius(CGSConnection cid, CGSWindow wid, NSUInteger blur);
+}
+
+#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsCocoaWindow, Inherited, nsPIWidgetCocoa)
+
+// A note on testing to see if your object is a sheet...
+// |mWindowType == eWindowType_sheet| is true if your gecko nsIWidget is a sheet
+// widget - whether or not the sheet is showing. |[mWindow isSheet]| will return
+// true *only when the sheet is actually showing*. Choose your test wisely.
+
+static void RollUpPopups()
+{
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE_VOID(rollupListener);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (!rollupWidget)
+ return;
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+}
+
+nsCocoaWindow::nsCocoaWindow()
+: mParent(nullptr)
+, mAncestorLink(nullptr)
+, mWindow(nil)
+, mDelegate(nil)
+, mSheetWindowParent(nil)
+, mPopupContentView(nil)
+, mFullscreenTransitionAnimation(nil)
+, mShadowStyle(NS_STYLE_WINDOW_SHADOW_DEFAULT)
+, mBackingScaleFactor(0.0)
+, mAnimationType(nsIWidget::eGenericWindowAnimation)
+, mWindowMadeHere(false)
+, mSheetNeedsShow(false)
+, mInFullScreenMode(false)
+, mInFullScreenTransition(false)
+, mModal(false)
+, mFakeModal(false)
+, mSupportsNativeFullScreen(false)
+, mInNativeFullScreenMode(false)
+, mIsAnimationSuppressed(false)
+, mInReportMoveEvent(false)
+, mInResize(false)
+, mAlwaysOnTop(false)
+, mNumModalDescendents(0)
+{
+ if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)]) {
+ // Disable automatic tabbing on 10.12. We need to do this before we
+ // orderFront any of our windows.
+ [NSWindow setAllowsAutomaticWindowTabbing:NO];
+ }
+}
+
+void nsCocoaWindow::DestroyNativeWindow()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // We want to unhook the delegate here because we don't want events
+ // sent to it after this object has been destroyed.
+ [mWindow setDelegate:nil];
+ [mWindow close];
+ mWindow = nil;
+ [mDelegate autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsCocoaWindow::~nsCocoaWindow()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Notify the children that we're gone. Popup windows (e.g. tooltips) can
+ // have nsChildView children. 'kid' is an nsChildView object if and only if
+ // its 'type' is 'eWindowType_child'.
+ // childView->ResetParent() can change our list of children while it's
+ // being iterated, so the way we iterate the list must allow for this.
+ for (nsIWidget* kid = mLastChild; kid;) {
+ nsWindowType kidType = kid->WindowType();
+ if (kidType == eWindowType_child) {
+ nsChildView* childView = static_cast<nsChildView*>(kid);
+ kid = kid->GetPrevSibling();
+ childView->ResetParent();
+ } else {
+ nsCocoaWindow* childWindow = static_cast<nsCocoaWindow*>(kid);
+ childWindow->mParent = nullptr;
+ childWindow->mAncestorLink = mAncestorLink;
+ kid = kid->GetPrevSibling();
+ }
+ }
+
+ if (mWindow && mWindowMadeHere) {
+ DestroyNativeWindow();
+ }
+
+ NS_IF_RELEASE(mPopupContentView);
+
+ // Deal with the possiblity that we're being destroyed while running modal.
+ if (mModal) {
+ NS_WARNING("Widget destroyed while running modal!");
+ --gXULModalLevel;
+ NS_ASSERTION(gXULModalLevel >= 0, "Weirdness setting modality!");
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Find the screen that overlaps aRect the most,
+// if none are found default to the mainScreen.
+static NSScreen*
+FindTargetScreenForRect(const DesktopIntRect& aRect)
+{
+ NSScreen *targetScreen = [NSScreen mainScreen];
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ int largestIntersectArea = 0;
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ DesktopIntRect screenRect =
+ nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]);
+ screenRect = screenRect.Intersect(aRect);
+ int area = screenRect.width * screenRect.height;
+ if (area > largestIntersectArea) {
+ largestIntersectArea = area;
+ targetScreen = screen;
+ }
+ }
+ return targetScreen;
+}
+
+// fits the rect to the screen that contains the largest area of it,
+// or to aScreen if a screen is passed in
+// NB: this operates with aRect in desktop pixels
+static void
+FitRectToVisibleAreaForScreen(DesktopIntRect& aRect, NSScreen* aScreen)
+{
+ if (!aScreen) {
+ aScreen = FindTargetScreenForRect(aRect);
+ }
+
+ DesktopIntRect screenBounds =
+ nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]);
+
+ if (aRect.width > screenBounds.width) {
+ aRect.width = screenBounds.width;
+ }
+ if (aRect.height > screenBounds.height) {
+ aRect.height = screenBounds.height;
+ }
+
+ if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
+ aRect.x += screenBounds.width - (aRect.x - screenBounds.x + aRect.width);
+ }
+ if (aRect.y - screenBounds.y + aRect.height > screenBounds.height) {
+ aRect.y += screenBounds.height - (aRect.y - screenBounds.y + aRect.height);
+ }
+
+ // If the left/top edge of the window is off the screen in either direction,
+ // then set the window to start at the left/top edge of the screen.
+ if (aRect.x < screenBounds.x || aRect.x > (screenBounds.x + screenBounds.width)) {
+ aRect.x = screenBounds.x;
+ }
+ if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) {
+ aRect.y = screenBounds.y;
+ }
+}
+
+// Some applications use native popup windows
+// (native context menus, native tooltips)
+static bool UseNativePopupWindows()
+{
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return true;
+#else
+ return false;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+}
+
+// aRect here is specified in desktop pixels
+nsresult
+nsCocoaWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const DesktopIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Because the hidden window is created outside of an event loop,
+ // we have to provide an autorelease pool (see bug 559075).
+ nsAutoreleasePool localPool;
+
+ DesktopIntRect newBounds = aRect;
+ FitRectToVisibleAreaForScreen(newBounds, nullptr);
+
+ // Set defaults which can be overriden from aInitData in BaseCreate
+ mWindowType = eWindowType_toplevel;
+ mBorderStyle = eBorderStyle_default;
+
+ // Ensure that the toolkit is created.
+ nsToolkit::GetToolkit();
+
+ Inherited::BaseCreate(aParent, aInitData);
+
+ mParent = aParent;
+ mAncestorLink = aParent;
+
+ // Applications that use native popups don't want us to create popup windows.
+ if ((mWindowType == eWindowType_popup) && UseNativePopupWindows())
+ return NS_OK;
+
+ nsresult rv =
+ CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(newBounds),
+ mBorderStyle, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mWindowType == eWindowType_popup) {
+ if (aInitData->mMouseTransparent) {
+ [mWindow setIgnoresMouseEvents:YES];
+ } else {
+ [mWindow setIgnoresMouseEvents:NO];
+ }
+ // now we can convert newBounds to device pixels for the window we created,
+ // as the child view expects a rect expressed in the dev pix of its parent
+ LayoutDeviceIntRect devRect =
+ RoundedToInt(newBounds * GetDesktopToDeviceScale());
+ return CreatePopupContentView(devRect, aInitData);
+ }
+
+ mIsAnimationSuppressed = aInitData->mIsAnimationSuppressed;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsCocoaWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ DesktopIntRect desktopRect =
+ RoundedToInt(aRect / GetDesktopToDeviceScale());
+ return Create(aParent, aNativeParent, desktopRect, aInitData);
+}
+
+static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle)
+{
+ bool allOrDefault = (aBorderStyle == eBorderStyle_all ||
+ aBorderStyle == eBorderStyle_default);
+
+ /* Apple's docs on NSWindow styles say that "a window's style mask should
+ * include NSTitledWindowMask if it includes any of the others [besides
+ * NSBorderlessWindowMask]". This implies that a borderless window
+ * shouldn't have any other styles than NSBorderlessWindowMask.
+ */
+ if (!allOrDefault && !(aBorderStyle & eBorderStyle_title))
+ return NSBorderlessWindowMask;
+
+ unsigned int mask = NSTitledWindowMask;
+ if (allOrDefault || aBorderStyle & eBorderStyle_close)
+ mask |= NSClosableWindowMask;
+ if (allOrDefault || aBorderStyle & eBorderStyle_minimize)
+ mask |= NSMiniaturizableWindowMask;
+ if (allOrDefault || aBorderStyle & eBorderStyle_resizeh)
+ mask |= NSResizableWindowMask;
+
+ return mask;
+}
+
+// If aRectIsFrameRect, aRect specifies the frame rect of the new window.
+// Otherwise, aRect.x/y specify the position of the window's frame relative to
+// the bottom of the menubar and aRect.width/height specify the size of the
+// content rect.
+nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
+ nsBorderStyle aBorderStyle,
+ bool aRectIsFrameRect)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // We default to NSBorderlessWindowMask, add features if needed.
+ unsigned int features = NSBorderlessWindowMask;
+
+ // Configure the window we will create based on the window type.
+ switch (mWindowType)
+ {
+ case eWindowType_invisible:
+ case eWindowType_child:
+ case eWindowType_plugin:
+ break;
+ case eWindowType_popup:
+ if (aBorderStyle != eBorderStyle_default && mBorderStyle & eBorderStyle_title) {
+ features |= NSTitledWindowMask;
+ if (aBorderStyle & eBorderStyle_close) {
+ features |= NSClosableWindowMask;
+ }
+ }
+ break;
+ case eWindowType_toplevel:
+ case eWindowType_dialog:
+ features = WindowMaskForBorderStyle(aBorderStyle);
+ break;
+ case eWindowType_sheet:
+ if (mParent->WindowType() != eWindowType_invisible &&
+ aBorderStyle & eBorderStyle_resizeh) {
+ features = NSResizableWindowMask;
+ }
+ else {
+ features = NSMiniaturizableWindowMask;
+ }
+ features |= NSTitledWindowMask;
+ break;
+ default:
+ NS_ERROR("Unhandled window type!");
+ return NS_ERROR_FAILURE;
+ }
+
+ NSRect contentRect;
+
+ if (aRectIsFrameRect) {
+ contentRect = [NSWindow contentRectForFrameRect:aRect styleMask:features];
+ } else {
+ /*
+ * We pass a content area rect to initialize the native Cocoa window. The
+ * content rect we give is the same size as the size we're given by gecko.
+ * The origin we're given for non-popup windows is moved down by the height
+ * of the menu bar so that an origin of (0,100) from gecko puts the window
+ * 100 pixels below the top of the available desktop area. We also move the
+ * origin down by the height of a title bar if it exists. This is so the
+ * origin that gecko gives us for the top-left of the window turns out to
+ * be the top-left of the window we create. This is how it was done in
+ * Carbon. If it ought to be different we'll probably need to look at all
+ * the callers.
+ *
+ * Note: This means that if you put a secondary screen on top of your main
+ * screen and open a window in the top screen, it'll be incorrectly shifted
+ * down by the height of the menu bar. Same thing would happen in Carbon.
+ *
+ * Note: If you pass a rect with 0,0 for an origin, the window ends up in a
+ * weird place for some reason. This stops that without breaking popups.
+ */
+ // Compensate for difference between frame and content area height (e.g. title bar).
+ NSRect newWindowFrame = [NSWindow frameRectForContentRect:aRect styleMask:features];
+
+ contentRect = aRect;
+ contentRect.origin.y -= (newWindowFrame.size.height - aRect.size.height);
+
+ if (mWindowType != eWindowType_popup)
+ contentRect.origin.y -= [[NSApp mainMenu] menuBarHeight];
+ }
+
+ // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
+ // rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
+
+ Class windowClass = [BaseWindow class];
+ // If we have a titlebar on a top-level window, we want to be able to control the
+ // titlebar color (for unified windows), so use the special ToolbarWindow class.
+ // Note that we need to check the window type because we mark sheets as
+ // having titlebars.
+ if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) &&
+ (features & NSTitledWindowMask))
+ windowClass = [ToolbarWindow class];
+ // If we're a popup window we need to use the PopupWindow class.
+ else if (mWindowType == eWindowType_popup)
+ windowClass = [PopupWindow class];
+ // If we're a non-popup borderless window we need to use the
+ // BorderlessWindow class.
+ else if (features == NSBorderlessWindowMask)
+ windowClass = [BorderlessWindow class];
+
+ // Create the window
+ mWindow = [[windowClass alloc] initWithContentRect:contentRect styleMask:features
+ backing:NSBackingStoreBuffered defer:YES];
+
+ // Make sure that window titles don't leak to disk in private browsing mode
+ // due to macOS' resume feature.
+ [mWindow setRestorable:NO];
+ [mWindow disableSnapshotRestoration];
+
+ // setup our notification delegate. Note that setDelegate: does NOT retain.
+ mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this];
+ [mWindow setDelegate:mDelegate];
+
+ // Make sure that the content rect we gave has been honored.
+ NSRect wantedFrame = [mWindow frameRectForContentRect:contentRect];
+ if (!NSEqualRects([mWindow frame], wantedFrame)) {
+ // This can happen when the window is not on the primary screen.
+ [mWindow setFrame:wantedFrame display:NO];
+ }
+ UpdateBounds();
+
+ if (mWindowType == eWindowType_invisible) {
+ [mWindow setLevel:kCGDesktopWindowLevelKey];
+ }
+
+ if (mWindowType == eWindowType_popup) {
+ SetPopupWindowLevel();
+ [mWindow setBackgroundColor:[NSColor clearColor]];
+ [mWindow setOpaque:NO];
+ } else {
+ // Make sure that regular windows are opaque from the start, so that
+ // nsChildView::WidgetTypeSupportsAcceleration returns true for them.
+ [mWindow setOpaque:YES];
+ }
+
+ NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
+ if (mAlwaysOnTop) {
+ [mWindow setLevel:NSFloatingWindowLevel];
+ newBehavior |= NSWindowCollectionBehaviorCanJoinAllSpaces;
+ }
+ [mWindow setCollectionBehavior:newBehavior];
+
+ [mWindow setContentMinSize:NSMakeSize(60, 60)];
+ [mWindow disableCursorRects];
+
+ // Make sure the window starts out not draggable by the background.
+ // We will turn it on as necessary.
+ [mWindow setMovableByWindowBackground:NO];
+
+ [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
+ mWindowMadeHere = true;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsCocoaWindow::CreatePopupContentView(const LayoutDeviceIntRect &aRect,
+ nsWidgetInitData* aInitData)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // We need to make our content view a ChildView.
+ mPopupContentView = new nsChildView();
+ if (!mPopupContentView)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(mPopupContentView);
+
+ nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
+ nsresult rv = mPopupContentView->Create(thisAsWidget, nullptr, aRect,
+ aInitData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ NSView* contentView = [mWindow contentView];
+ ChildView* childView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
+ [childView setFrame:NSZeroRect];
+ [childView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [contentView addSubview:childView];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsCocoaWindow::Destroy()
+{
+ if (mOnDestroyCalled)
+ return;
+ mOnDestroyCalled = true;
+
+ // SetFakeModal(true) is called for non-modal window opened by modal window.
+ // On Cocoa, it needs corresponding SetFakeModal(false) on destroy to restore
+ // ancestor windows' state.
+ if (mFakeModal) {
+ SetFakeModal(false);
+ }
+
+ // If we don't hide here we run into problems with panels, this is not ideal.
+ // (Bug 891424)
+ Show(false);
+
+ if (mPopupContentView)
+ mPopupContentView->Destroy();
+
+ if (mFullscreenTransitionAnimation) {
+ [mFullscreenTransitionAnimation stopAnimation];
+ ReleaseFullscreenTransitionAnimation();
+ }
+
+ nsBaseWidget::Destroy();
+ // nsBaseWidget::Destroy() calls GetParent()->RemoveChild(this). But we
+ // don't implement GetParent(), so we need to do the equivalent here.
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ nsBaseWidget::OnDestroy();
+
+ if (mInFullScreenMode) {
+ // On Lion we don't have to mess with the OS chrome when in Full Screen
+ // mode. But we do have to destroy the native window here (and not wait
+ // for that to happen in our destructor). We don't switch away from the
+ // native window's space until the window is destroyed, and otherwise this
+ // might not happen for several seconds (because at least one object
+ // holding a reference to ourselves is usually waiting to be garbage-
+ // collected). See bug 757618.
+ if (mInNativeFullScreenMode) {
+ DestroyNativeWindow();
+ } else if (mWindow) {
+ nsCocoaUtils::HideOSChromeOnScreen(false);
+ }
+ }
+}
+
+nsIWidget* nsCocoaWindow::GetSheetWindowParent(void)
+{
+ if (mWindowType != eWindowType_sheet)
+ return nullptr;
+ nsCocoaWindow *parent = static_cast<nsCocoaWindow*>(mParent);
+ while (parent && (parent->mWindowType == eWindowType_sheet))
+ parent = static_cast<nsCocoaWindow*>(parent->mParent);
+ return parent;
+}
+
+void* nsCocoaWindow::GetNativeData(uint32_t aDataType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSNULL;
+
+ void* retVal = nullptr;
+
+ switch (aDataType) {
+ // to emulate how windows works, we always have to return a NSView
+ // for NS_NATIVE_WIDGET
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = [mWindow contentView];
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = mWindow;
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ // There isn't anything that makes sense to return here,
+ // and it doesn't matter so just return nullptr.
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a top-level window!");
+ break;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ NSView* view = mWindow ? [mWindow contentView] : nil;
+ if (view) {
+ retVal = [view inputContext];
+ }
+ // If inputContext isn't available on this window, return this window's
+ // pointer instead of nullptr since if this returns nullptr,
+ // IMEStateManager cannot manage composition with TextComposition
+ // instance. Although, this case shouldn't occur.
+ if (NS_WARN_IF(!retVal)) {
+ retVal = this;
+ }
+ break;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
+
+bool nsCocoaWindow::IsVisible() const
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return (mWindow && ([mWindow isVisibleOrBeingShown] || mSheetNeedsShow));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+void
+nsCocoaWindow::SetModal(bool aState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // This is used during startup (outside the event loop) when creating
+ // the add-ons compatibility checking dialog and the profile manager UI;
+ // therefore, it needs to provide an autorelease pool to avoid cocoa
+ // objects leaking.
+ nsAutoreleasePool localPool;
+
+ mModal = aState;
+ nsCocoaWindow *ancestor = static_cast<nsCocoaWindow*>(mAncestorLink);
+ if (aState) {
+ ++gXULModalLevel;
+ // When a non-sheet window gets "set modal", make the window(s) that it
+ // appears over behave as they should. We can't rely on native methods to
+ // do this, for the following reason: The OS runs modal non-sheet windows
+ // in an event loop (using [NSApplication runModalForWindow:] or similar
+ // methods) that's incompatible with the modal event loop in nsXULWindow::
+ // ShowModal() (each of these event loops is "exclusive", and can't run at
+ // the same time as other (similar) event loops).
+ if (mWindowType != eWindowType_sheet) {
+ while (ancestor) {
+ if (ancestor->mNumModalDescendents++ == 0) {
+ NSWindow *aWindow = ancestor->GetCocoaWindow();
+ if (ancestor->mWindowType != eWindowType_invisible) {
+ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:NO];
+ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO];
+ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:NO];
+ }
+ }
+ ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
+ }
+ [mWindow setLevel:NSModalPanelWindowLevel];
+ nsCocoaWindowList *windowList = new nsCocoaWindowList;
+ if (windowList) {
+ windowList->window = this; // Don't ADDREF
+ windowList->prev = gGeckoAppModalWindowList;
+ gGeckoAppModalWindowList = windowList;
+ }
+ }
+ }
+ else {
+ --gXULModalLevel;
+ NS_ASSERTION(gXULModalLevel >= 0, "Mismatched call to nsCocoaWindow::SetModal(false)!");
+ if (mWindowType != eWindowType_sheet) {
+ while (ancestor) {
+ if (--ancestor->mNumModalDescendents == 0) {
+ NSWindow *aWindow = ancestor->GetCocoaWindow();
+ if (ancestor->mWindowType != eWindowType_invisible) {
+ [[aWindow standardWindowButton:NSWindowCloseButton] setEnabled:YES];
+ [[aWindow standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES];
+ [[aWindow standardWindowButton:NSWindowZoomButton] setEnabled:YES];
+ }
+ }
+ NS_ASSERTION(ancestor->mNumModalDescendents >= 0, "Widget hierarchy changed while modal!");
+ ancestor = static_cast<nsCocoaWindow*>(ancestor->mParent);
+ }
+ if (gGeckoAppModalWindowList) {
+ NS_ASSERTION(gGeckoAppModalWindowList->window == this, "Widget hierarchy changed while modal!");
+ nsCocoaWindowList *saved = gGeckoAppModalWindowList;
+ gGeckoAppModalWindowList = gGeckoAppModalWindowList->prev;
+ delete saved; // "window" not ADDREFed
+ }
+ if (mWindowType == eWindowType_popup)
+ SetPopupWindowLevel();
+ else
+ [mWindow setLevel:NSNormalWindowLevel];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsCocoaWindow::SetFakeModal(bool aState)
+{
+ mFakeModal = aState;
+ SetModal(aState);
+}
+
+bool
+nsCocoaWindow::IsRunningAppModal()
+{
+ return [NSApp _isRunningAppModal];
+}
+
+// Hide or show this window
+NS_IMETHODIMP nsCocoaWindow::Show(bool bState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow)
+ return NS_OK;
+
+ // We need to re-execute sometimes in order to bring already-visible
+ // windows forward.
+ if (!mSheetNeedsShow && !bState && ![mWindow isVisible])
+ return NS_OK;
+
+ // Protect against re-entering.
+ if (bState && [mWindow isBeingShown])
+ return NS_OK;
+
+ [mWindow setBeingShown:bState];
+
+ nsIWidget* parentWidget = mParent;
+ nsCOMPtr<nsPIWidgetCocoa> piParentWidget(do_QueryInterface(parentWidget));
+ NSWindow* nativeParentWindow = (parentWidget) ?
+ (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW) : nil;
+
+ if (bState && !mBounds.IsEmpty()) {
+ // Don't try to show a popup when the parent isn't visible or is minimized.
+ if (mWindowType == eWindowType_popup && nativeParentWindow) {
+ if (![nativeParentWindow isVisible] || [nativeParentWindow isMiniaturized]) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mPopupContentView) {
+ // Ensure our content view is visible. We never need to hide it.
+ mPopupContentView->Show(true);
+ }
+
+ if (mWindowType == eWindowType_sheet) {
+ // bail if no parent window (its basically what we do in Carbon)
+ if (!nativeParentWindow || !piParentWidget)
+ return NS_ERROR_FAILURE;
+
+ NSWindow* topNonSheetWindow = nativeParentWindow;
+
+ // If this sheet is the child of another sheet, hide the parent so that
+ // this sheet can be displayed. Leave the parent mSheetNeedsShow alone,
+ // that is only used to handle sibling sheet contention. The parent will
+ // return once there are no more child sheets.
+ bool parentIsSheet = false;
+ if (NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
+ parentIsSheet) {
+ piParentWidget->GetSheetWindowParent(&topNonSheetWindow);
+ [NSApp endSheet:nativeParentWindow];
+ }
+
+ nsCOMPtr<nsIWidget> sheetShown;
+ if (NS_SUCCEEDED(piParentWidget->GetChildSheet(
+ true, getter_AddRefs(sheetShown))) &&
+ (!sheetShown || sheetShown == this)) {
+ // If this sheet is already the sheet actually being shown, don't
+ // tell it to show again. Otherwise the number of calls to
+ // [NSApp beginSheet...] won't match up with [NSApp endSheet...].
+ if (![mWindow isVisible]) {
+ mSheetNeedsShow = false;
+ mSheetWindowParent = topNonSheetWindow;
+ // Only set contextInfo if our parent isn't a sheet.
+ NSWindow* contextInfo = parentIsSheet ? nil : mSheetWindowParent;
+ [TopLevelWindowData deactivateInWindow:mSheetWindowParent];
+ [NSApp beginSheet:mWindow
+ modalForWindow:mSheetWindowParent
+ modalDelegate:mDelegate
+ didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
+ contextInfo:contextInfo];
+ [TopLevelWindowData activateInWindow:mWindow];
+ SendSetZLevelEvent();
+ }
+ }
+ else {
+ // A sibling of this sheet is active, don't show this sheet yet.
+ // When the active sheet hides, its brothers and sisters that have
+ // mSheetNeedsShow set will have their opportunities to display.
+ mSheetNeedsShow = true;
+ }
+ }
+ else if (mWindowType == eWindowType_popup) {
+ if (!nsCocoaFeatures::OnMojaveOrLater()) {
+ // If a popup window is shown after being hidden, it needs to be "reset"
+ // for it to receive any mouse events aside from mouse-moved events
+ // (because it was removed from the "window cache" when it was hidden
+ // -- see below). Setting the window number to -1 and then back to its
+ // original value seems to accomplish this. The idea was "borrowed"
+ // from the Java Embedding Plugin. This is fixed on macOS 10.14+.
+ NSInteger windowNumber = [mWindow windowNumber];
+ [mWindow _setWindowNumber:-1];
+ [mWindow _setWindowNumber:windowNumber];
+ }
+ // For reasons that aren't yet clear, calls to [NSWindow orderFront:] or
+ // [NSWindow makeKeyAndOrderFront:] can sometimes trigger "Error (1000)
+ // creating CGSWindow", which in turn triggers an internal inconsistency
+ // NSException. These errors shouldn't be fatal. So we need to wrap
+ // calls to ...orderFront: in TRY blocks. See bmo bug 470864.
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ [[mWindow contentView] setNeedsDisplay:YES];
+ [mWindow orderFront:nil];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ SendSetZLevelEvent();
+ AdjustWindowShadow();
+ SetWindowBackgroundBlur();
+ // If our popup window is a non-native context menu, tell the OS (and
+ // other programs) that a menu has opened. This is how the OS knows to
+ // close other programs' context menus when ours open.
+ if ([mWindow isKindOfClass:[PopupWindow class]] &&
+ [(PopupWindow*) mWindow isContextMenu]) {
+ [[NSDistributedNotificationCenter defaultCenter]
+ postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
+ object:@"org.mozilla.gecko.PopupWindow"];
+ }
+
+ // If a parent window was supplied and this is a popup at the parent
+ // level, set its child window. This will cause the child window to
+ // appear above the parent and move when the parent does. Setting this
+ // needs to happen after the _setWindowNumber calls above, otherwise the
+ // window doesn't focus properly.
+ if (nativeParentWindow && mPopupLevel == ePopupLevelParent)
+ [nativeParentWindow addChildWindow:mWindow
+ ordered:NSWindowAbove];
+ }
+ else {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ if (mWindowType == eWindowType_toplevel &&
+ [mWindow respondsToSelector:@selector(setAnimationBehavior:)]) {
+ NSWindowAnimationBehavior behavior;
+ if (mIsAnimationSuppressed) {
+ behavior = NSWindowAnimationBehaviorNone;
+ } else {
+ switch (mAnimationType) {
+ case nsIWidget::eDocumentWindowAnimation:
+ behavior = NSWindowAnimationBehaviorDocumentWindow;
+ break;
+ default:
+ NS_NOTREACHED("unexpected mAnimationType value");
+ // fall through
+ case nsIWidget::eGenericWindowAnimation:
+ behavior = NSWindowAnimationBehaviorDefault;
+ break;
+ }
+ }
+ [mWindow setAnimationBehavior:behavior];
+ }
+ [mWindow makeKeyAndOrderFront:nil];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ SendSetZLevelEvent();
+ }
+ }
+ else {
+ // roll up any popups if a top-level window is going away
+ if (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)
+ RollUpPopups();
+
+ // now get rid of the window/sheet
+ if (mWindowType == eWindowType_sheet) {
+ if (mSheetNeedsShow) {
+ // This is an attempt to hide a sheet that never had a chance to
+ // be shown. There's nothing to do other than make sure that it
+ // won't show.
+ mSheetNeedsShow = false;
+ }
+ else {
+ // get sheet's parent *before* hiding the sheet (which breaks the linkage)
+ NSWindow* sheetParent = mSheetWindowParent;
+
+ // hide the sheet
+ [NSApp endSheet:mWindow];
+
+ [TopLevelWindowData deactivateInWindow:mWindow];
+
+ nsCOMPtr<nsIWidget> siblingSheetToShow;
+ bool parentIsSheet = false;
+
+ if (nativeParentWindow && piParentWidget &&
+ NS_SUCCEEDED(piParentWidget->GetChildSheet(
+ false, getter_AddRefs(siblingSheetToShow))) &&
+ siblingSheetToShow) {
+ // First, give sibling sheets an opportunity to show.
+ siblingSheetToShow->Show(true);
+ }
+ else if (nativeParentWindow && piParentWidget &&
+ NS_SUCCEEDED(piParentWidget->GetIsSheet(&parentIsSheet)) &&
+ parentIsSheet) {
+ // Only set contextInfo if the parent of the parent sheet we're about
+ // to restore isn't itself a sheet.
+ NSWindow* contextInfo = sheetParent;
+ nsIWidget* grandparentWidget = nil;
+ if (NS_SUCCEEDED(piParentWidget->GetRealParent(&grandparentWidget)) && grandparentWidget) {
+ nsCOMPtr<nsPIWidgetCocoa> piGrandparentWidget(do_QueryInterface(grandparentWidget));
+ bool grandparentIsSheet = false;
+ if (piGrandparentWidget && NS_SUCCEEDED(piGrandparentWidget->GetIsSheet(&grandparentIsSheet)) &&
+ grandparentIsSheet) {
+ contextInfo = nil;
+ }
+ }
+ // If there are no sibling sheets, but the parent is a sheet, restore
+ // it. It wasn't sent any deactivate events when it was hidden, so
+ // don't call through Show, just let the OS put it back up.
+ [NSApp beginSheet:nativeParentWindow
+ modalForWindow:sheetParent
+ modalDelegate:[nativeParentWindow delegate]
+ didEndSelector:@selector(didEndSheet:returnCode:contextInfo:)
+ contextInfo:contextInfo];
+ }
+ else {
+ // Sheet, that was hard. No more siblings or parents, going back
+ // to a real window.
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ [sheetParent makeKeyAndOrderFront:nil];
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+ }
+ SendSetZLevelEvent();
+ }
+ }
+ else {
+ // If the window is a popup window with a parent window we need to
+ // unhook it here before ordering it out. When you order out the child
+ // of a window it hides the parent window.
+ if (mWindowType == eWindowType_popup && nativeParentWindow)
+ [nativeParentWindow removeChildWindow:mWindow];
+
+ [mWindow orderOut:nil];
+
+ if (!nsCocoaFeatures::OnMojaveOrLater()) {
+ // Unless it's explicitly removed from NSApp's "window cache", a popup
+ // window will keep receiving mouse-moved events even after it's been
+ // "ordered out" (instead of the browser window that was underneath it,
+ // until you click on that window). This is bmo bug 378645, but it's
+ // surely an Apple bug. The "window cache" is an undocumented
+ // subsystem, all of whose methods are included in the NSWindowCache
+ // category of the NSApplication class (in header files generated using
+ // class-dump). This workaround was "borrowed" from the Java Embedding
+ // Plugin (which uses it for a different purpose). This is fixed on
+ // macOS 10.14+.
+ if (mWindowType == eWindowType_popup) {
+ [NSApp _removeWindowFromCache:mWindow];
+ }
+ }
+
+ // If our popup window is a non-native context menu, tell the OS (and
+ // other programs) that a menu has closed.
+ if ([mWindow isKindOfClass:[PopupWindow class]] &&
+ [(PopupWindow*) mWindow isContextMenu]) {
+ [[NSDistributedNotificationCenter defaultCenter]
+ postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification"
+ object:@"org.mozilla.gecko.PopupWindow"];
+ }
+ }
+ }
+
+ [mWindow setBeingShown:NO];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+struct ShadowParams {
+ float standardDeviation;
+ float density;
+ int offsetX;
+ int offsetY;
+ unsigned int flags;
+};
+
+// These numbers have been determined by looking at the results of
+// CGSGetWindowShadowAndRimParameters for native window types.
+static const ShadowParams kWindowShadowParametersPreYosemite[] = {
+ { 0.0f, 0.0f, 0, 0, 0 }, // none
+ { 8.0f, 0.5f, 0, 6, 1 }, // default
+ { 10.0f, 0.44f, 0, 10, 512 }, // menu
+ { 8.0f, 0.5f, 0, 6, 1 }, // tooltip
+ { 4.0f, 0.6f, 0, 4, 512 } // sheet
+};
+
+static const ShadowParams kWindowShadowParametersPostYosemite[] = {
+ { 0.0f, 0.0f, 0, 0, 0 }, // none
+ { 8.0f, 0.5f, 0, 6, 1 }, // default
+ { 9.882353f, 0.3f, 0, 4, 0 }, // menu
+ { 3.294118f, 0.2f, 0, 1, 0 }, // tooltip
+ { 9.882353f, 0.3f, 0, 4, 0 } // sheet
+};
+
+// This method will adjust the window shadow style for popup windows after
+// they have been made visible. Before they're visible, their window number
+// might be -1, which is not useful.
+// We won't attempt to change the shadow for windows that can acquire key state
+// since OS X will reset the shadow whenever that happens.
+void
+nsCocoaWindow::AdjustWindowShadow()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow isVisible] || ![mWindow hasShadow] ||
+ [mWindow canBecomeKeyWindow] || [mWindow windowNumber] == -1)
+ return;
+
+ const ShadowParams& params = nsCocoaFeatures::OnYosemiteOrLater()
+ ? kWindowShadowParametersPostYosemite[mShadowStyle]
+ : kWindowShadowParametersPreYosemite[mShadowStyle];
+ CGSConnection cid = _CGSDefaultConnection();
+ CGSSetWindowShadowAndRimParameters(cid, [mWindow windowNumber],
+ params.standardDeviation, params.density,
+ params.offsetX, params.offsetY,
+ params.flags);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSUInteger kWindowBackgroundBlurRadius = 4;
+
+void
+nsCocoaWindow::SetWindowBackgroundBlur()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow isVisible] || [mWindow windowNumber] == -1)
+ return;
+
+ // Only blur the background of menus and fake sheets.
+ if (mShadowStyle != NS_STYLE_WINDOW_SHADOW_MENU &&
+ mShadowStyle != NS_STYLE_WINDOW_SHADOW_SHEET)
+ return;
+
+ CGSConnection cid = _CGSDefaultConnection();
+ CGSSetWindowBackgroundBlurRadius(cid, [mWindow windowNumber], kWindowBackgroundBlurRadius);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult
+nsCocoaWindow::ConfigureChildren(const nsTArray<Configuration>& aConfigurations)
+{
+ if (mPopupContentView) {
+ mPopupContentView->ConfigureChildren(aConfigurations);
+ }
+ return NS_OK;
+}
+
+LayerManager*
+nsCocoaWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence)
+{
+ if (mPopupContentView) {
+ return mPopupContentView->GetLayerManager(aShadowManager,
+ aBackendHint,
+ aPersistence);
+ }
+ return nullptr;
+}
+
+nsTransparencyMode nsCocoaWindow::GetTransparencyMode()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return (!mWindow || [mWindow isOpaque]) ? eTransparencyOpaque : eTransparencyTransparent;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(eTransparencyOpaque);
+}
+
+// This is called from nsMenuPopupFrame when making a popup transparent, or
+// from nsChildView::SetTransparencyMode for other window types.
+void nsCocoaWindow::SetTransparencyMode(nsTransparencyMode aMode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // Transparent windows are only supported on popups.
+ BOOL isTransparent = aMode == eTransparencyTransparent &&
+ mWindowType == eWindowType_popup;
+ BOOL currentTransparency = ![mWindow isOpaque];
+ if (isTransparent != currentTransparency) {
+ [mWindow setOpaque:!isTransparent];
+ [mWindow setBackgroundColor:(isTransparent ? [NSColor clearColor] : [NSColor whiteColor])];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::Enable(bool aState)
+{
+ return NS_OK;
+}
+
+bool nsCocoaWindow::IsEnabled() const
+{
+ return true;
+}
+
+#define kWindowPositionSlop 20
+
+void
+nsCocoaWindow::ConstrainPosition(bool aAllowSlop, int32_t *aX, int32_t *aY)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow screen]) {
+ return;
+ }
+
+ nsIntRect screenBounds;
+
+ int32_t width, height;
+
+ NSRect frame = [mWindow frame];
+
+ // zero size rects confuse the screen manager
+ width = std::max<int32_t>(frame.size.width, 1);
+ height = std::max<int32_t>(frame.size.height, 1);
+
+ nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenMgr) {
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->ScreenForRect(*aX, *aY, width, height, getter_AddRefs(screen));
+
+ if (screen) {
+ screen->GetRectDisplayPix(&(screenBounds.x), &(screenBounds.y),
+ &(screenBounds.width), &(screenBounds.height));
+ }
+ }
+
+ if (aAllowSlop) {
+ if (*aX < screenBounds.x - width + kWindowPositionSlop) {
+ *aX = screenBounds.x - width + kWindowPositionSlop;
+ } else if (*aX >= screenBounds.x + screenBounds.width - kWindowPositionSlop) {
+ *aX = screenBounds.x + screenBounds.width - kWindowPositionSlop;
+ }
+
+ if (*aY < screenBounds.y - height + kWindowPositionSlop) {
+ *aY = screenBounds.y - height + kWindowPositionSlop;
+ } else if (*aY >= screenBounds.y + screenBounds.height - kWindowPositionSlop) {
+ *aY = screenBounds.y + screenBounds.height - kWindowPositionSlop;
+ }
+ } else {
+ if (*aX < screenBounds.x) {
+ *aX = screenBounds.x;
+ } else if (*aX >= screenBounds.x + screenBounds.width - width) {
+ *aX = screenBounds.x + screenBounds.width - width;
+ }
+
+ if (*aY < screenBounds.y) {
+ *aY = screenBounds.y;
+ } else if (*aY >= screenBounds.y + screenBounds.height - height) {
+ *aY = screenBounds.y + screenBounds.height - height;
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Popups can be smaller than (60, 60)
+ NSRect rect =
+ (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60);
+ rect = [mWindow frameRectForContentRect:rect];
+
+ CGFloat scaleFactor = BackingScaleFactor();
+
+ SizeConstraints c = aConstraints;
+ c.mMinSize.width =
+ std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, scaleFactor),
+ c.mMinSize.width);
+ c.mMinSize.height =
+ std::max(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, scaleFactor),
+ c.mMinSize.height);
+
+ NSSize minSize = {
+ nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, scaleFactor),
+ nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, scaleFactor)
+ };
+ [mWindow setMinSize:minSize];
+
+ NSSize maxSize = {
+ c.mMaxSize.width == NS_MAXSIZE ?
+ FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, scaleFactor),
+ c.mMaxSize.height == NS_MAXSIZE ?
+ FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, scaleFactor)
+ };
+ [mWindow setMaxSize:maxSize];
+
+ nsBaseWidget::SetSizeConstraints(c);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Coordinates are desktop pixels
+NS_IMETHODIMP nsCocoaWindow::Move(double aX, double aY)
+{
+ if (!mWindow) {
+ return NS_OK;
+ }
+
+ // The point we have is in Gecko coordinates (origin top-left). Convert
+ // it to Cocoa ones (origin bottom-left).
+ NSPoint coord = {
+ static_cast<float>(aX),
+ static_cast<float>(nsCocoaUtils::FlippedScreenY(NSToIntRound(aY)))
+ };
+
+ NSRect frame = [mWindow frame];
+ if (frame.origin.x != coord.x ||
+ frame.origin.y + frame.size.height != coord.y) {
+ [mWindow setFrameTopLeftPoint:coord];
+ }
+
+ return NS_OK;
+}
+
+void
+nsCocoaWindow::SetSizeMode(nsSizeMode aMode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // mSizeMode will be updated in DispatchSizeModeEvent, which will be called
+ // from a delegate method that handles the state change during one of the
+ // calls below.
+ nsSizeMode previousMode = mSizeMode;
+
+ if (aMode == nsSizeMode_Normal) {
+ if ([mWindow isMiniaturized])
+ [mWindow deminiaturize:nil];
+ else if (previousMode == nsSizeMode_Maximized && [mWindow isZoomed])
+ [mWindow zoom:nil];
+ }
+ else if (aMode == nsSizeMode_Minimized) {
+ if (![mWindow isMiniaturized])
+ [mWindow miniaturize:nil];
+ }
+ else if (aMode == nsSizeMode_Maximized) {
+ if ([mWindow isMiniaturized])
+ [mWindow deminiaturize:nil];
+ if (![mWindow isZoomed])
+ [mWindow zoom:nil];
+ }
+ else if (aMode == nsSizeMode_Fullscreen) {
+ if (!mInFullScreenMode)
+ MakeFullScreen(true);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// This has to preserve the window's frame bounds.
+// This method requires (as does the Windows impl.) that you call Resize shortly
+// after calling HideWindowChrome. See bug 498835 for fixing this.
+NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow || !mWindowMadeHere ||
+ (mWindowType != eWindowType_toplevel && mWindowType != eWindowType_dialog))
+ return NS_ERROR_FAILURE;
+
+ BOOL isVisible = [mWindow isVisible];
+
+ // Remove child windows.
+ NSArray* childWindows = [mWindow childWindows];
+ NSEnumerator* enumerator = [childWindows objectEnumerator];
+ NSWindow* child = nil;
+ while ((child = [enumerator nextObject])) {
+ [mWindow removeChildWindow:child];
+ }
+
+ // Remove the content view.
+ NSView* contentView = [mWindow contentView];
+ [contentView retain];
+ [contentView removeFromSuperviewWithoutNeedingDisplay];
+
+ // Save state (like window title).
+ NSMutableDictionary* state = [mWindow exportState];
+
+ // Recreate the window with the right border style.
+ NSRect frameRect = [mWindow frame];
+ DestroyNativeWindow();
+ nsresult rv = CreateNativeWindow(frameRect, aShouldHide ? eBorderStyle_none : mBorderStyle, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Re-import state.
+ [mWindow importState:state];
+
+ // Reparent the content view.
+ [mWindow setContentView:contentView];
+ [contentView release];
+
+ // Reparent child windows.
+ enumerator = [childWindows objectEnumerator];
+ while ((child = [enumerator nextObject])) {
+ [mWindow addChildWindow:child ordered:NSWindowAbove];
+ }
+
+ // Show the new window.
+ if (isVisible) {
+ bool wasAnimationSuppressed = mIsAnimationSuppressed;
+ mIsAnimationSuppressed = true;
+ rv = Show(true);
+ mIsAnimationSuppressed = wasAnimationSuppressed;
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+class FullscreenTransitionData : public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionData(NSWindow* aWindow)
+ : mTransitionWindow(aWindow) { }
+
+ NSWindow* mTransitionWindow;
+
+private:
+ virtual ~FullscreenTransitionData()
+ {
+ [mTransitionWindow close];
+ }
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionData)
+
+@interface FullscreenTransitionDelegate : NSObject <NSAnimationDelegate>
+{
+@public
+ nsCocoaWindow* mWindow;
+ nsIRunnable* mCallback;
+}
+@end
+
+@implementation FullscreenTransitionDelegate
+- (void)cleanupAndDispatch:(NSAnimation* )animation
+{
+ [animation setDelegate:nil];
+ [self autorelease];
+ // The caller should have added ref for us.
+ NS_DispatchToMainThread(already_AddRefed<nsIRunnable>(mCallback));
+}
+
+- (void)animationDidEnd:(NSAnimation *)animation
+{
+ MOZ_ASSERT(animation == mWindow->FullscreenTransitionAnimation(),
+ "Should be handling the only animation on the window");
+ mWindow->ReleaseFullscreenTransitionAnimation();
+ [self cleanupAndDispatch:animation];
+}
+
+- (void)animationDidStop:(NSAnimation *)animation
+{
+ [self cleanupAndDispatch:animation];
+}
+@end
+
+/* virtual */ bool
+nsCocoaWindow::PrepareForFullscreenTransition(nsISupports** aData)
+{
+ nsCOMPtr<nsIScreen> widgetScreen = GetWidgetScreen();
+ nsScreenCocoa* screen = static_cast<nsScreenCocoa*>(widgetScreen.get());
+ NSScreen* cocoaScreen = screen->CocoaScreen();
+
+ NSWindow* win =
+ [[NSWindow alloc] initWithContentRect:[cocoaScreen frame]
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [win setBackgroundColor:[NSColor blackColor]];
+ [win setAlphaValue:0];
+ [win setIgnoresMouseEvents:YES];
+ [win setLevel:NSScreenSaverWindowLevel];
+ [win makeKeyAndOrderFront:nil];
+
+ auto data = new FullscreenTransitionData(win);
+ *aData = data;
+ NS_ADDREF(data);
+ return true;
+}
+
+/* virtual */ void
+nsCocoaWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback)
+{
+ auto data = static_cast<FullscreenTransitionData*>(aData);
+ FullscreenTransitionDelegate* delegate =
+ [[FullscreenTransitionDelegate alloc] init];
+ delegate->mWindow = this;
+ // Storing already_AddRefed directly could cause static checking fail.
+ delegate->mCallback = nsCOMPtr<nsIRunnable>(aCallback).forget().take();
+
+ if (mFullscreenTransitionAnimation) {
+ [mFullscreenTransitionAnimation stopAnimation];
+ ReleaseFullscreenTransitionAnimation();
+ }
+
+ NSDictionary* dict = @{
+ NSViewAnimationTargetKey: data->mTransitionWindow,
+ NSViewAnimationEffectKey: aStage == eBeforeFullscreenToggle ?
+ NSViewAnimationFadeInEffect : NSViewAnimationFadeOutEffect
+ };
+ mFullscreenTransitionAnimation =
+ [[NSViewAnimation alloc] initWithViewAnimations:@[dict]];
+ [mFullscreenTransitionAnimation setDelegate:delegate];
+ [mFullscreenTransitionAnimation setDuration:aDuration / 1000.0];
+ [mFullscreenTransitionAnimation startAnimation];
+}
+
+void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode)
+{
+ mInFullScreenTransition = false;
+ bool wasInFullscreen = mInFullScreenMode;
+ mInFullScreenMode = aFullScreen;
+ if (aNativeMode || mInNativeFullScreenMode) {
+ mInNativeFullScreenMode = aFullScreen;
+ }
+ DispatchSizeModeEvent();
+ if (mWidgetListener && wasInFullscreen != aFullScreen) {
+ mWidgetListener->FullscreenChanged(aFullScreen);
+ }
+}
+
+inline bool
+nsCocoaWindow::ShouldToggleNativeFullscreen(bool aFullScreen,
+ bool aUseSystemTransition)
+{
+ if (!mSupportsNativeFullScreen) {
+ // If we cannot use native fullscreen, don't touch it.
+ return false;
+ }
+ if (mInNativeFullScreenMode) {
+ // If we are using native fullscreen, go ahead to exit it.
+ return true;
+ }
+ if (!aUseSystemTransition) {
+ // If we do not want the system fullscreen transition,
+ // don't use the native fullscreen.
+ return false;
+ }
+ // If we are using native fullscreen, we should have returned earlier.
+ return aFullScreen;
+}
+
+nsresult
+nsCocoaWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
+{
+ return DoMakeFullScreen(aFullScreen, false);
+}
+
+NS_IMETHODIMP
+nsCocoaWindow::MakeFullScreenWithNativeTransition(bool aFullScreen,
+ nsIScreen* aTargetScreen)
+{
+ return DoMakeFullScreen(aFullScreen, true);
+}
+
+nsresult
+nsCocoaWindow::DoMakeFullScreen(bool aFullScreen, bool aUseSystemTransition)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow) {
+ return NS_OK;
+ }
+
+ // We will call into MakeFullScreen redundantly when entering/exiting
+ // fullscreen mode via OS X controls. When that happens we should just handle
+ // it gracefully - no need to ASSERT.
+ if (mInFullScreenMode == aFullScreen) {
+ return NS_OK;
+ }
+
+ mInFullScreenTransition = true;
+
+ if (ShouldToggleNativeFullscreen(aFullScreen, aUseSystemTransition)) {
+ // If we're using native fullscreen mode and our native window is invisible,
+ // our attempt to go into fullscreen mode will fail with an assertion in
+ // system code, without [WindowDelegate windowDidFailToEnterFullScreen:]
+ // ever getting called. To pre-empt this we bail here. See bug 752294.
+ if (aFullScreen && ![mWindow isVisible]) {
+ EnteredFullScreen(false);
+ return NS_OK;
+ }
+ MOZ_ASSERT(mInNativeFullScreenMode != aFullScreen,
+ "We shouldn't have been in native fullscreen.");
+ // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen
+ // to be called from the OS. We will call EnteredFullScreen from those methods,
+ // where mInFullScreenMode will be set and a sizemode event will be dispatched.
+ [mWindow toggleFullScreen:nil];
+ } else {
+ NSDisableScreenUpdates();
+ // The order here matters. When we exit full screen mode, we need to show the
+ // Dock first, otherwise the newly-created window won't have its minimize
+ // button enabled. See bug 526282.
+ nsCocoaUtils::HideOSChromeOnScreen(aFullScreen);
+ nsBaseWidget::InfallibleMakeFullScreen(aFullScreen);
+ NSEnableScreenUpdates();
+ EnteredFullScreen(aFullScreen, /* aNativeMode */ false);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Coordinates are desktop pixels
+nsresult nsCocoaWindow::DoResize(double aX, double aY,
+ double aWidth, double aHeight,
+ bool aRepaint,
+ bool aConstrainToCurrentScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow || mInResize) {
+ return NS_OK;
+ }
+
+ AutoRestore<bool> reentrantResizeGuard(mInResize);
+ mInResize = true;
+
+ // ConstrainSize operates in device pixels, so we need to convert using
+ // the backing scale factor here
+ CGFloat scale = BackingScaleFactor();
+ int32_t width = NSToIntRound(aWidth * scale);
+ int32_t height = NSToIntRound(aHeight * scale);
+ ConstrainSize(&width, &height);
+
+ DesktopIntRect newBounds(NSToIntRound(aX), NSToIntRound(aY),
+ NSToIntRound(width / scale),
+ NSToIntRound(height / scale));
+
+ // constrain to the screen that contains the largest area of the new rect
+ FitRectToVisibleAreaForScreen(newBounds, aConstrainToCurrentScreen ?
+ [mWindow screen] : nullptr);
+
+ // convert requested bounds into Cocoa coordinate system
+ NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
+
+ NSRect frame = [mWindow frame];
+ BOOL isMoving = newFrame.origin.x != frame.origin.x ||
+ newFrame.origin.y != frame.origin.y;
+ BOOL isResizing = newFrame.size.width != frame.size.width ||
+ newFrame.size.height != frame.size.height;
+
+ if (!isMoving && !isResizing) {
+ return NS_OK;
+ }
+
+ // We ignore aRepaint -- we have to call display:YES, otherwise the
+ // title bar doesn't immediately get repainted and is displayed in
+ // the wrong place, leading to a visual jump.
+ [mWindow setFrame:newFrame display:YES];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Coordinates are desktop pixels
+NS_IMETHODIMP nsCocoaWindow::Resize(double aX, double aY,
+ double aWidth, double aHeight,
+ bool aRepaint)
+{
+ return DoResize(aX, aY, aWidth, aHeight, aRepaint, false);
+}
+
+// Coordinates are desktop pixels
+NS_IMETHODIMP nsCocoaWindow::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ double invScale = 1.0 / BackingScaleFactor();
+ return DoResize(mBounds.x * invScale, mBounds.y * invScale,
+ aWidth, aHeight, aRepaint, true);
+}
+
+LayoutDeviceIntRect
+nsCocoaWindow::GetClientBounds()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ CGFloat scaleFactor = BackingScaleFactor();
+ if (!mWindow) {
+ return nsCocoaUtils::CocoaRectToGeckoRectDevPix(NSZeroRect, scaleFactor);
+ }
+
+ NSRect r;
+ if ([mWindow isKindOfClass:[ToolbarWindow class]] &&
+ [(ToolbarWindow*)mWindow drawsContentsIntoWindowFrame]) {
+ r = [mWindow frame];
+ } else {
+ r = [mWindow contentRectForFrameRect:[mWindow frame]];
+ }
+
+ return nsCocoaUtils::CocoaRectToGeckoRectDevPix(r, scaleFactor);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
+}
+
+void
+nsCocoaWindow::UpdateBounds()
+{
+ NSRect frame = NSZeroRect;
+ if (mWindow) {
+ frame = [mWindow frame];
+ }
+ mBounds =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+}
+
+LayoutDeviceIntRect
+nsCocoaWindow::GetScreenBounds()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+#ifdef DEBUG
+ LayoutDeviceIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
+ NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
+#endif
+
+ return mBounds;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntRect(0, 0, 0, 0));
+}
+
+double
+nsCocoaWindow::GetDefaultScaleInternal()
+{
+ return BackingScaleFactor();
+}
+
+static CGFloat
+GetBackingScaleFactor(NSWindow* aWindow)
+{
+ NSRect frame = [aWindow frame];
+ if (frame.size.width > 0 && frame.size.height > 0) {
+ return nsCocoaUtils::GetBackingScaleFactor(aWindow);
+ }
+
+ // For windows with zero width or height, the backingScaleFactor method
+ // is broken - it will always return 2 on a retina macbook, even when
+ // the window position implies it's on a non-hidpi external display
+ // (to the extent that a zero-area window can be said to be "on" a
+ // display at all!)
+ // And to make matters worse, Cocoa even fires a
+ // windowDidChangeBackingProperties notification with the
+ // NSBackingPropertyOldScaleFactorKey key when a window on an
+ // external display is resized to/from zero height, even though it hasn't
+ // really changed screens.
+
+ // This causes us to handle popup window sizing incorrectly when the
+ // popup is resized to zero height (bug 820327) - nsXULPopupManager
+ // becomes (incorrectly) convinced the popup has been explicitly forced
+ // to a non-default size and needs to have size attributes attached.
+
+ // Workaround: instead of asking the window, we'll find the screen it is on
+ // and ask that for *its* backing scale factor.
+
+ // (See bug 853252 and additional comments in windowDidChangeScreen: below
+ // for further complications this causes.)
+
+ // First, expand the rect so that it actually has a measurable area,
+ // for FindTargetScreenForRect to use.
+ if (frame.size.width == 0) {
+ frame.size.width = 1;
+ }
+ if (frame.size.height == 0) {
+ frame.size.height = 1;
+ }
+
+ // Then identify the screen it belongs to, and return its scale factor.
+ NSScreen *screen =
+ FindTargetScreenForRect(nsCocoaUtils::CocoaRectToGeckoRect(frame));
+ return nsCocoaUtils::GetBackingScaleFactor(screen);
+}
+
+CGFloat
+nsCocoaWindow::BackingScaleFactor()
+{
+ if (mBackingScaleFactor > 0.0) {
+ return mBackingScaleFactor;
+ }
+ if (!mWindow) {
+ return 1.0;
+ }
+ mBackingScaleFactor = GetBackingScaleFactor(mWindow);
+ return mBackingScaleFactor;
+}
+
+void
+nsCocoaWindow::BackingScaleFactorChanged()
+{
+ CGFloat newScale = GetBackingScaleFactor(mWindow);
+
+ // ignore notification if it hasn't really changed (or maybe we have
+ // disabled HiDPI mode via prefs)
+ if (mBackingScaleFactor == newScale) {
+ return;
+ }
+
+ if (mBackingScaleFactor > 0.0) {
+ // convert size constraints to the new device pixel coordinate space
+ double scaleFactor = newScale / mBackingScaleFactor;
+ mSizeConstraints.mMinSize.width =
+ NSToIntRound(mSizeConstraints.mMinSize.width * scaleFactor);
+ mSizeConstraints.mMinSize.height =
+ NSToIntRound(mSizeConstraints.mMinSize.height * scaleFactor);
+ if (mSizeConstraints.mMaxSize.width < NS_MAXSIZE) {
+ mSizeConstraints.mMaxSize.width =
+ std::min(NS_MAXSIZE,
+ NSToIntRound(mSizeConstraints.mMaxSize.width * scaleFactor));
+ }
+ if (mSizeConstraints.mMaxSize.height < NS_MAXSIZE) {
+ mSizeConstraints.mMaxSize.height =
+ std::min(NS_MAXSIZE,
+ NSToIntRound(mSizeConstraints.mMaxSize.height * scaleFactor));
+ }
+ }
+
+ mBackingScaleFactor = newScale;
+
+ if (!mWidgetListener || mWidgetListener->GetXULWindow()) {
+ return;
+ }
+
+ nsIPresShell* presShell = mWidgetListener->GetPresShell();
+ if (presShell) {
+ presShell->BackingScaleFactorChanged();
+ }
+}
+
+int32_t
+nsCocoaWindow::RoundsWidgetCoordinatesTo()
+{
+ if (BackingScaleFactor() == 2.0) {
+ return 2;
+ }
+ return 1;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetCursor(nsCursor aCursor)
+{
+ if (mPopupContentView)
+ return mPopupContentView->SetCursor(aCursor);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetCursor(imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY)
+{
+ if (mPopupContentView)
+ return mPopupContentView->SetCursor(aCursor, aHotspotX, aHotspotY);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetTitle(const nsAString& aTitle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mWindow)
+ return NS_OK;
+
+ const nsString& strTitle = PromiseFlatString(aTitle);
+ NSString* title = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(strTitle.get())
+ length:strTitle.Length()];
+
+ if ([mWindow drawsContentsIntoWindowFrame] && ![mWindow wantsTitleDrawn]) {
+ // Don't cause invalidations.
+ [mWindow disableSetNeedsDisplay];
+ [mWindow setTitle:title];
+ [mWindow enableSetNeedsDisplay];
+ } else {
+ [mWindow setTitle:title];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsCocoaWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ if (mPopupContentView) {
+ return mPopupContentView->Invalidate(aRect);
+ }
+
+ return NS_OK;
+}
+
+// Pass notification of some drag event to Gecko
+//
+// The drag manager has let us know that something related to a drag has
+// occurred in this window. It could be any number of things, ranging from
+// a drop, to a drag enter/leave, or a drag over event. The actual event
+// is passed in |aMessage| and is passed along to our event hanlder so Gecko
+// knows about it.
+bool nsCocoaWindow::DragEvent(unsigned int aMessage, mozilla::gfx::Point aMouseGlobal, UInt16 aKeyModifiers)
+{
+ return false;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SendSetZLevelEvent()
+{
+ nsWindowZ placement = nsWindowZTop;
+ nsCOMPtr<nsIWidget> actualBelow;
+ if (mWidgetListener)
+ mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetChildSheet(bool aShown, nsIWidget** _retval)
+{
+ nsIWidget* child = GetFirstChild();
+
+ while (child) {
+ if (child->WindowType() == eWindowType_sheet) {
+ // if it's a sheet, it must be an nsCocoaWindow
+ nsCocoaWindow* cocoaWindow = static_cast<nsCocoaWindow*>(child);
+ if (cocoaWindow->mWindow &&
+ ((aShown && [cocoaWindow->mWindow isVisible]) ||
+ (!aShown && cocoaWindow->mSheetNeedsShow))) {
+ nsCOMPtr<nsIWidget> widget = cocoaWindow;
+ widget.forget(_retval);
+ return NS_OK;
+ }
+ }
+ child = child->GetNextSibling();
+ }
+
+ *_retval = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetRealParent(nsIWidget** parent)
+{
+ *parent = mParent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetIsSheet(bool* isSheet)
+{
+ mWindowType == eWindowType_sheet ? *isSheet = true : *isSheet = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetSheetWindowParent(NSWindow** sheetWindowParent)
+{
+ *sheetWindowParent = mSheetWindowParent;
+ return NS_OK;
+}
+
+// Invokes callback and ProcessEvent methods on Event Listener object
+NS_IMETHODIMP
+nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus)
+{
+ aStatus = nsEventStatus_eIgnore;
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(event->mWidget);
+ mozilla::Unused << kungFuDeathGrip; // Not used within this function
+
+ if (mWidgetListener)
+ aStatus = mWidgetListener->HandleEvent(event, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+// aFullScreen should be the window's mInFullScreenMode. We don't have access to that
+// from here, so we need to pass it in. mInFullScreenMode should be the canonical
+// indicator that a window is currently full screen and it makes sense to keep
+// all sizemode logic here.
+static nsSizeMode
+GetWindowSizeMode(NSWindow* aWindow, bool aFullScreen) {
+ if (aFullScreen)
+ return nsSizeMode_Fullscreen;
+ if ([aWindow isMiniaturized])
+ return nsSizeMode_Minimized;
+ if (([aWindow styleMask] & NSResizableWindowMask) && [aWindow isZoomed])
+ return nsSizeMode_Maximized;
+ return nsSizeMode_Normal;
+}
+
+void
+nsCocoaWindow::ReportMoveEvent()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent recursion, which can become infinite (see bug 708278). This
+ // can happen when the call to [NSWindow setFrameTopLeftPoint:] in
+ // nsCocoaWindow::Move() triggers an immediate NSWindowDidMove notification
+ // (and a call to [WindowDelegate windowDidMove:]).
+ if (mInReportMoveEvent) {
+ return;
+ }
+ mInReportMoveEvent = true;
+
+ UpdateBounds();
+
+ // Dispatch the move event to Gecko
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ mInReportMoveEvent = false;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsCocoaWindow::DispatchSizeModeEvent()
+{
+ if (!mWindow) {
+ return;
+ }
+
+ nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode);
+
+ // Don't dispatch a sizemode event if:
+ // 1. the window is transitioning to fullscreen
+ // 2. the new sizemode is the same as the current sizemode
+ if (mInFullScreenTransition || mSizeMode == newMode) {
+ return;
+ }
+
+ mSizeMode = newMode;
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(newMode);
+ }
+}
+
+void
+nsCocoaWindow::ReportSizeEvent()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ UpdateBounds();
+
+ if (mWidgetListener) {
+ LayoutDeviceIntRect innerBounds = GetClientBounds();
+ mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetMenuBar(nsMenuBarX *aMenuBar)
+{
+ if (mMenuBar)
+ mMenuBar->SetParent(nullptr);
+ if (!mWindow) {
+ mMenuBar = nullptr;
+ return;
+ }
+ mMenuBar = aMenuBar;
+
+ // Only paint for active windows, or paint the hidden window menu bar if no
+ // other menu bar has been painted yet so that some reasonable menu bar is
+ // displayed when the app starts up.
+ id windowDelegate = [mWindow delegate];
+ if (mMenuBar &&
+ ((!gSomeMenuBarPainted && nsMenuUtilsX::GetHiddenWindowMenuBar() == mMenuBar) ||
+ (windowDelegate && [windowDelegate toplevelActiveState])))
+ mMenuBar->Paint();
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetFocus(bool aState)
+{
+ if (!mWindow)
+ return NS_OK;
+
+ if (mPopupContentView) {
+ mPopupContentView->SetFocus(aState);
+ }
+ else if (aState && ([mWindow isVisible] || [mWindow isMiniaturized])) {
+ if ([mWindow isMiniaturized]) {
+ [mWindow deminiaturize:nil];
+ }
+
+ [mWindow makeKeyAndOrderFront:nil];
+ SendSetZLevelEvent();
+ }
+
+ return NS_OK;
+}
+
+LayoutDeviceIntPoint nsCocoaWindow::WidgetToScreenOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSRect rect = NSZeroRect;
+ LayoutDeviceIntRect r;
+ if (mWindow) {
+ rect = [mWindow contentRectForFrameRect:[mWindow frame]];
+ }
+ r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(rect, BackingScaleFactor());
+
+ return r.TopLeft();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0,0));
+}
+
+LayoutDeviceIntPoint nsCocoaWindow::GetClientOffset()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ LayoutDeviceIntRect clientRect = GetClientBounds();
+
+ return clientRect.TopLeft() - mBounds.TopLeft();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntPoint(0, 0));
+}
+
+LayoutDeviceIntSize
+nsCocoaWindow::ClientToWindowSize(const LayoutDeviceIntSize& aClientSize)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!mWindow)
+ return LayoutDeviceIntSize(0, 0);
+
+ CGFloat backingScale = BackingScaleFactor();
+ LayoutDeviceIntRect r(0, 0, aClientSize.width, aClientSize.height);
+ NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale);
+
+ NSRect inflatedRect = [mWindow frameRectForContentRect:rect];
+ r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale);
+ return r.Size();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(LayoutDeviceIntSize(0,0));
+}
+
+nsMenuBarX* nsCocoaWindow::GetMenuBar()
+{
+ return mMenuBar;
+}
+
+void
+nsCocoaWindow::CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ gRollupListener = nullptr;
+
+ if (aDoCapture) {
+ if (![NSApp isActive]) {
+ // We need to capture mouse event if we aren't
+ // the active application. We only set this up when needed
+ // because they cause spurious mouse event after crash
+ // and gdb sessions. See bug 699538.
+ nsToolkit::GetToolkit()->RegisterForAllProcessMouseEvents();
+ }
+ gRollupListener = aListener;
+
+ // Sometimes more than one popup window can be visible at the same time
+ // (e.g. nested non-native context menus, or the test case (attachment
+ // 276885) for bmo bug 392389, which displays a non-native combo-box in a
+ // non-native popup window). In these cases the "active" popup window should
+ // be the topmost -- the (nested) context menu the mouse is currently over,
+ // or the combo-box's drop-down list (when it's displayed). But (among
+ // windows that have the same "level") OS X makes topmost the window that
+ // last received a mouse-down event, which may be incorrect (in the combo-
+ // box case, it makes topmost the window containing the combo-box). So
+ // here we fiddle with a non-native popup window's level to make sure the
+ // "active" one is always above any other non-native popup windows that
+ // may be visible.
+ if (mWindow && (mWindowType == eWindowType_popup))
+ SetPopupWindowLevel();
+ } else {
+ nsToolkit::GetToolkit()->UnregisterAllProcessMouseEventHandlers();
+
+ // XXXndeakin this doesn't make sense.
+ // Why is the new window assumed to be a modal panel?
+ if (mWindow && (mWindowType == eWindowType_popup))
+ [mWindow setLevel:NSModalPanelWindowLevel];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::GetAttention(int32_t aCycleCount)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [NSApp requestUserAttention:NSInformationalRequest];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool
+nsCocoaWindow::HasPendingInputEvent()
+{
+ return nsChildView::DoHasPendingInputEvent();
+}
+
+void
+nsCocoaWindow::SetWindowShadowStyle(int32_t aStyle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ mShadowStyle = aStyle;
+
+ // Shadowless windows are only supported on popups.
+ if (mWindowType == eWindowType_popup)
+ [mWindow setHasShadow:(aStyle != NS_STYLE_WINDOW_SHADOW_NONE)];
+
+ [mWindow setUseMenuStyle:(aStyle == NS_STYLE_WINDOW_SHADOW_MENU)];
+ AdjustWindowShadow();
+ SetWindowBackgroundBlur();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetShowsToolbarButton(bool aShow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mWindow)
+ [mWindow setShowsToolbarButton:aShow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetShowsFullScreenButton(bool aShow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow || ![mWindow respondsToSelector:@selector(toggleFullScreen:)] ||
+ mSupportsNativeFullScreen == aShow) {
+ return;
+ }
+
+ // If the window is currently in fullscreen mode, then we're going to
+ // transition out first, then set the collection behavior & toggle
+ // mSupportsNativeFullScreen, then transtion back into fullscreen mode. This
+ // prevents us from getting into a conflicting state with MakeFullScreen
+ // where mSupportsNativeFullScreen would lead us down the wrong path.
+ bool wasFullScreen = mInFullScreenMode;
+
+ if (wasFullScreen) {
+ MakeFullScreen(false);
+ }
+
+ NSWindowCollectionBehavior newBehavior = [mWindow collectionBehavior];
+ if (aShow) {
+ newBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
+ } else {
+ newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
+ }
+ [mWindow setCollectionBehavior:newBehavior];
+ mSupportsNativeFullScreen = aShow;
+
+ if (wasFullScreen) {
+ MakeFullScreen(true);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetWindowAnimationType(nsIWidget::WindowAnimationType aType)
+{
+ mAnimationType = aType;
+}
+
+void
+nsCocoaWindow::SetDrawsTitle(bool aDrawTitle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindow setWantsTitleDrawn:aDrawTitle];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsCocoaWindow::SetUseBrightTitlebarForeground(bool aBrightForeground)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindow setUseBrightTitlebarForeground:aBrightForeground];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SetNonClientMargins(LayoutDeviceIntMargin &margins)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ SetDrawsInTitlebar(margins.top == 0);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsCocoaWindow::SetWindowTitlebarColor(nscolor aColor, bool aActive)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mWindow)
+ return;
+
+ // If they pass a color with a complete transparent alpha component, use the
+ // native titlebar appearance.
+ if (NS_GET_A(aColor) == 0) {
+ [mWindow setTitlebarColor:nil forActiveWindow:(BOOL)aActive];
+ } else {
+ // Transform from sRGBA to monitor RGBA. This seems like it would make trying
+ // to match the system appearance lame, so probably we just shouldn't color
+ // correct chrome.
+ if (gfxPlatform::GetCMSMode() == eCMSMode_All) {
+ qcms_transform *transform = gfxPlatform::GetCMSRGBATransform();
+ if (transform) {
+ uint8_t color[3];
+ color[0] = NS_GET_R(aColor);
+ color[1] = NS_GET_G(aColor);
+ color[2] = NS_GET_B(aColor);
+ qcms_transform_data(transform, color, color, 1);
+ aColor = NS_RGB(color[0], color[1], color[2]);
+ }
+ }
+
+ [mWindow setTitlebarColor:[NSColor colorWithDeviceRed:NS_GET_R(aColor)/255.0
+ green:NS_GET_G(aColor)/255.0
+ blue:NS_GET_B(aColor)/255.0
+ alpha:NS_GET_A(aColor)/255.0]
+ forActiveWindow:(BOOL)aActive];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetDrawsInTitlebar(bool aState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mWindow)
+ [mWindow setDrawsContentsIntoWindowFrame:aState];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+ if (mPopupContentView)
+ return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage,
+ aModifierFlags, nullptr);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsCocoaWindow::UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPopupContentView) {
+ return mPopupContentView->UpdateThemeGeometries(aThemeGeometries);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsCocoaWindow::SetPopupWindowLevel()
+{
+ if (!mWindow)
+ return;
+
+ // Floating popups are at the floating level and hide when the window is
+ // deactivated.
+ if (mPopupLevel == ePopupLevelFloating) {
+ [mWindow setLevel:NSFloatingWindowLevel];
+ [mWindow setHidesOnDeactivate:YES];
+ }
+ else {
+ // Otherwise, this is a top-level or parent popup. Parent popups always
+ // appear just above their parent and essentially ignore the level.
+ [mWindow setLevel:NSPopUpMenuWindowLevel];
+ [mWindow setHidesOnDeactivate:NO];
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsCocoaWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mInputContext = aContext;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP_(bool)
+nsCocoaWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData)
+{
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ return keyBindings->Execute(aEvent, aCallback, aCallbackData);
+}
+
+@implementation WindowDelegate
+
+// We try to find a gecko menu bar to paint. If one does not exist, just paint
+// the application menu by itself so that a window doesn't have some other
+// window's menu bar.
++ (void)paintMenubarForWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // make sure we only act on windows that have this kind of
+ // object as a delegate
+ id windowDelegate = [aWindow delegate];
+ if ([windowDelegate class] != [self class])
+ return;
+
+ nsCocoaWindow* geckoWidget = [windowDelegate geckoWidget];
+ NS_ASSERTION(geckoWidget, "Window delegate not returning a gecko widget!");
+
+ nsMenuBarX* geckoMenuBar = geckoWidget->GetMenuBar();
+ if (geckoMenuBar) {
+ geckoMenuBar->Paint();
+ }
+ else {
+ // sometimes we don't have a native application menu early in launching
+ if (!sApplicationMenu)
+ return;
+
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ // Create a new menu bar.
+ // We create a GeckoNSMenu because all menu bar NSMenu objects should use that subclass for
+ // key handling reasons.
+ GeckoNSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
+
+ // move the application menu from the existing menu bar to the new one
+ NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
+ [mainMenu removeItemAtIndex:0];
+ [newMenuBar insertItem:firstMenuItem atIndex:0];
+ [firstMenuItem release];
+
+ // set our new menu bar as the main menu
+ [NSApp setMainMenu:newMenuBar];
+ [newMenuBar release];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (id)initWithGeckoWindow:(nsCocoaWindow*)geckoWind
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ [super init];
+ mGeckoWindow = geckoWind;
+ mToplevelActiveState = false;
+ mHasEverBeenZoomed = false;
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize
+{
+ RollUpPopups();
+
+ return proposedFrameSize;
+}
+
+- (void)windowDidResize:(NSNotification *)aNotification
+{
+ BaseWindow* window = [aNotification object];
+ [window updateTrackingArea];
+
+ if (!mGeckoWindow)
+ return;
+
+ // Resizing might have changed our zoom state.
+ mGeckoWindow->DispatchSizeModeEvent();
+ mGeckoWindow->ReportSizeEvent();
+}
+
+- (void)windowDidChangeScreen:(NSNotification *)aNotification
+{
+ if (!mGeckoWindow)
+ return;
+
+ // Because of Cocoa's peculiar treatment of zero-size windows (see comments
+ // at GetBackingScaleFactor() above), we sometimes have a situation where
+ // our concept of backing scale (based on the screen where the zero-sized
+ // window is positioned) differs from Cocoa's idea (always based on the
+ // Retina screen, AFAICT, even when an external non-Retina screen is the
+ // primary display).
+ //
+ // As a result, if the window was created with zero size on an external
+ // display, but then made visible on the (secondary) Retina screen, we
+ // will *not* get a windowDidChangeBackingProperties notification for it.
+ // This leads to an incorrect GetDefaultScale(), and widget coordinate
+ // confusion, as per bug 853252.
+ //
+ // To work around this, we check for a backing scale mismatch when we
+ // receive a windowDidChangeScreen notification, as we will receive this
+ // even if Cocoa was already treating the zero-size window as having
+ // Retina backing scale.
+ NSWindow *window = (NSWindow *)[aNotification object];
+ if ([window respondsToSelector:@selector(backingScaleFactor)]) {
+ if (GetBackingScaleFactor(window) != mGeckoWindow->BackingScaleFactor()) {
+ mGeckoWindow->BackingScaleFactorChanged();
+ }
+ }
+
+ mGeckoWindow->ReportMoveEvent();
+}
+
+// Lion's full screen mode will bypass our internal fullscreen tracking, so
+// we need to catch it when we transition and call our own methods, which in
+// turn will fire "fullscreen" events.
+- (void)windowDidEnterFullScreen:(NSNotification *)notification
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(true);
+
+ // On Yosemite, the NSThemeFrame class has two new properties --
+ // titlebarView (an NSTitlebarView object) and titlebarContainerView (an
+ // NSTitlebarContainerView object). These are used to display the titlebar
+ // in fullscreen mode. In Safari they're not transparent. But in Firefox
+ // for some reason they are, which causes bug 1069658. The following code
+ // works around this Apple bug or design flaw.
+ NSWindow *window = (NSWindow *) [notification object];
+ NSView *frameView = [[window contentView] superview];
+ NSView *titlebarView = nil;
+ NSView *titlebarContainerView = nil;
+ if ([frameView respondsToSelector:@selector(titlebarView)]) {
+ titlebarView = [frameView titlebarView];
+ }
+ if ([frameView respondsToSelector:@selector(titlebarContainerView)]) {
+ titlebarContainerView = [frameView titlebarContainerView];
+ }
+ if ([titlebarView respondsToSelector:@selector(setTransparent:)]) {
+ [titlebarView setTransparent:NO];
+ }
+ if ([titlebarContainerView respondsToSelector:@selector(setTransparent:)]) {
+ [titlebarContainerView setTransparent:NO];
+ }
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(false);
+}
+
+- (void)windowDidFailToEnterFullScreen:(NSWindow *)window
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(false);
+}
+
+- (void)windowDidFailToExitFullScreen:(NSWindow *)window
+{
+ if (!mGeckoWindow) {
+ return;
+ }
+
+ mGeckoWindow->EnteredFullScreen(true);
+}
+
+- (void)windowDidBecomeMain:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
+ // app modally. If one of those is up then we want it to retain its menu bar.
+ if ([NSApp _isRunningAppModal])
+ return;
+ NSWindow* window = [aNotification object];
+ if (window)
+ [WindowDelegate paintMenubarForWindow:window];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidResignMain:(NSNotification *)aNotification
+{
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // [NSApp _isRunningAppModal] will return true if we're running an OS dialog
+ // app modally. If one of those is up then we want it to retain its menu bar.
+ if ([NSApp _isRunningAppModal])
+ return;
+ RefPtr<nsMenuBarX> hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
+ if (hiddenWindowMenuBar) {
+ // printf("painting hidden window menu bar due to window losing main status\n");
+ hiddenWindowMenuBar->Paint();
+ }
+}
+
+- (void)windowDidBecomeKey:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ NSWindow* window = [aNotification object];
+ if ([window isSheet])
+ [WindowDelegate paintMenubarForWindow:window];
+
+ nsChildView* mainChildView =
+ static_cast<nsChildView*>([[(BaseWindow*)window mainChildView] widget]);
+ if (mainChildView) {
+ if (mainChildView->GetInputContext().IsPasswordEditor()) {
+ TextInputHandler::EnableSecureEventInput();
+ } else {
+ TextInputHandler::EnsureSecureEventInputDisabled();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidResignKey:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+ ChildViewMouseTracker::ReEvaluateMouseEnterState();
+
+ // If a sheet just resigned key then we should paint the menu bar
+ // for whatever window is now main.
+ NSWindow* window = [aNotification object];
+ if ([window isSheet])
+ [WindowDelegate paintMenubarForWindow:[NSApp mainWindow]];
+
+ TextInputHandler::EnsureSecureEventInputDisabled();
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowWillMove:(NSNotification *)aNotification
+{
+ RollUpPopups();
+}
+
+- (void)windowDidMove:(NSNotification *)aNotification
+{
+ if (mGeckoWindow)
+ mGeckoWindow->ReportMoveEvent();
+}
+
+- (BOOL)windowShouldClose:(id)sender
+{
+ nsIWidgetListener* listener = mGeckoWindow ? mGeckoWindow->GetWidgetListener() : nullptr;
+ if (listener)
+ listener->RequestWindowClose(mGeckoWindow);
+ return NO; // gecko will do it
+}
+
+- (void)windowWillClose:(NSNotification *)aNotification
+{
+ RollUpPopups();
+}
+
+- (void)windowWillMiniaturize:(NSNotification *)aNotification
+{
+ RollUpPopups();
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)aNotification
+{
+ if (mGeckoWindow)
+ mGeckoWindow->DispatchSizeModeEvent();
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)aNotification
+{
+ if (mGeckoWindow)
+ mGeckoWindow->DispatchSizeModeEvent();
+}
+
+- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)proposedFrame
+{
+ if (!mHasEverBeenZoomed && [window isZoomed])
+ return NO; // See bug 429954.
+
+ mHasEverBeenZoomed = YES;
+ return YES;
+}
+
+- (void)didEndSheet:(NSWindow*)sheet returnCode:(int)returnCode contextInfo:(void*)contextInfo
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Note: 'contextInfo' (if it is set) is the window that is the parent of
+ // the sheet. The value of contextInfo is determined in
+ // nsCocoaWindow::Show(). If it's set, 'contextInfo' is always the top-
+ // level window, not another sheet itself. But 'contextInfo' is nil if
+ // our parent window is also a sheet -- in that case we shouldn't send
+ // the top-level window any activate events (because it's our parent
+ // window that needs to get these events, not the top-level window).
+ [TopLevelWindowData deactivateInWindow:sheet];
+ [sheet orderOut:self];
+ if (contextInfo)
+ [TopLevelWindowData activateInWindow:(NSWindow*)contextInfo];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)aNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSWindow *window = (NSWindow *)[aNotification object];
+
+ if ([window respondsToSelector:@selector(backingScaleFactor)]) {
+ CGFloat oldFactor =
+ [[[aNotification userInfo]
+ objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue];
+ if ([window backingScaleFactor] != oldFactor) {
+ mGeckoWindow->BackingScaleFactorChanged();
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (nsCocoaWindow*)geckoWidget
+{
+ return mGeckoWindow;
+}
+
+- (bool)toplevelActiveState
+{
+ return mToplevelActiveState;
+}
+
+- (void)sendToplevelActivateEvents
+{
+ if (!mToplevelActiveState && mGeckoWindow) {
+ nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
+ if (listener) {
+ listener->WindowActivated();
+ }
+ mToplevelActiveState = true;
+ }
+}
+
+- (void)sendToplevelDeactivateEvents
+{
+ if (mToplevelActiveState && mGeckoWindow) {
+ nsIWidgetListener* listener = mGeckoWindow->GetWidgetListener();
+ if (listener) {
+ listener->WindowDeactivated();
+ }
+ mToplevelActiveState = false;
+ }
+}
+
+@end
+
+static float
+GetDPI(NSWindow* aWindow)
+{
+ NSScreen* screen = [aWindow screen];
+ if (!screen)
+ return 96.0f;
+
+ CGDirectDisplayID displayID =
+ [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] intValue];
+ CGFloat heightMM = ::CGDisplayScreenSize(displayID).height;
+ size_t heightPx = ::CGDisplayPixelsHigh(displayID);
+ if (heightMM < 1 || heightPx < 1) {
+ // Something extremely bogus is going on
+ return 96.0f;
+ }
+
+ float dpi = heightPx / (heightMM / MM_PER_INCH_FLOAT);
+
+ // Account for HiDPI mode where Cocoa's "points" do not correspond to real
+ // device pixels
+ CGFloat backingScale = GetBackingScaleFactor(aWindow);
+
+ return dpi * backingScale;
+}
+
+@interface NSView(FrameViewMethodSwizzling)
+- (NSPoint)FrameView__closeButtonOrigin;
+- (NSPoint)FrameView__fullScreenButtonOrigin;
+@end
+
+@implementation NSView(FrameViewMethodSwizzling)
+
+- (NSPoint)FrameView__closeButtonOrigin
+{
+ NSPoint defaultPosition = [self FrameView__closeButtonOrigin];
+ if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
+ return [(ToolbarWindow*)[self window] windowButtonsPositionWithDefaultPosition:defaultPosition];
+ }
+ return defaultPosition;
+}
+
+- (NSPoint)FrameView__fullScreenButtonOrigin
+{
+ NSPoint defaultPosition = [self FrameView__fullScreenButtonOrigin];
+ if ([[self window] isKindOfClass:[ToolbarWindow class]]) {
+ return [(ToolbarWindow*)[self window] fullScreenButtonPositionWithDefaultPosition:defaultPosition];
+ }
+ return defaultPosition;
+}
+
+@end
+
+static NSMutableSet *gSwizzledFrameViewClasses = nil;
+
+@interface NSWindow(PrivateSetNeedsDisplayInRectMethod)
+ - (void)_setNeedsDisplayInRect:(NSRect)aRect;
+@end
+
+// This method is on NSThemeFrame starting with 10.10, but since NSThemeFrame
+// is not a public class, we declare the method on NSView instead. We only have
+// this declaration in order to avoid compiler warnings.
+@interface NSView(PrivateAddKnownSubviewMethod)
+ - (void)_addKnownSubview:(NSView*)aView positioned:(NSWindowOrderingMode)place relativeTo:(NSView*)otherView;
+@end
+
+#if !defined(MAC_OS_X_VERSION_10_10) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10
+
+@interface NSImage(CapInsets)
+- (void)setCapInsets:(NSEdgeInsets)capInsets;
+@end
+
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_8) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
+
+@interface NSImage(ImageCreationWithDrawingHandler)
++ (NSImage *)imageWithSize:(NSSize)size
+ flipped:(BOOL)drawingHandlerShouldBeCalledWithFlippedContext
+ drawingHandler:(BOOL (^)(NSRect dstRect))drawingHandler;
+@end
+
+#endif
+
+@interface NSView(NSVisualEffectViewSetMaskImage)
+- (void)setMaskImage:(NSImage*)image;
+@end
+
+@interface BaseWindow(Private)
+- (void)removeTrackingArea;
+- (void)cursorUpdated:(NSEvent*)aEvent;
+- (void)updateContentViewSize;
+- (void)reflowTitlebarElements;
+@end
+
+@implementation BaseWindow
+
+// The frame of a window is implemented using undocumented NSView subclasses.
+// We offset the window buttons by overriding the methods _closeButtonOrigin
+// and _fullScreenButtonOrigin on these frame view classes. The class which is
+// used for a window is determined in the window's frameViewClassForStyleMask:
+// method, so this is where we make sure that we have swizzled the method on
+// all encountered classes.
++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask
+{
+ Class frameViewClass = [super frameViewClassForStyleMask:styleMask];
+
+ if (!gSwizzledFrameViewClasses) {
+ gSwizzledFrameViewClasses = [[NSMutableSet setWithCapacity:3] retain];
+ if (!gSwizzledFrameViewClasses) {
+ return frameViewClass;
+ }
+ }
+
+ static IMP our_closeButtonOrigin =
+ class_getMethodImplementation([NSView class],
+ @selector(FrameView__closeButtonOrigin));
+ static IMP our_fullScreenButtonOrigin =
+ class_getMethodImplementation([NSView class],
+ @selector(FrameView__fullScreenButtonOrigin));
+
+ if (![gSwizzledFrameViewClasses containsObject:frameViewClass]) {
+ // Either of these methods might be implemented in both a subclass of
+ // NSFrameView and one of its own subclasses. Which means that if we
+ // aren't careful we might end up swizzling the same method twice.
+ // Since method swizzling involves swapping pointers, this would break
+ // things.
+ IMP _closeButtonOrigin =
+ class_getMethodImplementation(frameViewClass,
+ @selector(_closeButtonOrigin));
+ if (_closeButtonOrigin && _closeButtonOrigin != our_closeButtonOrigin) {
+ nsToolkit::SwizzleMethods(frameViewClass, @selector(_closeButtonOrigin),
+ @selector(FrameView__closeButtonOrigin));
+ }
+ IMP _fullScreenButtonOrigin =
+ class_getMethodImplementation(frameViewClass,
+ @selector(_fullScreenButtonOrigin));
+ if (_fullScreenButtonOrigin &&
+ _fullScreenButtonOrigin != our_fullScreenButtonOrigin) {
+ nsToolkit::SwizzleMethods(frameViewClass, @selector(_fullScreenButtonOrigin),
+ @selector(FrameView__fullScreenButtonOrigin));
+ }
+ [gSwizzledFrameViewClasses addObject:frameViewClass];
+ }
+
+ return frameViewClass;
+}
+
+- (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
+{
+ mDrawsIntoWindowFrame = NO;
+ [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag];
+ mState = nil;
+ mActiveTitlebarColor = nil;
+ mInactiveTitlebarColor = nil;
+ mScheduledShadowInvalidation = NO;
+ mDisabledNeedsDisplay = NO;
+ mDPI = GetDPI(self);
+ mTrackingArea = nil;
+ mDirtyRect = NSZeroRect;
+ mBeingShown = NO;
+ mDrawTitle = NO;
+ mBrightTitlebarForeground = NO;
+ mUseMenuStyle = NO;
+ [self updateTrackingArea];
+
+ return self;
+}
+
+// Returns an autoreleased NSImage.
+static NSImage*
+GetMenuMaskImage()
+{
+ CGFloat radius = 4.0f;
+ NSEdgeInsets insets = { 5, 5, 5, 5 };
+ NSSize maskSize = { 12, 12 };
+ NSImage* maskImage = [NSImage imageWithSize:maskSize flipped:YES drawingHandler:^BOOL(NSRect dstRect) {
+ NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:dstRect xRadius:radius yRadius:radius];
+ [[NSColor colorWithDeviceWhite:1.0 alpha:1.0] set];
+ [path fill];
+ return YES;
+ }];
+ [maskImage setCapInsets:insets];
+ return maskImage;
+}
+
+- (void)swapOutChildViewWrapper:(NSView*)aNewWrapper
+{
+ [aNewWrapper setFrame:[[self contentView] frame]];
+ NSView* childView = [[self mainChildView] retain];
+ [childView removeFromSuperview];
+ [aNewWrapper addSubview:childView];
+ [childView release];
+ [super setContentView:aNewWrapper];
+}
+
+- (void)setUseMenuStyle:(BOOL)aValue
+{
+ if (!VibrancyManager::SystemSupportsVibrancy()) {
+ return;
+ }
+
+ if (aValue && !mUseMenuStyle) {
+ // Turn on rounded corner masking.
+ NSView* effectView = VibrancyManager::CreateEffectView(VibrancyType::MENU, YES);
+ if ([effectView respondsToSelector:@selector(setMaskImage:)]) {
+ [effectView setMaskImage:GetMenuMaskImage()];
+ }
+ [self swapOutChildViewWrapper:effectView];
+ [effectView release];
+ } else if (mUseMenuStyle && !aValue) {
+ // Turn off rounded corner masking.
+ NSView* wrapper = [[NSView alloc] initWithFrame:NSZeroRect];
+ [self swapOutChildViewWrapper:wrapper];
+ [wrapper release];
+ }
+ mUseMenuStyle = aValue;
+}
+
+- (void)setBeingShown:(BOOL)aValue
+{
+ mBeingShown = aValue;
+}
+
+- (BOOL)isBeingShown
+{
+ return mBeingShown;
+}
+
+- (BOOL)isVisibleOrBeingShown
+{
+ return [super isVisible] || mBeingShown;
+}
+
+- (void)disableSetNeedsDisplay
+{
+ mDisabledNeedsDisplay = YES;
+}
+
+- (void)enableSetNeedsDisplay
+{
+ mDisabledNeedsDisplay = NO;
+}
+
+- (void)dealloc
+{
+ [mActiveTitlebarColor release];
+ [mInactiveTitlebarColor release];
+ [self removeTrackingArea];
+ ChildViewMouseTracker::OnDestroyWindow(self);
+ [super dealloc];
+}
+
+static const NSString* kStateTitleKey = @"title";
+static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIntoWindowFrame";
+static const NSString* kStateActiveTitlebarColorKey = @"activeTitlebarColor";
+static const NSString* kStateInactiveTitlebarColorKey = @"inactiveTitlebarColor";
+static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
+static const NSString* kStateCollectionBehavior = @"collectionBehavior";
+
+- (void)importState:(NSDictionary*)aState
+{
+ [self setTitle:[aState objectForKey:kStateTitleKey]];
+ [self setDrawsContentsIntoWindowFrame:[[aState objectForKey:kStateDrawsContentsIntoWindowFrameKey] boolValue]];
+ [self setTitlebarColor:[aState objectForKey:kStateActiveTitlebarColorKey] forActiveWindow:YES];
+ [self setTitlebarColor:[aState objectForKey:kStateInactiveTitlebarColorKey] forActiveWindow:NO];
+ [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]];
+ [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior] unsignedIntValue]];
+}
+
+- (NSMutableDictionary*)exportState
+{
+ NSMutableDictionary* state = [NSMutableDictionary dictionaryWithCapacity:10];
+ [state setObject:[self title] forKey:kStateTitleKey];
+ [state setObject:[NSNumber numberWithBool:[self drawsContentsIntoWindowFrame]]
+ forKey:kStateDrawsContentsIntoWindowFrameKey];
+ NSColor* activeTitlebarColor = [self titlebarColorForActiveWindow:YES];
+ if (activeTitlebarColor) {
+ [state setObject:activeTitlebarColor forKey:kStateActiveTitlebarColorKey];
+ }
+ NSColor* inactiveTitlebarColor = [self titlebarColorForActiveWindow:NO];
+ if (inactiveTitlebarColor) {
+ [state setObject:inactiveTitlebarColor forKey:kStateInactiveTitlebarColorKey];
+ }
+ [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]]
+ forKey:kStateShowsToolbarButton];
+ [state setObject:[NSNumber numberWithUnsignedInt: [self collectionBehavior]]
+ forKey:kStateCollectionBehavior];
+ return state;
+}
+
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
+{
+ bool changed = (aState != mDrawsIntoWindowFrame);
+ mDrawsIntoWindowFrame = aState;
+ if (changed) {
+ [self updateContentViewSize];
+ [self reflowTitlebarElements];
+ }
+}
+
+- (BOOL)drawsContentsIntoWindowFrame
+{
+ return mDrawsIntoWindowFrame;
+}
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle
+{
+ mDrawTitle = aDrawTitle;
+}
+
+- (BOOL)wantsTitleDrawn
+{
+ return mDrawTitle;
+}
+
+- (void)setUseBrightTitlebarForeground:(BOOL)aBrightForeground
+{
+ mBrightTitlebarForeground = aBrightForeground;
+ [[self standardWindowButton:NSWindowFullScreenButton] setNeedsDisplay:YES];
+}
+
+- (BOOL)useBrightTitlebarForeground
+{
+ return mBrightTitlebarForeground;
+}
+
+// Pass nil here to get the default appearance.
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
+{
+ [aColor retain];
+ if (aActive) {
+ [mActiveTitlebarColor release];
+ mActiveTitlebarColor = aColor;
+ } else {
+ [mInactiveTitlebarColor release];
+ mInactiveTitlebarColor = aColor;
+ }
+}
+
+- (NSColor*)titlebarColorForActiveWindow:(BOOL)aActive
+{
+ return aActive ? mActiveTitlebarColor : mInactiveTitlebarColor;
+}
+
+- (void)deferredInvalidateShadow
+{
+ if (mScheduledShadowInvalidation || [self isOpaque] || ![self hasShadow])
+ return;
+
+ [self performSelector:@selector(invalidateShadow) withObject:nil afterDelay:0];
+ mScheduledShadowInvalidation = YES;
+}
+
+- (void)invalidateShadow
+{
+ [super invalidateShadow];
+ mScheduledShadowInvalidation = NO;
+}
+
+- (float)getDPI
+{
+ return mDPI;
+}
+
+- (NSView*)trackingAreaView
+{
+ NSView* contentView = [self contentView];
+ return [contentView superview] ? [contentView superview] : contentView;
+}
+
+- (ChildView*)mainChildView
+{
+ NSView *contentView = [self contentView];
+ NSView* lastView = [[contentView subviews] lastObject];
+ if ([lastView isKindOfClass:[ChildView class]]) {
+ return (ChildView*)lastView;
+ }
+ return nil;
+}
+
+- (void)removeTrackingArea
+{
+ if (mTrackingArea) {
+ [[self trackingAreaView] removeTrackingArea:mTrackingArea];
+ [mTrackingArea release];
+ mTrackingArea = nil;
+ }
+}
+
+- (void)updateTrackingArea
+{
+ [self removeTrackingArea];
+
+ NSView* view = [self trackingAreaView];
+ const NSTrackingAreaOptions options =
+ NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways;
+ mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
+ options:options
+ owner:self
+ userInfo:nil];
+ [view addTrackingArea:mTrackingArea];
+}
+
+- (void)mouseEntered:(NSEvent*)aEvent
+{
+ ChildViewMouseTracker::MouseEnteredWindow(aEvent);
+}
+
+- (void)mouseExited:(NSEvent*)aEvent
+{
+ ChildViewMouseTracker::MouseExitedWindow(aEvent);
+}
+
+- (void)mouseMoved:(NSEvent*)aEvent
+{
+ ChildViewMouseTracker::MouseMoved(aEvent);
+}
+
+- (void)cursorUpdated:(NSEvent*)aEvent
+{
+ // Nothing to do here, but NSTrackingArea wants us to implement this method.
+}
+
+- (void)_setNeedsDisplayInRect:(NSRect)aRect
+{
+ // Prevent unnecessary invalidations due to moving NSViews (e.g. for plugins)
+ if (!mDisabledNeedsDisplay) {
+ // This method is only called by Cocoa, so when we're here, we know that
+ // it's available and don't need to check whether our superclass responds
+ // to the selector.
+ [super _setNeedsDisplayInRect:aRect];
+ mDirtyRect = NSUnionRect(mDirtyRect, aRect);
+ }
+}
+
+- (NSRect)getAndResetNativeDirtyRect
+{
+ NSRect dirtyRect = mDirtyRect;
+ mDirtyRect = NSZeroRect;
+ return dirtyRect;
+}
+
+- (void)updateContentViewSize
+{
+ NSRect rect = [self contentRectForFrameRect:[self frame]];
+ [[self contentView] setFrameSize:rect.size];
+}
+
+// Possibly move the titlebar buttons.
+- (void)reflowTitlebarElements
+{
+ NSView *frameView = [[self contentView] superview];
+ if ([frameView respondsToSelector:@selector(_tileTitlebarAndRedisplay:)]) {
+ [frameView _tileTitlebarAndRedisplay:NO];
+ }
+}
+
+// Override methods that translate between content rect and frame rect.
+- (NSRect)contentRectForFrameRect:(NSRect)aRect
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ return [super contentRectForFrameRect:aRect];
+}
+
+- (NSRect)contentRectForFrameRect:(NSRect)aRect styleMask:(NSUInteger)aMask
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ if ([super respondsToSelector:@selector(contentRectForFrameRect:styleMask:)]) {
+ return [super contentRectForFrameRect:aRect styleMask:aMask];
+ } else {
+ return [NSWindow contentRectForFrameRect:aRect styleMask:aMask];
+ }
+}
+
+- (NSRect)frameRectForContentRect:(NSRect)aRect
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ return [super frameRectForContentRect:aRect];
+}
+
+- (NSRect)frameRectForContentRect:(NSRect)aRect styleMask:(NSUInteger)aMask
+{
+ if ([self drawsContentsIntoWindowFrame]) {
+ return aRect;
+ }
+ if ([super respondsToSelector:@selector(frameRectForContentRect:styleMask:)]) {
+ return [super frameRectForContentRect:aRect styleMask:aMask];
+ } else {
+ return [NSWindow frameRectForContentRect:aRect styleMask:aMask];
+ }
+}
+
+- (void)setContentView:(NSView*)aView
+{
+ [super setContentView:aView];
+
+ // Now move the contentView to the bottommost layer so that it's guaranteed
+ // to be under the window buttons.
+ NSView* frameView = [aView superview];
+ [aView removeFromSuperview];
+ if ([frameView respondsToSelector:@selector(_addKnownSubview:positioned:relativeTo:)]) {
+ // 10.10 prints a warning when we call addSubview on the frame view, so we
+ // silence the warning by calling a private method instead.
+ [frameView _addKnownSubview:aView positioned:NSWindowBelow relativeTo:nil];
+ } else {
+ [frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil];
+ }
+}
+
+- (NSArray*)titlebarControls
+{
+ // Return all subviews of the frameView which are not the content view.
+ NSView* frameView = [[self contentView] superview];
+ NSMutableArray* array = [[[frameView subviews] mutableCopy] autorelease];
+ [array removeObject:[self contentView]];
+ return array;
+}
+
+- (BOOL)respondsToSelector:(SEL)aSelector
+{
+ // Claim the window doesn't respond to this so that the system
+ // doesn't steal keyboard equivalents for it. Bug 613710.
+ if (aSelector == @selector(cancelOperation:)) {
+ return NO;
+ }
+
+ return [super respondsToSelector:aSelector];
+}
+
+- (void) doCommandBySelector:(SEL)aSelector
+{
+ // We override this so that it won't beep if it can't act.
+ // We want to control the beeping for missing or disabled
+ // commands ourselves.
+ [self tryToPerform:aSelector with:nil];
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ id retval = [super accessibilityAttributeValue:attribute];
+
+ // The following works around a problem with Text-to-Speech on OS X 10.7.
+ // See bug 674612 for more info.
+ //
+ // When accessibility is off, AXUIElementCopyAttributeValue(), when called
+ // on an AXApplication object to get its AXFocusedUIElement attribute,
+ // always returns an AXWindow object (the actual browser window -- never a
+ // mozAccessible object). This also happens with accessibility turned on,
+ // if no other object in the browser window has yet been focused. But if
+ // the browser window has a title bar (as it currently always does), the
+ // AXWindow object will always have four "accessible" children, one of which
+ // is an AXStaticText object (the title bar's "title"; the other three are
+ // the close, minimize and zoom buttons). This means that (for complicated
+ // reasons, for which see bug 674612) Text-to-Speech on OS X 10.7 will often
+ // "speak" the window title, no matter what text is selected, or even if no
+ // text at all is selected. (This always happens when accessibility is off.
+ // It doesn't happen in Firefox releases because Apple has (on OS X 10.7)
+ // special-cased the handling of apps whose CFBundleIdentifier is
+ // org.mozilla.firefox.)
+ //
+ // We work around this problem by only returning AXChildren that are
+ // mozAccessible object or are one of the titlebar's buttons (which
+ // instantiate subclasses of NSButtonCell).
+ if ([retval isKindOfClass:[NSArray class]] &&
+ [attribute isEqualToString:@"AXChildren"]) {
+ NSMutableArray *holder = [NSMutableArray arrayWithCapacity:10];
+ [holder addObjectsFromArray:(NSArray *)retval];
+ NSUInteger count = [holder count];
+ for (NSInteger i = count - 1; i >= 0; --i) {
+ id item = [holder objectAtIndex:i];
+ // Remove anything from holder that isn't one of the titlebar's buttons
+ // (which instantiate subclasses of NSButtonCell) or a mozAccessible
+ // object (or one of its subclasses).
+ if (![item isKindOfClass:[NSButtonCell class]] &&
+ ![item respondsToSelector:@selector(hasRepresentedView)]) {
+ [holder removeObjectAtIndex:i];
+ }
+ }
+ retval = [NSArray arrayWithArray:holder];
+ }
+
+ return retval;
+}
+
+@end
+
+// This class allows us to exercise control over the window's title bar. This
+// allows for a "unified toolbar" look without having to extend the content
+// area into the title bar. It works like this:
+// 1) We set the window's style to textured.
+// 2) Because of this, the background color applies to the entire window, including
+// the titlebar area. For normal textured windows, the default pattern is a
+// "brushed metal" image on Tiger and a unified gradient on Leopard.
+// 3) We set the background color to a custom NSColor subclass that knows how tall the window is.
+// When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback,
+// it paints the the titlebar and background colors in the correct areas of the context it's given,
+// which will fill the entire window (CG will tile it horizontally for us).
+// 4) Whenever the window's main state changes and when [window display] is called,
+// Cocoa redraws the titlebar using the patternDraw callback function.
+//
+// This class also provides us with a pill button to show/hide the toolbar up to 10.6.
+//
+// Drawing the unified gradient in the titlebar and the toolbar works like this:
+// 1) In the style sheet we set the toolbar's -moz-appearance to toolbar.
+// 2) When the toolbar is visible and we paint the application chrome
+// window, the array that Gecko passes nsChildView::UpdateThemeGeometries
+// will contain an entry for the widget type NS_THEME_TOOLBAR.
+// 3) nsChildView::UpdateThemeGeometries finds the toolbar frame's ToolbarWindow
+// and passes the toolbar frame's height to setUnifiedToolbarHeight.
+// 4) If the toolbar height has changed, a titlebar redraw is triggered and the
+// upper part of the unified gradient is drawn in the titlebar.
+// 5) The lower part of the unified gradient in the toolbar is drawn during
+// normal window content painting in nsNativeThemeCocoa::DrawUnifiedToolbar.
+//
+// Whenever the unified gradient is drawn in the titlebar or the toolbar, both
+// titlebar height and toolbar height must be known in order to construct the
+// correct gradient. But you can only get from the toolbar frame
+// to the containing window - the other direction doesn't work. That's why the
+// toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply
+// query the window for its titlebar height when drawing the toolbar.
+//
+// Note that in drawsContentsIntoWindowFrame mode, titlebar drawing works in a
+// completely different way: In that mode, the window's mainChildView will
+// cover the titlebar completely and nothing that happens in the window
+// background will reach the screen.
+@implementation ToolbarWindow
+
+- (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ aStyle = aStyle | NSTexturedBackgroundWindowMask;
+ if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) {
+ mColor = [[TitlebarAndBackgroundColor alloc] initWithWindow:self];
+ // Bypass our guard method below.
+ [super setBackgroundColor:mColor];
+ mBackgroundColor = [[NSColor whiteColor] retain];
+
+ mUnifiedToolbarHeight = 22.0f;
+ mWindowButtonsRect = NSZeroRect;
+ mFullScreenButtonRect = NSZeroRect;
+
+ // setBottomCornerRounded: is a private API call, so we check to make sure
+ // we respond to it just in case.
+ if ([self respondsToSelector:@selector(setBottomCornerRounded:)])
+ [self setBottomCornerRounded:YES];
+
+ [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
+ [self setContentBorderThickness:0.0f forEdge:NSMaxYEdge];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [super setBackgroundColor:[NSColor whiteColor]];
+ [mColor release];
+ [mBackgroundColor release];
+ [mTitlebarView release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
+{
+ [super setTitlebarColor:aColor forActiveWindow:aActive];
+ [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
+}
+
+- (void)setBackgroundColor:(NSColor*)aColor
+{
+ [aColor retain];
+ [mBackgroundColor release];
+ mBackgroundColor = aColor;
+}
+
+- (NSColor*)windowBackgroundColor
+{
+ return mBackgroundColor;
+}
+
+- (void)setTemporaryBackgroundColor
+{
+ [super setBackgroundColor:[NSColor whiteColor]];
+}
+
+- (void)restoreBackgroundColor
+{
+ [super setBackgroundColor:mBackgroundColor];
+}
+
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect
+{
+ [self setTitlebarNeedsDisplayInRect:aRect sync:NO];
+}
+
+- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync
+{
+ NSRect titlebarRect = [self titlebarRect];
+ NSRect rect = NSIntersectionRect(titlebarRect, aRect);
+ if (NSIsEmptyRect(rect))
+ return;
+
+ NSView* borderView = [[self contentView] superview];
+ if (!borderView)
+ return;
+
+ if (aSync) {
+ [borderView displayRect:rect];
+ } else {
+ [borderView setNeedsDisplayInRect:rect];
+ }
+}
+
+- (NSRect)titlebarRect
+{
+ CGFloat titlebarHeight = [self titlebarHeight];
+ return NSMakeRect(0, [self frame].size.height - titlebarHeight,
+ [self frame].size.width, titlebarHeight);
+}
+
+// Returns the unified height of titlebar + toolbar.
+- (CGFloat)unifiedToolbarHeight
+{
+ return mUnifiedToolbarHeight;
+}
+
+- (CGFloat)titlebarHeight
+{
+ // We use the original content rect here, not what we return from
+ // [self contentRectForFrameRect:], because that would give us a
+ // titlebarHeight of zero in drawsContentsIntoWindowFrame mode.
+ NSRect frameRect = [self frame];
+ NSRect originalContentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[self styleMask]];
+ return NSMaxY(frameRect) - NSMaxY(originalContentRect);
+}
+
+// Stores the complete height of titlebar + toolbar.
+- (void)setUnifiedToolbarHeight:(CGFloat)aHeight
+{
+ if (aHeight == mUnifiedToolbarHeight)
+ return;
+
+ mUnifiedToolbarHeight = aHeight;
+
+ if (![self drawsContentsIntoWindowFrame]) {
+ // Redraw the title bar. If we're inside painting, we'll do it right now,
+ // otherwise we'll just invalidate it.
+ BOOL needSyncRedraw = ([NSView focusView] != nil);
+ [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw];
+ }
+}
+
+// Extending the content area into the title bar works by resizing the
+// mainChildView so that it covers the titlebar.
+- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
+{
+ BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
+ [super setDrawsContentsIntoWindowFrame:aState];
+ if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
+ // Here we extend / shrink our mainChildView. We do that by firing a resize
+ // event which will cause the ChildView to be resized to the rect returned
+ // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
+ // value on what we return from drawsContentsIntoWindowFrame.
+ WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
+ nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
+ if (geckoWindow) {
+ // Re-layout our contents.
+ geckoWindow->ReportSizeEvent();
+ }
+
+ // Resizing the content area causes a reflow which would send a synthesized
+ // mousemove event to the old mouse position relative to the top left
+ // corner of the content area. But the mouse has shifted relative to the
+ // content area, so that event would have wrong position information. So
+ // we'll send a mouse move event with the correct new position.
+ ChildViewMouseTracker::ResendLastMouseMoveEvent();
+ }
+}
+
+- (void)setWantsTitleDrawn:(BOOL)aDrawTitle
+{
+ [super setWantsTitleDrawn:aDrawTitle];
+ [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
+}
+
+- (void)setSheetAttachmentPosition:(CGFloat)aY
+{
+ CGFloat topMargin = aY - [self titlebarHeight];
+ [self setContentBorderThickness:topMargin forEdge:NSMaxYEdge];
+}
+
+- (void)placeWindowButtons:(NSRect)aRect
+{
+ if (!NSEqualRects(mWindowButtonsRect, aRect)) {
+ mWindowButtonsRect = aRect;
+ [self reflowTitlebarElements];
+ }
+}
+
+- (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition
+{
+ NSInteger styleMask = [self styleMask];
+ if ([self drawsContentsIntoWindowFrame] &&
+ !(styleMask & NSFullScreenWindowMask) && (styleMask & NSTitledWindowMask)) {
+ if (NSIsEmptyRect(mWindowButtonsRect)) {
+ // Empty rect. Let's hide the buttons.
+ // Position is in non-flipped window coordinates. Using frame's height
+ // for the vertical coordinate will move the buttons above the window,
+ // making them invisible.
+ return NSMakePoint(0, [self frame].size.height);
+ }
+ return NSMakePoint(mWindowButtonsRect.origin.x, mWindowButtonsRect.origin.y);
+ }
+ return aDefaultPosition;
+}
+
+- (void)placeFullScreenButton:(NSRect)aRect
+{
+ if (!NSEqualRects(mFullScreenButtonRect, aRect)) {
+ mFullScreenButtonRect = aRect;
+ [self reflowTitlebarElements];
+ }
+}
+
+- (NSPoint)fullScreenButtonPositionWithDefaultPosition:(NSPoint)aDefaultPosition
+{
+ if ([self drawsContentsIntoWindowFrame] && !NSIsEmptyRect(mFullScreenButtonRect)) {
+ return NSMakePoint(std::min(mFullScreenButtonRect.origin.x, aDefaultPosition.x),
+ std::min(mFullScreenButtonRect.origin.y, aDefaultPosition.y));
+ }
+ return aDefaultPosition;
+}
+
+// Returning YES here makes the setShowsToolbarButton method work even though
+// the window doesn't contain an NSToolbar.
+- (BOOL)_hasToolbar
+{
+ return YES;
+}
+
+// Dispatch a toolbar pill button clicked message to Gecko.
+- (void)_toolbarPillButtonClicked:(id)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ RollUpPopups();
+
+ if ([[self delegate] isKindOfClass:[WindowDelegate class]]) {
+ WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
+ nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
+ if (!geckoWindow)
+ return;
+
+ nsIWidgetListener* listener = geckoWindow->GetWidgetListener();
+ if (listener)
+ listener->OSToolbarButtonPressed();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Retain and release "self" to avoid crashes when our widget (and its native
+// window) is closed as a result of processing a key equivalent (e.g.
+// Command+w or Command+q). This workaround is only needed for a window
+// that can become key.
+- (BOOL)performKeyEquivalent:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSWindow *nativeWindow = [self retain];
+ BOOL retval = [super performKeyEquivalent:theEvent];
+ [nativeWindow release];
+ return retval;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (void)sendEvent:(NSEvent *)anEvent
+{
+ NSEventType type = [anEvent type];
+
+ switch (type) {
+ case NSScrollWheel:
+ case NSLeftMouseDown:
+ case NSLeftMouseUp:
+ case NSRightMouseDown:
+ case NSRightMouseUp:
+ case NSOtherMouseDown:
+ case NSOtherMouseUp:
+ case NSMouseMoved:
+ case NSLeftMouseDragged:
+ case NSRightMouseDragged:
+ case NSOtherMouseDragged:
+ {
+ // Drop all mouse events if a modal window has appeared above us.
+ // This helps make us behave as if the OS were running a "real" modal
+ // event loop.
+ id delegate = [self delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
+ nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
+ if (widget) {
+ if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
+ return;
+ if (widget->HasModalDescendents())
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ [super sendEvent:anEvent];
+}
+
+@end
+
+// Custom NSColor subclass where most of the work takes place for drawing in
+// the titlebar area. Not used in drawsContentsIntoWindowFrame mode.
+@implementation TitlebarAndBackgroundColor
+
+- (id)initWithWindow:(ToolbarWindow*)aWindow
+{
+ if ((self = [super init])) {
+ mWindow = aWindow; // weak ref to avoid a cycle
+ }
+ return self;
+}
+
+static void
+DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedToolbarHeight, BOOL aIsMain)
+{
+ nsNativeThemeCocoa::DrawNativeTitlebar(aContext, aTitlebarRect, aUnifiedToolbarHeight, aIsMain, NO);
+
+ // The call to CUIDraw doesn't draw the top pixel strip at some window widths.
+ // We don't want to have a flickering transparent line, so we overdraw it.
+ CGContextSetRGBFillColor(aContext, 0.95, 0.95, 0.95, 1);
+ CGContextFillRect(aContext, CGRectMake(0, CGRectGetMaxY(aTitlebarRect) - 1,
+ aTitlebarRect.size.width, 1));
+}
+
+// Pattern draw callback for standard titlebar gradients and solid titlebar colors
+static void
+TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
+{
+ ToolbarWindow *window = (ToolbarWindow*)aInfo;
+ if (![window drawsContentsIntoWindowFrame]) {
+ NSRect titlebarRect = [window titlebarRect];
+ BOOL isMain = [window isMainWindow];
+ NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];
+ if (!titlebarColor) {
+ // If the titlebar color is nil, draw the default titlebar shading.
+ DrawNativeTitlebar(aContext, NSRectToCGRect(titlebarRect),
+ [window unifiedToolbarHeight], isMain);
+ } else {
+ // If the titlebar color is not nil, just set and draw it normally.
+ [NSGraphicsContext saveGraphicsState];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:aContext flipped:NO]];
+ [titlebarColor set];
+ NSRectFill(titlebarRect);
+ [NSGraphicsContext restoreGraphicsState];
+ }
+ }
+}
+
+- (void)setFill
+{
+ float patternWidth = [mWindow frame].size.width;
+
+ CGPatternCallbacks callbacks = {0, &TitlebarDrawCallback, NULL};
+ CGPatternRef pattern = CGPatternCreate(mWindow, CGRectMake(0.0f, 0.0f, patternWidth, [mWindow frame].size.height),
+ CGAffineTransformIdentity, patternWidth, [mWindow frame].size.height,
+ kCGPatternTilingConstantSpacing, true, &callbacks);
+
+ // Set the pattern as the fill, which is what we were asked to do. All our
+ // drawing will take place in the patternDraw callback.
+ CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
+ CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSetFillColorSpace(context, patternSpace);
+ CGColorSpaceRelease(patternSpace);
+ CGFloat component = 1.0f;
+ CGContextSetFillPattern(context, pattern, &component);
+ CGPatternRelease(pattern);
+}
+
+- (void)set
+{
+ [self setFill];
+}
+
+- (NSString*)colorSpaceName
+{
+ return NSDeviceRGBColorSpace;
+}
+
+@end
+
+@implementation PopupWindow
+
+- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
+ backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ mIsContextMenu = false;
+ return [super initWithContentRect:contentRect styleMask:styleMask
+ backing:bufferingType defer:deferCreation];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isContextMenu
+{
+ return mIsContextMenu;
+}
+
+- (void)setIsContextMenu:(BOOL)flag
+{
+ mIsContextMenu = flag;
+}
+
+- (BOOL)canBecomeMainWindow
+{
+ // This is overriden because the default is 'yes' when a titlebar is present.
+ return NO;
+}
+
+@end
+
+// According to Apple's docs on [NSWindow canBecomeKeyWindow] and [NSWindow
+// canBecomeMainWindow], windows without a title bar or resize bar can't (by
+// default) become key or main. But if a window can't become key, it can't
+// accept keyboard input (bmo bug 393250). And it should also be possible for
+// an otherwise "ordinary" window to become main. We need to override these
+// two methods to make this happen.
+@implementation BorderlessWindow
+
+- (BOOL)canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (void)sendEvent:(NSEvent *)anEvent
+{
+ NSEventType type = [anEvent type];
+
+ switch (type) {
+ case NSScrollWheel:
+ case NSLeftMouseDown:
+ case NSLeftMouseUp:
+ case NSRightMouseDown:
+ case NSRightMouseUp:
+ case NSOtherMouseDown:
+ case NSOtherMouseUp:
+ case NSMouseMoved:
+ case NSLeftMouseDragged:
+ case NSRightMouseDragged:
+ case NSOtherMouseDragged:
+ {
+ // Drop all mouse events if a modal window has appeared above us.
+ // This helps make us behave as if the OS were running a "real" modal
+ // event loop.
+ id delegate = [self delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
+ nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
+ if (widget) {
+ if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
+ return;
+ if (widget->HasModalDescendents())
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ [super sendEvent:anEvent];
+}
+
+// Apple's doc on this method says that the NSWindow class's default is not to
+// become main if the window isn't "visible" -- so we should replicate that
+// behavior here. As best I can tell, the [NSWindow isVisible] method is an
+// accurate test of what Apple means by "visibility".
+- (BOOL)canBecomeMainWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (![self isVisible])
+ return NO;
+ return YES;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Retain and release "self" to avoid crashes when our widget (and its native
+// window) is closed as a result of processing a key equivalent (e.g.
+// Command+w or Command+q). This workaround is only needed for a window
+// that can become key.
+- (BOOL)performKeyEquivalent:(NSEvent*)theEvent
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ NSWindow *nativeWindow = [self retain];
+ BOOL retval = [super performKeyEquivalent:theEvent];
+ [nativeWindow release];
+ return retval;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+@end
diff --git a/widget/cocoa/nsColorPicker.h b/widget/cocoa/nsColorPicker.h
new file mode 100644
index 0000000000..4b3e262188
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsColorPicker_h_
+#define nsColorPicker_h_
+
+#include "nsIColorPicker.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsIColorPickerShownCallback;
+class mozIDOMWindowProxy;
+@class NSColorPanelWrapper;
+@class NSColor;
+
+class nsColorPicker final : public nsIColorPicker
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor) override;
+ NS_IMETHOD Open(nsIColorPickerShownCallback* aCallback) override;
+
+ // For NSColorPanelWrapper.
+ void Update(NSColor* aColor);
+ // Call this method if you are done with this input, but the color picker needs to
+ // stay open as it will be associated to another input
+ void DoneWithRetarget();
+ // Same as DoneWithRetarget + clean the static instance of sColorPanelWrapper,
+ // as it is not needed anymore for now
+ void Done();
+
+private:
+ ~nsColorPicker();
+
+ static NSColor* GetNSColorFromHexString(const nsAString& aColor);
+ static void GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult);
+
+ static NSColorPanelWrapper* sColorPanelWrapper;
+
+ nsString mTitle;
+ nsString mColor;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+};
+
+#endif // nsColorPicker_h_
diff --git a/widget/cocoa/nsColorPicker.mm b/widget/cocoa/nsColorPicker.mm
new file mode 100644
index 0000000000..263ea349b0
--- /dev/null
+++ b/widget/cocoa/nsColorPicker.mm
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsColorPicker.h"
+#include "nsCocoaUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+static unsigned int
+HexStrToInt(NSString* str)
+{
+ unsigned int result = 0;
+
+ for (unsigned int i = 0; i < [str length]; ++i) {
+ char c = [str characterAtIndex:i];
+ result *= 16;
+ if (c >= '0' && c <= '9') {
+ result += c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ result += 10 + (c - 'A');
+ } else {
+ result += 10 + (c - 'a');
+ }
+ }
+
+ return result;
+}
+
+@interface NSColorPanelWrapper : NSObject <NSWindowDelegate>
+{
+ NSColorPanel* mColorPanel;
+ nsColorPicker* mColorPicker;
+}
+- (id)initWithPicker:(nsColorPicker*)aPicker;
+- (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle;
+- (void)retarget:(nsColorPicker*)aPicker;
+- (void)colorChanged:(NSColorPanel*)aPanel;
+@end
+
+@implementation NSColorPanelWrapper
+- (id)initWithPicker:(nsColorPicker*)aPicker
+{
+ mColorPicker = aPicker;
+ mColorPanel = [NSColorPanel sharedColorPanel];
+
+ self = [super init];
+ return self;
+}
+
+- (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle
+{
+ [mColorPanel setTarget:self];
+ [mColorPanel setAction:@selector(colorChanged:)];
+ [mColorPanel setDelegate:self];
+ [mColorPanel setTitle:aTitle];
+ [mColorPanel setColor:aInitialColor];
+ [mColorPanel makeKeyAndOrderFront:nil];
+}
+
+- (void)colorChanged:(NSColorPanel*)aPanel
+{
+ mColorPicker->Update([mColorPanel color]);
+}
+
+- (void)windowWillClose:(NSNotification*)aNotification
+{
+ mColorPicker->Done();
+}
+
+- (void)retarget:(nsColorPicker*)aPicker
+{
+ mColorPicker->DoneWithRetarget();
+ mColorPicker = aPicker;
+}
+
+- (void)dealloc
+{
+ [mColorPanel setTarget:nil];
+ [mColorPanel setAction:nil];
+ [mColorPanel setDelegate:nil];
+
+ mColorPanel = nil;
+ mColorPicker = nullptr;
+
+ [super dealloc];
+}
+@end
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+NSColorPanelWrapper* nsColorPicker::sColorPanelWrapper = nullptr;
+
+nsColorPicker::~nsColorPicker()
+{
+}
+
+NS_IMETHODIMP
+nsColorPicker::Init(mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ const nsAString& aInitialColor)
+{
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Color pickers can only be opened from main thread currently");
+ mTitle = aTitle;
+ mColor = aInitialColor;
+
+ if (sColorPanelWrapper) {
+ // Update current wrapper to target the new input instead
+ [sColorPanelWrapper retarget:this];
+ } else {
+ // Create a brand new color panel wrapper
+ sColorPanelWrapper = [[NSColorPanelWrapper alloc] initWithPicker:this];
+ }
+ return NS_OK;
+}
+
+/* static */ NSColor*
+nsColorPicker::GetNSColorFromHexString(const nsAString& aColor)
+{
+ NSString* str = nsCocoaUtils::ToNSString(aColor);
+
+ double red = HexStrToInt([str substringWithRange:NSMakeRange(1, 2)]) / 255.0;
+ double green = HexStrToInt([str substringWithRange:NSMakeRange(3, 2)]) / 255.0;
+ double blue = HexStrToInt([str substringWithRange:NSMakeRange(5, 2)]) / 255.0;
+
+ return [NSColor colorWithDeviceRed: red green: green blue: blue alpha: 1.0];
+}
+
+/* static */ void
+nsColorPicker::GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult)
+{
+ CGFloat redFloat, greenFloat, blueFloat;
+
+ NSColor* color = aColor;
+ @try {
+ [color getRed:&redFloat green:&greenFloat blue:&blueFloat alpha: nil];
+ } @catch (NSException* e) {
+ color = [color colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]];
+ [color getRed:&redFloat green:&greenFloat blue:&blueFloat alpha: nil];
+ }
+
+ nsCocoaUtils::GetStringForNSString([NSString stringWithFormat:@"#%02x%02x%02x",
+ (int)(redFloat * 255),
+ (int)(greenFloat * 255),
+ (int)(blueFloat * 255)],
+ aResult);
+}
+
+NS_IMETHODIMP
+nsColorPicker::Open(nsIColorPickerShownCallback* aCallback)
+{
+ MOZ_ASSERT(aCallback);
+ mCallback = aCallback;
+
+ [sColorPanelWrapper open:GetNSColorFromHexString(mColor)
+ title:nsCocoaUtils::ToNSString(mTitle)];
+
+ NS_ADDREF_THIS();
+
+ return NS_OK;
+}
+
+void
+nsColorPicker::Update(NSColor* aColor)
+{
+ GetHexStringFromNSColor(aColor, mColor);
+ mCallback->Update(mColor);
+}
+
+void
+nsColorPicker::DoneWithRetarget()
+{
+ mCallback->Done(EmptyString());
+ mCallback = nullptr;
+ NS_RELEASE_THIS();
+}
+
+void
+nsColorPicker::Done()
+{
+ [sColorPanelWrapper release];
+ sColorPanelWrapper = nullptr;
+ DoneWithRetarget();
+}
diff --git a/widget/cocoa/nsCursorManager.h b/widget/cocoa/nsCursorManager.h
new file mode 100644
index 0000000000..6dba8f9034
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.h
@@ -0,0 +1,65 @@
+/* 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/. */
+
+#ifndef nsCursorManager_h_
+#define nsCursorManager_h_
+
+#import <Foundation/Foundation.h>
+#include "nsIWidget.h"
+#include "nsMacCursor.h"
+
+/*! @class nsCursorManager
+ @abstract Singleton service provides access to all cursors available in the application.
+ @discussion Use <code>nsCusorManager</code> to set the current cursor using an XP <code>nsCusor</code> enum value.
+ <code>nsCursorManager</code> encapsulates the details of setting different types of cursors, animating
+ cursors and cleaning up cursors when they are no longer in use.
+ */
+@interface nsCursorManager : NSObject
+{
+ @private
+ NSMutableDictionary *mCursors;
+ nsMacCursor *mCurrentMacCursor;
+}
+
+/*! @method setCursor:
+ @abstract Sets the current cursor.
+ @discussion Sets the current cursor to the cursor indicated by the XP cursor constant given as an argument.
+ Resources associated with the previous cursor are cleaned up.
+ @param aCursor the cursor to use
+*/
+- (nsresult) setCursor: (nsCursor) aCursor;
+
+/*! @method setCursorWithImage:hotSpotX:hotSpotY:
+ @abstract Sets the current cursor to a custom image
+ @discussion Sets the current cursor to the cursor given by the aCursorImage argument.
+ Resources associated with the previous cursor are cleaned up.
+ @param aCursorImage the cursor image to use
+ @param aHotSpotX the x coordinate of the cursor's hotspot
+ @param aHotSpotY the y coordinate of the cursor's hotspot
+ @param scaleFactor the scale factor of the target display (2 for a retina display)
+ */
+- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor;
+
+
+/*! @method sharedInstance
+ @abstract Get the Singleton instance of the cursor manager.
+ @discussion Use this method to obtain a reference to the cursor manager.
+ @result a reference to the cursor manager
+*/
++ (nsCursorManager *) sharedInstance;
+
+/*! @method dispose
+ @abstract Releases the shared instance of the cursor manager.
+ @discussion Use dispose to clean up the cursor manager and associated cursors.
+*/
++ (void) dispose;
+@end
+
+@interface NSCursor (Undocumented)
+// busyButClickableCursor is an undocumented NSCursor API, but has been in use since
+// at least OS X 10.4 and through 10.9.
++ (NSCursor*)busyButClickableCursor;
+@end
+
+#endif // nsCursorManager_h_
diff --git a/widget/cocoa/nsCursorManager.mm b/widget/cocoa/nsCursorManager.mm
new file mode 100644
index 0000000000..c4281a438a
--- /dev/null
+++ b/widget/cocoa/nsCursorManager.mm
@@ -0,0 +1,308 @@
+/* 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 "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "nsCursorManager.h"
+#include "nsObjCExceptions.h"
+#include <math.h>
+
+static nsCursorManager *gInstance;
+static CGFloat sCursorScaleFactor = 0.0f;
+static imgIContainer *sCursorImgContainer = nullptr;
+static const nsCursor sCustomCursor = eCursorCount;
+
+/*! @category nsCursorManager(PrivateMethods)
+ Private methods for the cursor manager class.
+*/
+@interface nsCursorManager(PrivateMethods)
+/*! @method getCursor:
+ @abstract Get a reference to the native Mac representation of a cursor.
+ @discussion Gets a reference to the Mac native implementation of a cursor.
+ If the cursor has been requested before, it is retreived from the cursor cache,
+ otherwise it is created and cached.
+ @param aCursor the cursor to get
+ @result the Mac native implementation of the cursor
+*/
+- (nsMacCursor *) getCursor: (nsCursor) aCursor;
+
+/*! @method setMacCursor:
+ @abstract Set the current Mac native cursor
+ @discussion Sets the current cursor - this routine is what actually causes the cursor to change.
+ The argument is retained and the old cursor is released.
+ @param aMacCursor the cursor to set
+ @result NS_OK
+ */
+- (nsresult) setMacCursor: (nsMacCursor*) aMacCursor;
+
+/*! @method createCursor:
+ @abstract Create a Mac native representation of a cursor.
+ @discussion Creates a version of the Mac native representation of this cursor
+ @param aCursor the cursor to create
+ @result the Mac native implementation of the cursor
+*/
++ (nsMacCursor *) createCursor: (enum nsCursor) aCursor;
+
+@end
+
+@implementation nsCursorManager
+
++ (nsCursorManager *) sharedInstance
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gInstance) {
+ gInstance = [[nsCursorManager alloc] init];
+ }
+ return gInstance;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (void) dispose
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [gInstance release];
+ gInstance = nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
++ (nsMacCursor *) createCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ switch(aCursor)
+ {
+ SEL cursorSelector;
+ case eCursor_standard:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ case eCursor_wait:
+ case eCursor_spinning:
+ {
+ return [nsMacCursor cursorWithCursor:[NSCursor busyButClickableCursor] type:aCursor];
+ }
+ case eCursor_select:
+ return [nsMacCursor cursorWithCursor:[NSCursor IBeamCursor] type:aCursor];
+ case eCursor_hyperlink:
+ return [nsMacCursor cursorWithCursor:[NSCursor pointingHandCursor] type:aCursor];
+ case eCursor_crosshair:
+ return [nsMacCursor cursorWithCursor:[NSCursor crosshairCursor] type:aCursor];
+ case eCursor_move:
+ return [nsMacCursor cursorWithImageNamed:@"move" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_help:
+ return [nsMacCursor cursorWithImageNamed:@"help" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_copy:
+ cursorSelector = @selector(dragCopyCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_alias:
+ cursorSelector = @selector(dragLinkCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_context_menu:
+ cursorSelector = @selector(contextualMenuCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ case eCursor_cell:
+ return [nsMacCursor cursorWithImageNamed:@"cell" hotSpot:NSMakePoint(12,12) type:aCursor];
+ case eCursor_grab:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_grabbing:
+ return [nsMacCursor cursorWithCursor:[NSCursor closedHandCursor] type:aCursor];
+ case eCursor_zoom_in:
+ return [nsMacCursor cursorWithImageNamed:@"zoomIn" hotSpot:NSMakePoint(10,10) type:aCursor];
+ case eCursor_zoom_out:
+ return [nsMacCursor cursorWithImageNamed:@"zoomOut" hotSpot:NSMakePoint(10,10) type:aCursor];
+ case eCursor_vertical_text:
+ return [nsMacCursor cursorWithImageNamed:@"vtIBeam" hotSpot:NSMakePoint(12,11) type:aCursor];
+ case eCursor_all_scroll:
+ return [nsMacCursor cursorWithCursor:[NSCursor openHandCursor] type:aCursor];
+ case eCursor_not_allowed:
+ case eCursor_no_drop:
+ cursorSelector = @selector(operationNotAllowedCursor);
+ return [nsMacCursor cursorWithCursor:[NSCursor respondsToSelector:cursorSelector] ?
+ [NSCursor performSelector:cursorSelector] :
+ [NSCursor arrowCursor] type:aCursor];
+ // Resize Cursors:
+ // North
+ case eCursor_n_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpCursor] type:aCursor];
+ // North East
+ case eCursor_ne_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNE" hotSpot:NSMakePoint(12,11) type:aCursor];
+ // East
+ case eCursor_e_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeRightCursor] type:aCursor];
+ // South East
+ case eCursor_se_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSE" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // South
+ case eCursor_s_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeDownCursor] type:aCursor];
+ // South West
+ case eCursor_sw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeSW" hotSpot:NSMakePoint(10,12) type:aCursor];
+ // West
+ case eCursor_w_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftCursor] type:aCursor];
+ // North West
+ case eCursor_nw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNW" hotSpot:NSMakePoint(11,11) type:aCursor];
+ // North & South
+ case eCursor_ns_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeUpDownCursor] type:aCursor];
+ // East & West
+ case eCursor_ew_resize:
+ return [nsMacCursor cursorWithCursor:[NSCursor resizeLeftRightCursor] type:aCursor];
+ // North East & South West
+ case eCursor_nesw_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNESW" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // North West & South East
+ case eCursor_nwse_resize:
+ return [nsMacCursor cursorWithImageNamed:@"sizeNWSE" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // Column Resize
+ case eCursor_col_resize:
+ return [nsMacCursor cursorWithImageNamed:@"colResize" hotSpot:NSMakePoint(12,12) type:aCursor];
+ // Row Resize
+ case eCursor_row_resize:
+ return [nsMacCursor cursorWithImageNamed:@"rowResize" hotSpot:NSMakePoint(12,12) type:aCursor];
+ default:
+ return [nsMacCursor cursorWithCursor:[NSCursor arrowCursor] type:aCursor];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) init
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mCursors = [[NSMutableDictionary alloc] initWithCapacity:25];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (nsresult) setCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Some plugins mess with our cursors and set a cursor that even
+ // [NSCursor currentCursor] doesn't know about. In case that happens, just
+ // reset the state.
+ [[NSCursor currentCursor] set];
+
+ nsCursor oldType = [mCurrentMacCursor type];
+ if (oldType != aCursor) {
+ if (aCursor == eCursor_none) {
+ [NSCursor hide];
+ } else if (oldType == eCursor_none) {
+ [NSCursor unhide];
+ }
+ }
+ [self setMacCursor:[self getCursor:aCursor]];
+
+ // if a custom cursor was previously set, release sCursorImgContainer
+ if (oldType == sCustomCursor) {
+ NS_IF_RELEASE(sCursorImgContainer);
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsresult) setMacCursor: (nsMacCursor*) aMacCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mCurrentMacCursor != aMacCursor || ![mCurrentMacCursor isSet]) {
+ [aMacCursor retain];
+ [mCurrentMacCursor unset];
+ [aMacCursor set];
+ [mCurrentMacCursor release];
+ mCurrentMacCursor = aMacCursor;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsresult) setCursorWithImage: (imgIContainer*) aCursorImage hotSpotX: (uint32_t) aHotspotX hotSpotY: (uint32_t) aHotspotY scaleFactor: (CGFloat) scaleFactor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+ // As the user moves the mouse, this gets called repeatedly with the same aCursorImage
+ if (sCursorImgContainer == aCursorImage && sCursorScaleFactor == scaleFactor && mCurrentMacCursor) {
+ [self setMacCursor:mCurrentMacCursor];
+ return NS_OK;
+ }
+
+ [[NSCursor currentCursor] set];
+ int32_t width = 0, height = 0;
+ aCursorImage->GetWidth(&width);
+ aCursorImage->GetHeight(&height);
+ // prevent DoS attacks
+ if (width > 128 || height > 128) {
+ return NS_OK;
+ }
+
+ NSImage *cursorImage;
+ nsresult rv = nsCocoaUtils::CreateNSImageFromImageContainer(aCursorImage, imgIContainer::FRAME_FIRST, &cursorImage, scaleFactor);
+ if (NS_FAILED(rv) || !cursorImage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // if the hotspot is nonsensical, make it 0,0
+ aHotspotX = (aHotspotX > (uint32_t)width - 1) ? 0 : aHotspotX;
+ aHotspotY = (aHotspotY > (uint32_t)height - 1) ? 0 : aHotspotY;
+
+ NSPoint hotSpot = ::NSMakePoint(aHotspotX, aHotspotY);
+ [self setMacCursor:[nsMacCursor cursorWithCursor:[[NSCursor alloc] initWithImage:cursorImage hotSpot:hotSpot] type:sCustomCursor]];
+ [cursorImage release];
+
+ NS_IF_RELEASE(sCursorImgContainer);
+ sCursorImgContainer = aCursorImage;
+ sCursorScaleFactor = scaleFactor;
+ NS_ADDREF(sCursorImgContainer);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+- (nsMacCursor *) getCursor: (enum nsCursor) aCursor
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsMacCursor * result = [mCursors objectForKey:[NSNumber numberWithInt:aCursor]];
+ if (!result) {
+ result = [nsCursorManager createCursor:aCursor];
+ [mCursors setObject:result forKey:[NSNumber numberWithInt:aCursor]];
+ }
+ return result;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mCurrentMacCursor unset];
+ [mCurrentMacCursor release];
+ [mCursors release];
+ NS_IF_RELEASE(sCursorImgContainer);
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/nsDeviceContextSpecX.h b/widget/cocoa/nsDeviceContextSpecX.h
new file mode 100644
index 0000000000..2df52418a7
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsDeviceContextSpecX_h_
+#define nsDeviceContextSpecX_h_
+
+#include "nsIDeviceContextSpec.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+class nsDeviceContextSpecX : public nsIDeviceContextSpec
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsDeviceContextSpecX();
+
+ NS_IMETHOD Init(nsIWidget *aWidget, nsIPrintSettings* aPS, bool aIsPrintPreview) override;
+ virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override;
+ NS_IMETHOD EndPage() override;
+
+ void GetPaperRect(double* aTop, double* aLeft, double* aBottom, double* aRight);
+
+protected:
+ virtual ~nsDeviceContextSpecX();
+
+protected:
+ PMPrintSession mPrintSession; // printing context.
+ PMPageFormat mPageFormat; // page format.
+ PMPrintSettings mPrintSettings; // print settings.
+};
+
+#endif //nsDeviceContextSpecX_h_
diff --git a/widget/cocoa/nsDeviceContextSpecX.mm b/widget/cocoa/nsDeviceContextSpecX.mm
new file mode 100644
index 0000000000..d252adef69
--- /dev/null
+++ b/widget/cocoa/nsDeviceContextSpecX.mm
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsDeviceContextSpecX.h"
+
+#include "mozilla/gfx/PrintTargetCG.h"
+#include "mozilla/RefPtr.h"
+#include "nsCRT.h"
+#include <unistd.h>
+
+#include "nsQueryObject.h"
+#include "nsIServiceManager.h"
+#include "nsPrintSettingsX.h"
+
+// This must be the last include:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+nsDeviceContextSpecX::nsDeviceContextSpecX()
+: mPrintSession(NULL)
+, mPageFormat(kPMNoPageFormat)
+, mPrintSettings(kPMNoPrintSettings)
+{
+}
+
+nsDeviceContextSpecX::~nsDeviceContextSpecX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mPrintSession)
+ ::PMRelease(mPrintSession);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecX, nsIDeviceContextSpec)
+
+NS_IMETHODIMP nsDeviceContextSpecX::Init(nsIWidget *aWidget,
+ nsIPrintSettings* aPS,
+ bool aIsPrintPreview)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsPrintSettingsX> settings(do_QueryObject(aPS));
+ if (!settings)
+ return NS_ERROR_NO_INTERFACE;
+
+ mPrintSession = settings->GetPMPrintSession();
+ ::PMRetain(mPrintSession);
+ mPageFormat = settings->GetPMPageFormat();
+ mPrintSettings = settings->GetPMPrintSettings();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage,
+ int32_t aEndPage)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aTitle.IsEmpty()) {
+ CFStringRef cfString =
+ ::CFStringCreateWithCharacters(NULL, reinterpret_cast<const UniChar*>(aTitle.BeginReading()),
+ aTitle.Length());
+ if (cfString) {
+ ::PMPrintSettingsSetJobName(mPrintSettings, cfString);
+ ::CFRelease(cfString);
+ }
+ }
+
+ OSStatus status;
+ status = ::PMSetFirstPage(mPrintSettings, aStartPage, false);
+ NS_ASSERTION(status == noErr, "PMSetFirstPage failed");
+ status = ::PMSetLastPage(mPrintSettings, aEndPage, false);
+ NS_ASSERTION(status == noErr, "PMSetLastPage failed");
+
+ status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, mPageFormat);
+ if (status != noErr)
+ return NS_ERROR_ABORT;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::EndDocument()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ ::PMSessionEndDocumentNoDialog(mPrintSession);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::BeginPage()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMSessionError(mPrintSession);
+ OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, NULL);
+ if (status != noErr) return NS_ERROR_ABORT;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecX::EndPage()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession);
+ if (status != noErr) return NS_ERROR_ABORT;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsDeviceContextSpecX::GetPaperRect(double* aTop, double* aLeft, double* aBottom, double* aRight)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ PMRect paperRect;
+ ::PMGetAdjustedPaperRect(mPageFormat, &paperRect);
+
+ *aTop = paperRect.top, *aLeft = paperRect.left;
+ *aBottom = paperRect.bottom, *aRight = paperRect.right;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecX::MakePrintTarget()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ double top, left, bottom, right;
+ GetPaperRect(&top, &left, &bottom, &right);
+ const double width = right - left;
+ const double height = bottom - top;
+ IntSize size = IntSize::Floor(width, height);
+
+ CGContextRef context;
+ ::PMSessionGetCGGraphicsContext(mPrintSession, &context);
+
+ if (context) {
+ // Initially, origin is at bottom-left corner of the paper.
+ // Here, we translate it to top-left corner of the paper.
+ CGContextTranslateCTM(context, 0, height);
+ CGContextScaleCTM(context, 1.0, -1.0);
+ return PrintTargetCG::CreateOrNull(context, size);
+ }
+
+ // Apparently we do need this branch - bug 368933.
+ return PrintTargetCG::CreateOrNull(size, SurfaceFormat::A8R8G8B8_UINT32);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSNULL;
+}
diff --git a/widget/cocoa/nsDragService.h b/widget/cocoa/nsDragService.h
new file mode 100644
index 0000000000..ea6702812a
--- /dev/null
+++ b/widget/cocoa/nsDragService.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsDragService_h_
+#define nsDragService_h_
+
+#include "nsBaseDragService.h"
+
+#include <Cocoa/Cocoa.h>
+
+extern NSString* const kWildcardPboardType;
+extern NSString* const kCorePboardType_url;
+extern NSString* const kCorePboardType_urld;
+extern NSString* const kCorePboardType_urln;
+extern NSString* const kCustomTypesPboardType;
+
+class nsDragService : public nsBaseDragService
+{
+public:
+ nsDragService();
+
+ // nsBaseDragService
+ virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType);
+ // nsIDragService
+ NS_IMETHOD EndDragSession(bool aDoneDrag);
+
+ // nsIDragSession
+ NS_IMETHOD GetData(nsITransferable * aTransferable, uint32_t aItemIndex);
+ NS_IMETHOD IsDataFlavorSupported(const char *aDataFlavor, bool *_retval);
+ NS_IMETHOD GetNumDropItems(uint32_t * aNumItems);
+
+protected:
+ virtual ~nsDragService();
+
+private:
+
+ NSImage* ConstructDragImage(nsIDOMNode* aDOMNode,
+ mozilla::LayoutDeviceIntRect* aDragRect,
+ nsIScriptableRegion* aRegion);
+ bool IsValidType(NSString* availableType, bool allowFileURL);
+ NSString* GetStringForType(NSPasteboardItem* item, const NSString* type,
+ bool allowFileURL = false);
+ NSString* GetTitleForURL(NSPasteboardItem* item);
+ NSString* GetFilePath(NSPasteboardItem* item);
+
+ nsCOMPtr<nsIArray> mDataItems; // only valid for a drag started within gecko
+ NSView* mNativeDragView;
+ NSEvent* mNativeDragEvent;
+};
+
+#endif // nsDragService_h_
diff --git a/widget/cocoa/nsDragService.mm b/widget/cocoa/nsDragService.mm
new file mode 100644
index 0000000000..c4d8e6ff06
--- /dev/null
+++ b/widget/cocoa/nsDragService.mm
@@ -0,0 +1,669 @@
+/* -*- Mode: C++; tab-width: 2; 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 "mozilla/Logging.h"
+
+#include "nsArrayUtils.h"
+#include "nsDragService.h"
+#include "nsArrayUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsITransferable.h"
+#include "nsString.h"
+#include "nsClipboard.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCOMPtr.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsLinebreakConverter.h"
+#include "nsIMacUtils.h"
+#include "nsIDOMNode.h"
+#include "nsRect.h"
+#include "nsPoint.h"
+#include "nsIIOService.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsView.h"
+#include "gfxContext.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+extern PRLogModuleInfo* sCocoaLog;
+
+extern void EnsureLogInitialized();
+
+extern NSPasteboard* globalDragPboard;
+extern NSView* gLastDragView;
+extern NSEvent* gLastDragMouseDownEvent;
+extern bool gUserCancelledDrag;
+
+// This global makes the transferable array available to Cocoa's promised
+// file destination callback.
+nsIArray *gDraggedTransferables = nullptr;
+
+NSString* const kWildcardPboardType = @"MozillaWildcard";
+NSString* const kCorePboardType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url
+NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' desc
+NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title
+NSString* const kUTTypeURLName = @"public.url-name";
+NSString* const kCustomTypesPboardType = @"org.mozilla.custom-clipdata";
+
+nsDragService::nsDragService()
+{
+ mNativeDragView = nil;
+ mNativeDragEvent = nil;
+
+ EnsureLogInitialized();
+}
+
+nsDragService::~nsDragService()
+{
+}
+
+static nsresult SetUpDragClipboard(nsIArray* aTransferableArray)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aTransferableArray)
+ return NS_ERROR_FAILURE;
+
+ uint32_t count = 0;
+ aTransferableArray->GetLength(&count);
+
+ NSPasteboard* dragPBoard = [NSPasteboard pasteboardWithName:NSDragPboard];
+
+ for (uint32_t j = 0; j < count; j++) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(aTransferableArray, j);
+ if (!currentTransferable)
+ return NS_ERROR_FAILURE;
+
+ // Transform the transferable to an NSDictionary
+ NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(currentTransferable);
+ if (!pasteboardOutputDict)
+ return NS_ERROR_FAILURE;
+
+ // write everything out to the general pasteboard
+ unsigned int typeCount = [pasteboardOutputDict count];
+ NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
+ [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
+ // Gecko is initiating this drag so we always want its own views to consider
+ // it. Add our wildcard type to the pasteboard to accomplish this.
+ [types addObject:kWildcardPboardType]; // we don't increase the count for the loop below on purpose
+ [dragPBoard declareTypes:types owner:nil];
+ for (unsigned int k = 0; k < typeCount; k++) {
+ NSString* currentKey = [types objectAtIndex:k];
+ id currentValue = [pasteboardOutputDict valueForKey:currentKey];
+ if (currentKey == NSStringPboardType ||
+ currentKey == kCorePboardType_url ||
+ currentKey == kCorePboardType_urld ||
+ currentKey == kCorePboardType_urln) {
+ [dragPBoard setString:currentValue forType:currentKey];
+ }
+ else if (currentKey == NSHTMLPboardType) {
+ [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
+ forType:currentKey];
+ }
+ else if (currentKey == NSTIFFPboardType ||
+ currentKey == kCustomTypesPboardType) {
+ [dragPBoard setData:currentValue forType:currentKey];
+ }
+ else if (currentKey == NSFilesPromisePboardType ||
+ currentKey == NSFilenamesPboardType) {
+ [dragPBoard setPropertyList:currentValue forType:currentKey];
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NSImage*
+nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode,
+ LayoutDeviceIntRect* aDragRect,
+ nsIScriptableRegion* aRegion)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
+
+ RefPtr<SourceSurface> surface;
+ nsPresContext* pc;
+ nsresult rv = DrawDrag(aDOMNode, aRegion, mScreenPosition,
+ aDragRect, &surface, &pc);
+ if (pc && (!aDragRect->width || !aDragRect->height)) {
+ // just use some suitable defaults
+ int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
+ aDragRect->SetRect(pc->CSSPixelsToDevPixels(mScreenPosition.x),
+ pc->CSSPixelsToDevPixels(mScreenPosition.y), size, size);
+ }
+
+ if (NS_FAILED(rv) || !surface)
+ return nil;
+
+ uint32_t width = aDragRect->width;
+ uint32_t height = aDragRect->height;
+
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateDataSourceSurface(IntSize(width, height),
+ SurfaceFormat::B8G8R8A8);
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
+ return nil;
+ }
+
+ RefPtr<DrawTarget> dt =
+ Factory::CreateDrawTargetForData(BackendType::CAIRO,
+ map.mData,
+ dataSurface->GetSize(),
+ map.mStride,
+ dataSurface->GetFormat());
+ if (!dt) {
+ dataSurface->Unmap();
+ return nil;
+ }
+
+ dt->FillRect(gfx::Rect(0, 0, width, height),
+ SurfacePattern(surface, ExtendMode::CLAMP),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+
+ NSBitmapImageRep* imageRep =
+ [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bytesPerRow:width * 4
+ bitsPerPixel:32];
+
+ uint8_t* dest = [imageRep bitmapData];
+ for (uint32_t i = 0; i < height; ++i) {
+ uint8_t* src = map.mData + i * map.mStride;
+ for (uint32_t j = 0; j < width; ++j) {
+ // Reduce transparency overall by multipying by a factor. Remember, Alpha
+ // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
+#ifdef IS_BIG_ENDIAN
+ dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
+ dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
+ dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
+ dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
+#else
+ dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
+ dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
+ dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
+ dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
+#endif
+ src += 4;
+ dest += 4;
+ }
+ }
+ dataSurface->Unmap();
+
+ NSImage* image =
+ [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
+ height / scaleFactor)];
+ [image addRepresentation:imageRep];
+ [imageRep release];
+
+ return [image autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool
+nsDragService::IsValidType(NSString* availableType, bool allowFileURL)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // Prevent exposing fileURL for non-fileURL type.
+ // We need URL provided by dropped webloc file, but don't need file's URL.
+ // kUTTypeFileURL is returned by [NSPasteboard availableTypeFromArray:] for
+ // kUTTypeURL, since it conforms to kUTTypeURL.
+ if (!allowFileURL && [availableType isEqualToString:(id)kUTTypeFileURL]) {
+ return false;
+ }
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+NSString*
+nsDragService::GetStringForType(NSPasteboardItem* item, const NSString* type,
+ bool allowFileURL)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
+ if (availableType && IsValidType(availableType, allowFileURL)) {
+ return [item stringForType:(id)availableType];
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NSString*
+nsDragService::GetTitleForURL(NSPasteboardItem* item)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* name = GetStringForType(item, (const NSString*)kUTTypeURLName);
+ if (name) {
+ return name;
+ }
+
+ NSString* filePath = GetFilePath(item);
+ if (filePath) {
+ return [filePath lastPathComponent];
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NSString*
+nsDragService::GetFilePath(NSPasteboardItem* item)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* urlString = GetStringForType(item, (const NSString*)kUTTypeFileURL, true);
+ if (urlString) {
+ NSURL* url = [NSURL URLWithString:urlString];
+ if (url) {
+ return [url path];
+ }
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from
+// within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the
+// stack when InvokeDragSession gets called.
+nsresult
+nsDragService::InvokeDragSessionImpl(nsIArray* aTransferableArray,
+ nsIScriptableRegion* aDragRgn,
+ uint32_t aActionType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mDataItems = aTransferableArray;
+
+ // put data on the clipboard
+ if (NS_FAILED(SetUpDragClipboard(aTransferableArray)))
+ return NS_ERROR_FAILURE;
+
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
+
+ LayoutDeviceIntRect dragRect(0, 0, 20, 20);
+ NSImage* image = ConstructDragImage(mSourceNode, &dragRect, aDragRgn);
+ if (!image) {
+ // if no image was returned, just draw a rectangle
+ NSSize size;
+ size.width = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.width, scaleFactor);
+ size.height = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.height, scaleFactor);
+ image = [[NSImage alloc] initWithSize:size];
+ [image lockFocus];
+ [[NSColor grayColor] set];
+ NSBezierPath* path = [NSBezierPath bezierPath];
+ [path setLineWidth:2.0];
+ [path moveToPoint:NSMakePoint(0, 0)];
+ [path lineToPoint:NSMakePoint(0, size.height)];
+ [path lineToPoint:NSMakePoint(size.width, size.height)];
+ [path lineToPoint:NSMakePoint(size.width, 0)];
+ [path lineToPoint:NSMakePoint(0, 0)];
+ [path stroke];
+ [image unlockFocus];
+ }
+
+ LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
+ NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
+ point.y = nsCocoaUtils::FlippedScreenY(point.y);
+
+ point = nsCocoaUtils::ConvertPointFromScreen([gLastDragView window], point);
+ NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
+
+ // Save the transferables away in case a promised file callback is invoked.
+ gDraggedTransferables = aTransferableArray;
+
+ nsBaseDragService::StartDragSession();
+ nsBaseDragService::OpenDragPopup();
+
+ // We need to retain the view and the event during the drag in case either gets destroyed.
+ mNativeDragView = [gLastDragView retain];
+ mNativeDragEvent = [gLastDragMouseDownEvent retain];
+
+ gUserCancelledDrag = false;
+ [mNativeDragView dragImage:image
+ at:localPoint
+ offset:NSZeroSize
+ event:mNativeDragEvent
+ pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
+ source:mNativeDragView
+ slideBack:YES];
+ gUserCancelledDrag = false;
+
+ if (mDoingDrag)
+ nsBaseDragService::EndDragSession(false);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aTransferable)
+ return NS_ERROR_FAILURE;
+
+ // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return NS_ERROR_FAILURE;
+
+ uint32_t acceptableFlavorCount;
+ flavorList->GetLength(&acceptableFlavorCount);
+
+ // if this drag originated within Mozilla we should just use the cached data from
+ // when the drag started if possible
+ if (mDataItems) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, aItemIndex);
+ if (currentTransferable) {
+ for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ nsCOMPtr<nsISupports> dataSupports;
+ uint32_t dataSize = 0;
+ rv = currentTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
+ return NS_OK; // maybe try to fill in more types? Is there a point?
+ }
+ }
+ }
+ }
+
+ // now check the actual clipboard for data
+ for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ MOZ_LOG(sCocoaLog, LogLevel::Info, ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
+
+ NSArray* droppedItems = [globalDragPboard pasteboardItems];
+ if (!droppedItems) {
+ continue;
+ }
+
+ uint32_t itemCount = [droppedItems count];
+ if (aItemIndex >= itemCount) {
+ continue;
+ }
+
+ NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
+ if (!item) {
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ NSString* filePath = GetFilePath(item);
+ if (!filePath)
+ continue;
+
+ unsigned int stringLength = [filePath length];
+ unsigned int dataLength = (stringLength + 1) * sizeof(char16_t); // in bytes
+ char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
+ if (!clipboardDataPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
+ clipboardDataPtr[stringLength] = 0; // null terminate
+
+ nsCOMPtr<nsIFile> file;
+ rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
+ free(clipboardDataPtr);
+ if (NS_FAILED(rv))
+ continue;
+
+ aTransferable->SetTransferData(flavorStr, file, dataLength);
+
+ break;
+ }
+ else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObject:kCustomTypesPboardType]];
+ if (!availableType || !IsValidType(availableType, false)) {
+ continue;
+ }
+ NSData *pasteboardData = [item dataForType:availableType];
+ if (!pasteboardData) {
+ continue;
+ }
+
+ unsigned int dataLength = [pasteboardData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ [pasteboardData getBytes:clipboardDataPtr];
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
+ getter_AddRefs(genericDataWrapper));
+
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, sizeof(nsIInputStream*));
+ free(clipboardDataPtr);
+ break;
+ }
+
+ NSString* pString = nil;
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeUTF8PlainText);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeHTML);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeURL);
+ if (pString) {
+ NSString* title = GetTitleForURL(item);
+ if (!title) {
+ title = pString;
+ }
+ pString = [NSString stringWithFormat:@"%@\n%@", pString, title];
+ }
+ } else if (flavorStr.EqualsLiteral(kURLDataMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeURL);
+ } else if (flavorStr.EqualsLiteral(kURLDescriptionMime)) {
+ pString = GetTitleForURL(item);
+ } else if (flavorStr.EqualsLiteral(kRTFMime)) {
+ pString = GetStringForType(item, (const NSString*)kUTTypeRTF);
+ }
+ if (pString) {
+ NSData* stringData;
+ if (flavorStr.EqualsLiteral(kRTFMime)) {
+ stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
+ } else {
+ stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
+ }
+ unsigned int dataLength = [stringData length];
+ void* clipboardDataPtr = malloc(dataLength);
+ if (!clipboardDataPtr)
+ return NS_ERROR_OUT_OF_MEMORY;
+ [stringData getBytes:clipboardDataPtr];
+
+ // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
+ int32_t signedDataLength = dataLength;
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
+ dataLength = signedDataLength;
+
+ // skip BOM (Byte Order Mark to distinguish little or big endian)
+ char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
+ if ((dataLength > 2) &&
+ ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
+ (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
+ dataLength -= sizeof(char16_t);
+ clipboardDataPtrNoBOM += 1;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
+ free(clipboardDataPtr);
+ break;
+ }
+
+ // We have never supported this on Mac OS X, we should someday. Normally dragging images
+ // in is accomplished with a file path drag instead of the image data itself.
+ /*
+ if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
+
+ }
+ */
+ }
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *_retval = false;
+
+ if (!globalDragPboard)
+ return NS_ERROR_FAILURE;
+
+ nsDependentCString dataFlavor(aDataFlavor);
+
+ // first see if we have data for this in our cached transferable
+ if (mDataItems) {
+ uint32_t dataItemsCount;
+ mDataItems->GetLength(&dataItemsCount);
+ for (unsigned int i = 0; i < dataItemsCount; i++) {
+ nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, i);
+ if (!currentTransferable)
+ continue;
+
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ continue;
+
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+ for (uint32_t j = 0; j < flavorCount; j++) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, j);
+ if (!currentFlavor)
+ continue;
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ if (dataFlavor.Equals(flavorStr)) {
+ *_retval = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ const NSString* type = nil;
+ bool allowFileURL = false;
+ if (dataFlavor.EqualsLiteral(kFileMime)) {
+ type = (const NSString*)kUTTypeFileURL;
+ allowFileURL = true;
+ } else if (dataFlavor.EqualsLiteral(kUnicodeMime)) {
+ type = (const NSString*)kUTTypeUTF8PlainText;
+ } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
+ type = (const NSString*)kUTTypeHTML;
+ } else if (dataFlavor.EqualsLiteral(kURLMime) ||
+ dataFlavor.EqualsLiteral(kURLDataMime)) {
+ type = (const NSString*)kUTTypeURL;
+ } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
+ type = (const NSString*)kUTTypeURLName;
+ } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
+ type = (const NSString*)kUTTypeRTF;
+ } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
+ type = (const NSString*)kCustomTypesPboardType;
+ }
+
+ NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
+ if (availableType && IsValidType(availableType, allowFileURL)) {
+ *_retval = true;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t* aNumItems)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *aNumItems = 0;
+
+ // first check to see if we have a number of items cached
+ if (mDataItems) {
+ mDataItems->GetLength(aNumItems);
+ return NS_OK;
+ }
+
+ NSArray* droppedItems = [globalDragPboard pasteboardItems];
+ if (droppedItems) {
+ *aNumItems = [droppedItems count];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mNativeDragView) {
+ [mNativeDragView release];
+ mNativeDragView = nil;
+ }
+ if (mNativeDragEvent) {
+ [mNativeDragEvent release];
+ mNativeDragEvent = nil;
+ }
+
+ mUserCancelled = gUserCancelledDrag;
+
+ nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
+ mDataItems = nullptr;
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsFilePicker.h b/widget/cocoa/nsFilePicker.h
new file mode 100644
index 0000000000..1aeb22cc18
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsFilePicker_h_
+#define nsFilePicker_h_
+
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsIFileChannel.h"
+#include "nsIFile.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+
+class nsILocalFileMac;
+@class NSArray;
+
+class nsFilePicker : public nsBaseFilePicker
+{
+public:
+ nsFilePicker();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker)
+ NS_IMETHOD GetDefaultString(nsAString& aDefaultString) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aDefaultString) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aDefaultExtension) override;
+ NS_IMETHOD GetFilterIndex(int32_t *aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aDefaultExtension) override;
+ NS_IMETHOD GetFile(nsIFile * *aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI * *aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles) override;
+ NS_IMETHOD Show(int16_t *_retval) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle, const nsAString& aFilter) override;
+
+ /**
+ * Returns the current filter list in the format used by Cocoa's NSSavePanel
+ * and NSOpenPanel.
+ * Returns nil if no filter currently apply.
+ */
+ NSArray* GetFilterList();
+
+protected:
+ virtual ~nsFilePicker();
+
+ virtual void InitNative(nsIWidget *aParent, const nsAString& aTitle) override;
+
+ // actual implementations of get/put dialogs using NSOpenPanel & NSSavePanel
+ // aFile is an existing but unspecified file. These functions must specify it.
+ //
+ // will return |returnCancel| or |returnOK| as result.
+ int16_t GetLocalFiles(const nsString& inTitle, bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles);
+ int16_t GetLocalFolder(const nsString& inTitle, nsIFile** outFile);
+ int16_t PutLocalFile(const nsString& inTitle, const nsString& inDefaultName, nsIFile** outFile);
+
+ void SetDialogTitle(const nsString& inTitle, id aDialog);
+ NSString *PanelDefaultDirectory();
+ NSView* GetAccessoryView();
+
+ nsString mTitle;
+ nsCOMArray<nsIFile> mFiles;
+ nsString mDefault;
+
+ nsTArray<nsString> mFilters;
+ nsTArray<nsString> mTitles;
+
+ int32_t mSelectedTypeIndex;
+};
+
+#endif // nsFilePicker_h_
diff --git a/widget/cocoa/nsFilePicker.mm b/widget/cocoa/nsFilePicker.mm
new file mode 100644
index 0000000000..5213dee241
--- /dev/null
+++ b/widget/cocoa/nsFilePicker.mm
@@ -0,0 +1,676 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsFilePicker.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsNetUtil.h"
+#include "nsIComponentManager.h"
+#include "nsIFile.h"
+#include "nsILocalFileMac.h"
+#include "nsIURL.h"
+#include "nsArrayEnumerator.h"
+#include "nsIStringBundle.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/Preferences.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+const float kAccessoryViewPadding = 5;
+const int kSaveTypeControlTag = 1;
+
+static bool gCallSecretHiddenFileAPI = false;
+const char kShowHiddenFilesPref[] = "filepicker.showHiddenFiles";
+
+/**
+ * This class is an observer of NSPopUpButton selection change.
+ */
+@interface NSPopUpButtonObserver : NSObject
+{
+ NSPopUpButton* mPopUpButton;
+ NSOpenPanel* mOpenPanel;
+ nsFilePicker* mFilePicker;
+}
+- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton;
+- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel;
+- (void) setFilePicker:(nsFilePicker*)aFilePicker;
+- (void) menuChangedItem:(NSNotification*)aSender;
+@end
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+// We never want to call the secret show hidden files API unless the pref
+// has been set. Once the pref has been set we always need to call it even
+// if it disappears so that we stop showing hidden files if a user deletes
+// the pref. If the secret API was used once and things worked out it should
+// continue working for subsequent calls so the user is at no more risk.
+static void SetShowHiddenFileState(NSSavePanel* panel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ bool show = false;
+ if (NS_SUCCEEDED(Preferences::GetBool(kShowHiddenFilesPref, &show))) {
+ gCallSecretHiddenFileAPI = true;
+ }
+
+ if (gCallSecretHiddenFileAPI) {
+ // invoke a method to get a Cocoa-internal nav view
+ SEL navViewSelector = @selector(_navView);
+ NSMethodSignature* navViewSignature = [panel methodSignatureForSelector:navViewSelector];
+ if (!navViewSignature)
+ return;
+ NSInvocation* navViewInvocation = [NSInvocation invocationWithMethodSignature:navViewSignature];
+ [navViewInvocation setSelector:navViewSelector];
+ [navViewInvocation setTarget:panel];
+ [navViewInvocation invoke];
+
+ // get the returned nav view
+ id navView = nil;
+ [navViewInvocation getReturnValue:&navView];
+
+ // invoke the secret show hidden file state method on the nav view
+ SEL showHiddenFilesSelector = @selector(setShowsHiddenFiles:);
+ NSMethodSignature* showHiddenFilesSignature = [navView methodSignatureForSelector:showHiddenFilesSelector];
+ if (!showHiddenFilesSignature)
+ return;
+ NSInvocation* showHiddenFilesInvocation = [NSInvocation invocationWithMethodSignature:showHiddenFilesSignature];
+ [showHiddenFilesInvocation setSelector:showHiddenFilesSelector];
+ [showHiddenFilesInvocation setTarget:navView];
+ [showHiddenFilesInvocation setArgument:&show atIndex:2];
+ [showHiddenFilesInvocation invoke];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsFilePicker::nsFilePicker()
+: mSelectedTypeIndex(0)
+{
+}
+
+nsFilePicker::~nsFilePicker()
+{
+}
+
+void
+nsFilePicker::InitNative(nsIWidget *aParent, const nsAString& aTitle)
+{
+ mTitle = aTitle;
+}
+
+NSView* nsFilePicker::GetAccessoryView()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSView* accessoryView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0)] autorelease];
+
+ // Set a label's default value.
+ NSString* label = @"Format:";
+
+ // Try to get the localized string.
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = sbs->CreateBundle("chrome://global/locale/filepicker.properties", getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsXPIDLString locaLabel;
+ bundle->GetStringFromName(u"formatLabel", getter_Copies(locaLabel));
+ if (locaLabel) {
+ label = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(locaLabel.get())
+ length:locaLabel.Length()];
+ }
+ }
+
+ // set up label text field
+ NSTextField* textField = [[[NSTextField alloc] init] autorelease];
+ [textField setEditable:NO];
+ [textField setSelectable:NO];
+ [textField setDrawsBackground:NO];
+ [textField setBezeled:NO];
+ [textField setBordered:NO];
+ [textField setFont:[NSFont labelFontOfSize:13.0]];
+ [textField setStringValue:label];
+ [textField setTag:0];
+ [textField sizeToFit];
+
+ // set up popup button
+ NSPopUpButton* popupButton = [[[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 0, 0) pullsDown:NO] autorelease];
+ uint32_t numMenuItems = mTitles.Length();
+ for (uint32_t i = 0; i < numMenuItems; i++) {
+ const nsString& currentTitle = mTitles[i];
+ NSString *titleString;
+ if (currentTitle.IsEmpty()) {
+ const nsString& currentFilter = mFilters[i];
+ titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentFilter.get())
+ length:currentFilter.Length()];
+ }
+ else {
+ titleString = [[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(currentTitle.get())
+ length:currentTitle.Length()];
+ }
+ [popupButton addItemWithTitle:titleString];
+ [titleString release];
+ }
+ if (mSelectedTypeIndex >= 0 && (uint32_t)mSelectedTypeIndex < numMenuItems)
+ [popupButton selectItemAtIndex:mSelectedTypeIndex];
+ [popupButton setTag:kSaveTypeControlTag];
+ [popupButton sizeToFit]; // we have to do sizeToFit to get the height calculated for us
+ // This is just a default width that works well, doesn't truncate the vast majority of
+ // things that might end up in the menu.
+ [popupButton setFrameSize:NSMakeSize(180, [popupButton frame].size.height)];
+
+ // position everything based on control sizes with kAccessoryViewPadding pix padding
+ // on each side kAccessoryViewPadding pix horizontal padding between controls
+ float greatestHeight = [textField frame].size.height;
+ if ([popupButton frame].size.height > greatestHeight)
+ greatestHeight = [popupButton frame].size.height;
+ float totalViewHeight = greatestHeight + kAccessoryViewPadding * 2;
+ float totalViewWidth = [textField frame].size.width + [popupButton frame].size.width + kAccessoryViewPadding * 3;
+ [accessoryView setFrameSize:NSMakeSize(totalViewWidth, totalViewHeight)];
+
+ float textFieldOriginY = ((greatestHeight - [textField frame].size.height) / 2 + 1) + kAccessoryViewPadding;
+ [textField setFrameOrigin:NSMakePoint(kAccessoryViewPadding, textFieldOriginY)];
+
+ float popupOriginX = [textField frame].size.width + kAccessoryViewPadding * 2;
+ float popupOriginY = ((greatestHeight - [popupButton frame].size.height) / 2) + kAccessoryViewPadding;
+ [popupButton setFrameOrigin:NSMakePoint(popupOriginX, popupOriginY)];
+
+ [accessoryView addSubview:textField];
+ [accessoryView addSubview:popupButton];
+ return accessoryView;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Display the file dialog
+NS_IMETHODIMP nsFilePicker::Show(int16_t *retval)
+{
+ NS_ENSURE_ARG_POINTER(retval);
+
+ *retval = returnCancel;
+
+ int16_t userClicksOK = returnCancel;
+
+// Random questions from DHH:
+//
+// Why do we pass mTitle, mDefault to the functions? Can GetLocalFile. PutLocalFile,
+// and GetLocalFolder get called someplace else? It generates a bunch of warnings
+// as it is right now.
+//
+// I think we could easily combine GetLocalFile and GetLocalFolder together, just
+// setting panel pick options based on mMode. I didn't do it here b/c I wanted to
+// make this look as much like Carbon nsFilePicker as possible.
+
+ mFiles.Clear();
+ nsCOMPtr<nsIFile> theFile;
+
+ switch (mMode)
+ {
+ case modeOpen:
+ userClicksOK = GetLocalFiles(mTitle, false, mFiles);
+ break;
+
+ case modeOpenMultiple:
+ userClicksOK = GetLocalFiles(mTitle, true, mFiles);
+ break;
+
+ case modeSave:
+ userClicksOK = PutLocalFile(mTitle, mDefault, getter_AddRefs(theFile));
+ break;
+
+ case modeGetFolder:
+ userClicksOK = GetLocalFolder(mTitle, getter_AddRefs(theFile));
+ break;
+
+ default:
+ NS_ERROR("Unknown file picker mode");
+ break;
+ }
+
+ if (theFile)
+ mFiles.AppendObject(theFile);
+
+ *retval = userClicksOK;
+ return NS_OK;
+}
+
+static
+void UpdatePanelFileTypes(NSOpenPanel* aPanel, NSArray* aFilters)
+{
+ // If we show all file types, also "expose" bundles' contents.
+ [aPanel setTreatsFilePackagesAsDirectories:!aFilters];
+
+ [aPanel setAllowedFileTypes:aFilters];
+}
+
+@implementation NSPopUpButtonObserver
+- (void) setPopUpButton:(NSPopUpButton*)aPopUpButton
+{
+ mPopUpButton = aPopUpButton;
+}
+
+- (void) setOpenPanel:(NSOpenPanel*)aOpenPanel
+{
+ mOpenPanel = aOpenPanel;
+}
+
+- (void) setFilePicker:(nsFilePicker*)aFilePicker
+{
+ mFilePicker = aFilePicker;
+}
+
+- (void) menuChangedItem:(NSNotification *)aSender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ int32_t selectedItem = [mPopUpButton indexOfSelectedItem];
+ if (selectedItem < 0) {
+ return;
+ }
+
+ mFilePicker->SetFilterIndex(selectedItem);
+ UpdatePanelFileTypes(mOpenPanel, mFilePicker->GetFilterList());
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN();
+}
+@end
+
+// Use OpenPanel to do a GetFile. Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::GetLocalFiles(const nsString& inTitle, bool inAllowMultiple, nsCOMArray<nsIFile>& outFiles)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ int16_t retVal = (int16_t)returnCancel;
+ NSOpenPanel *thePanel = [NSOpenPanel openPanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ // Set the options for how the get file dialog will appear
+ SetDialogTitle(inTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:inAllowMultiple];
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:NO];
+ [thePanel setCanChooseFiles:YES];
+ [thePanel setResolvesAliases:YES]; //this is default - probably doesn't need to be set
+
+ // Get filters
+ // filters may be null, if we should allow all file types.
+ NSArray *filters = GetFilterList();
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+
+ // if this is the "Choose application..." dialog, and no other start
+ // dir has been set, then use the Applications folder.
+ if (!theDir) {
+ if (filters && [filters count] == 1 &&
+ [(NSString *)[filters objectAtIndex:0] isEqualToString:@"app"])
+ theDir = @"/Applications/";
+ else
+ theDir = @"";
+ }
+
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ int result;
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (mFilters.Length() > 1) {
+ // [NSURL initWithString:] (below) throws an exception if URLString is nil.
+
+ NSPopUpButtonObserver* observer = [[NSPopUpButtonObserver alloc] init];
+
+ NSView* accessoryView = GetAccessoryView();
+ [thePanel setAccessoryView:accessoryView];
+
+ [observer setPopUpButton:[accessoryView viewWithTag:kSaveTypeControlTag]];
+ [observer setOpenPanel:thePanel];
+ [observer setFilePicker:this];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:observer
+ selector:@selector(menuChangedItem:)
+ name:NSMenuWillSendActionNotification object:nil];
+
+ UpdatePanelFileTypes(thePanel, filters);
+ result = [thePanel runModal];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:observer];
+ [observer release];
+ } else {
+ // If we show all file types, also "expose" bundles' contents.
+ if (!filters) {
+ [thePanel setTreatsFilePackagesAsDirectories:YES];
+ }
+ [thePanel setAllowedFileTypes:filters];
+ result = [thePanel runModal];
+ }
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // Converts data from a NSArray of NSURL to the returned format.
+ // We should be careful to not call [thePanel URLs] more than once given that
+ // it creates a new array each time.
+ // We are using Fast Enumeration, thus the NSURL array is created once then
+ // iterated.
+ for (NSURL* url in [thePanel URLs]) {
+ if (!url) {
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)url))) {
+ outFiles.AppendObject(localFile);
+ }
+ }
+
+ if (outFiles.Count() > 0)
+ retVal = returnOK;
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Use OpenPanel to do a GetFolder. Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::GetLocalFolder(const nsString& inTitle, nsIFile** outFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
+
+ int16_t retVal = (int16_t)returnCancel;
+ NSOpenPanel *thePanel = [NSOpenPanel openPanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ // Set the options for how the get file dialog will appear
+ SetDialogTitle(inTitle, thePanel);
+ [thePanel setAllowsMultipleSelection:NO]; //this is default -probably doesn't need to be set
+ [thePanel setCanSelectHiddenExtension:YES];
+ [thePanel setCanChooseDirectories:YES];
+ [thePanel setCanChooseFiles:NO];
+ [thePanel setResolvesAliases:YES]; //this is default - probably doesn't need to be set
+ [thePanel setCanCreateDirectories:YES];
+
+ // packages != folders
+ [thePanel setTreatsFilePackagesAsDirectories:NO];
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // get the path for the folder (we allow just 1, so that's all we get)
+ NSURL *theURL = [[thePanel URLs] objectAtIndex:0];
+ if (theURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)theURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+// Returns |returnOK| if the user presses OK in the dialog.
+int16_t
+nsFilePicker::PutLocalFile(const nsString& inTitle, const nsString& inDefaultName, nsIFile** outFile)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+ NS_ASSERTION(outFile, "this protected member function expects a null initialized out pointer");
+
+ int16_t retVal = returnCancel;
+ NSSavePanel *thePanel = [NSSavePanel savePanel];
+
+ SetShowHiddenFileState(thePanel);
+
+ SetDialogTitle(inTitle, thePanel);
+
+ // set up accessory view for file format options
+ NSView* accessoryView = GetAccessoryView();
+ [thePanel setAccessoryView:accessoryView];
+
+ // set up default file name
+ NSString* defaultFilename = [NSString stringWithCharacters:(const unichar*)inDefaultName.get() length:inDefaultName.Length()];
+
+ // set up allowed types; this prevents the extension from being selected
+ // use the UTI for the file type to allow alternate extensions (e.g., jpg vs. jpeg)
+ NSString* extension = defaultFilename.pathExtension;
+ if (extension.length != 0) {
+ CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)extension, NULL);
+
+ if (type) {
+ thePanel.allowedFileTypes = @[(NSString*)type];
+ CFRelease(type);
+ } else {
+ // if there's no UTI for the file extension, use the extension itself.
+ thePanel.allowedFileTypes = @[extension];
+ }
+ }
+ // Allow users to change the extension.
+ thePanel.allowsOtherFileTypes = YES;
+
+ // set up default directory
+ NSString *theDir = PanelDefaultDirectory();
+ if (theDir) {
+ [thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
+ }
+
+ // load the panel
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ [thePanel setNameFieldStringValue:defaultFilename];
+ int result = [thePanel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+ if (result == NSFileHandlingPanelCancelButton)
+ return retVal;
+
+ // get the save type
+ NSPopUpButton* popupButton = [accessoryView viewWithTag:kSaveTypeControlTag];
+ if (popupButton) {
+ mSelectedTypeIndex = [popupButton indexOfSelectedItem];
+ }
+
+ NSURL* fileURL = [thePanel URL];
+ if (fileURL) {
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile));
+ nsCOMPtr<nsILocalFileMac> macLocalFile = do_QueryInterface(localFile);
+ if (macLocalFile && NS_SUCCEEDED(macLocalFile->InitWithCFURL((CFURLRef)fileURL))) {
+ *outFile = localFile;
+ NS_ADDREF(*outFile);
+ // We tell if we are replacing or not by just looking to see if the file exists.
+ // The user could not have hit OK and not meant to replace the file.
+ if ([[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]])
+ retVal = returnReplace;
+ else
+ retVal = returnOK;
+ }
+ }
+
+ return retVal;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+NSArray *
+nsFilePicker::GetFilterList()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mFilters.Length()) {
+ return nil;
+ }
+
+ if (mFilters.Length() <= (uint32_t)mSelectedTypeIndex) {
+ NS_WARNING("An out of range index has been selected. Using the first index instead.");
+ mSelectedTypeIndex = 0;
+ }
+
+ const nsString& filterWide = mFilters[mSelectedTypeIndex];
+ if (!filterWide.Length()) {
+ return nil;
+ }
+
+ if (filterWide.Equals(NS_LITERAL_STRING("*"))) {
+ return nil;
+ }
+
+ // The extensions in filterWide are in the format "*.ext" but are expected
+ // in the format "ext" by NSOpenPanel. So we need to filter some characters.
+ NSMutableString* filterString = [[[NSMutableString alloc] initWithString:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(filterWide.get())
+ length:filterWide.Length()]] autorelease];
+ NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@". *"];
+ NSRange range = [filterString rangeOfCharacterFromSet:set];
+ while (range.length) {
+ [filterString replaceCharactersInRange:range withString:@""];
+ range = [filterString rangeOfCharacterFromSet:set];
+ }
+
+ return [[[NSArray alloc] initWithArray:
+ [filterString componentsSeparatedByString:@";"]] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// Sets the dialog title to whatever it should be. If it fails, eh,
+// the OS will provide a sensible default.
+void
+nsFilePicker::SetDialogTitle(const nsString& inTitle, id aPanel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [aPanel setTitle:[NSString stringWithCharacters:(const unichar*)inTitle.get() length:inTitle.Length()]];
+
+ if (!mOkButtonLabel.IsEmpty()) {
+ [aPanel setPrompt:[NSString stringWithCharacters:(const unichar*)mOkButtonLabel.get() length:mOkButtonLabel.Length()]];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Converts path from an nsIFile into a NSString path
+// If it fails, returns an empty string.
+NSString *
+nsFilePicker::PanelDefaultDirectory()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString *directory = nil;
+ if (mDisplayDirectory) {
+ nsAutoString pathStr;
+ mDisplayDirectory->GetPath(pathStr);
+ directory = [[[NSString alloc] initWithCharacters:reinterpret_cast<const unichar*>(pathStr.get())
+ length:pathStr.Length()] autorelease];
+ }
+ return directory;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFile(nsIFile **aFile)
+{
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+
+ // just return the first file
+ if (mFiles.Count() > 0) {
+ *aFile = mFiles.ObjectAt(0);
+ NS_IF_ADDREF(*aFile);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::GetFileURL(nsIURI **aFileURL)
+{
+ NS_ENSURE_ARG_POINTER(aFileURL);
+ *aFileURL = nullptr;
+
+ if (mFiles.Count() == 0)
+ return NS_OK;
+
+ return NS_NewFileURI(aFileURL, mFiles.ObjectAt(0));
+}
+
+NS_IMETHODIMP nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
+{
+ return NS_NewArrayEnumerator(aFiles, mFiles);
+}
+
+NS_IMETHODIMP nsFilePicker::SetDefaultString(const nsAString& aString)
+{
+ mDefault = aString;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::GetDefaultString(nsAString& aString)
+{
+ return NS_ERROR_FAILURE;
+}
+
+// The default extension to use for files
+NS_IMETHODIMP nsFilePicker::GetDefaultExtension(nsAString& aExtension)
+{
+ aExtension.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
+{
+ return NS_OK;
+}
+
+// Append an entry to the filters array
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
+{
+ // "..apps" has to be translated with native executable extensions.
+ if (aFilter.EqualsLiteral("..apps")) {
+ mFilters.AppendElement(NS_LITERAL_STRING("*.app"));
+ } else {
+ mFilters.AppendElement(aFilter);
+ }
+ mTitles.AppendElement(aTitle);
+
+ return NS_OK;
+}
+
+// Get the filter index - do we still need this?
+NS_IMETHODIMP nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
+{
+ *aFilterIndex = mSelectedTypeIndex;
+ return NS_OK;
+}
+
+// Set the filter index - do we still need this?
+NS_IMETHODIMP nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
+{
+ mSelectedTypeIndex = aFilterIndex;
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsIdleServiceX.h b/widget/cocoa/nsIdleServiceX.h
new file mode 100644
index 0000000000..f0b3d92ed5
--- /dev/null
+++ b/widget/cocoa/nsIdleServiceX.h
@@ -0,0 +1,33 @@
+/* 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/. */
+
+#ifndef nsIdleServiceX_h_
+#define nsIdleServiceX_h_
+
+#include "nsIdleService.h"
+
+class nsIdleServiceX : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsIdleServiceX> GetInstance()
+ {
+ RefPtr<nsIdleService> idleService = nsIdleService::GetInstance();
+ if (!idleService) {
+ idleService = new nsIdleServiceX();
+ }
+
+ return idleService.forget().downcast<nsIdleServiceX>();
+ }
+
+protected:
+ nsIdleServiceX() { }
+ virtual ~nsIdleServiceX() { }
+ bool UsePollMode() override;
+};
+
+#endif // nsIdleServiceX_h_
diff --git a/widget/cocoa/nsIdleServiceX.mm b/widget/cocoa/nsIdleServiceX.mm
new file mode 100644
index 0000000000..234a154146
--- /dev/null
+++ b/widget/cocoa/nsIdleServiceX.mm
@@ -0,0 +1,77 @@
+/* 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 "nsIdleServiceX.h"
+#include "nsObjCExceptions.h"
+#include "nsIServiceManager.h"
+#import <Foundation/Foundation.h>
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceX, nsIdleService)
+
+bool
+nsIdleServiceX::PollIdleTime(uint32_t *aIdleTime)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ kern_return_t rval;
+ mach_port_t masterPort;
+
+ rval = IOMasterPort(kIOMasterPortDefault, &masterPort);
+ if (rval != KERN_SUCCESS)
+ return false;
+
+ io_iterator_t hidItr;
+ rval = IOServiceGetMatchingServices(masterPort,
+ IOServiceMatching("IOHIDSystem"),
+ &hidItr);
+
+ if (rval != KERN_SUCCESS)
+ return false;
+ NS_ASSERTION(hidItr, "Our iterator is null, but it ought not to be!");
+
+ io_registry_entry_t entry = IOIteratorNext(hidItr);
+ NS_ASSERTION(entry, "Our IO Registry Entry is null, but it shouldn't be!");
+
+ IOObjectRelease(hidItr);
+
+ NSMutableDictionary *hidProps;
+ rval = IORegistryEntryCreateCFProperties(entry,
+ (CFMutableDictionaryRef*)&hidProps,
+ kCFAllocatorDefault, 0);
+ if (rval != KERN_SUCCESS)
+ return false;
+ NS_ASSERTION(hidProps, "HIDProperties is null, but no error was returned.");
+ [hidProps autorelease];
+
+ id idleObj = [hidProps objectForKey:@"HIDIdleTime"];
+ NS_ASSERTION([idleObj isKindOfClass: [NSData class]] ||
+ [idleObj isKindOfClass: [NSNumber class]],
+ "What we got for the idle object is not what we expect!");
+
+ uint64_t time;
+ if ([idleObj isKindOfClass: [NSData class]])
+ [idleObj getBytes: &time];
+ else
+ time = [idleObj unsignedLongLongValue];
+
+ IOObjectRelease(entry);
+
+ // convert to ms from ns
+ time /= 1000000;
+ if (time > UINT32_MAX) // Overflow will occur
+ return false;
+
+ *aIdleTime = static_cast<uint32_t>(time);
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+bool
+nsIdleServiceX::UsePollMode()
+{
+ return true;
+}
+
diff --git a/widget/cocoa/nsLookAndFeel.h b/widget/cocoa/nsLookAndFeel.h
new file mode 100644
index 0000000000..2ad31a2aa1
--- /dev/null
+++ b/widget/cocoa/nsLookAndFeel.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsLookAndFeel_h_
+#define nsLookAndFeel_h_
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel: public nsXPLookAndFeel {
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+ virtual char16_t GetPasswordCharacterImpl()
+ {
+ // unicode value for the bullet character, used for password textfields.
+ return 0x2022;
+ }
+
+ static bool UseOverlayScrollbars();
+
+ virtual nsTArray<LookAndFeelInt> GetIntCacheImpl();
+ virtual void SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache);
+
+ virtual void RefreshImpl();
+
+protected:
+ static bool SystemWantsOverlayScrollbars();
+ static bool AllowOverlayScrollbarsOverlap();
+
+private:
+ int32_t mUseOverlayScrollbars;
+ bool mUseOverlayScrollbarsCached;
+
+ int32_t mAllowOverlayScrollbarsOverlap;
+ bool mAllowOverlayScrollbarsOverlapCached;
+};
+
+#endif // nsLookAndFeel_h_
diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm
new file mode 100644
index 0000000000..0b68cd0e4f
--- /dev/null
+++ b/widget/cocoa/nsLookAndFeel.mm
@@ -0,0 +1,584 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsLookAndFeel.h"
+#include "nsCocoaFeatures.h"
+#include "nsIServiceManager.h"
+#include "nsNativeThemeColors.h"
+#include "nsStyleConsts.h"
+#include "nsCocoaFeatures.h"
+#include "nsIContent.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "gfxPlatformMac.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/widget/WidgetMessageUtils.h"
+
+#import <Cocoa/Cocoa.h>
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+enum {
+ mozNSScrollerStyleLegacy = 0,
+ mozNSScrollerStyleOverlay = 1
+};
+typedef NSInteger mozNSScrollerStyle;
+
+@interface NSScroller(AvailableSinceLion)
++ (mozNSScrollerStyle)preferredScrollerStyle;
+@end
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+ , mUseOverlayScrollbars(-1)
+ , mUseOverlayScrollbarsCached(false)
+ , mAllowOverlayScrollbarsOverlap(-1)
+ , mAllowOverlayScrollbarsOverlapCached(false)
+{
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+static nscolor GetColorFromNSColor(NSColor* aColor)
+{
+ NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ return NS_RGB((unsigned int)([deviceColor redComponent] * 255.0),
+ (unsigned int)([deviceColor greenComponent] * 255.0),
+ (unsigned int)([deviceColor blueComponent] * 255.0));
+}
+
+static nscolor GetColorFromNSColorWithAlpha(NSColor* aColor, float alpha)
+{
+ NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ return NS_RGBA((unsigned int)([deviceColor redComponent] * 255.0),
+ (unsigned int)([deviceColor greenComponent] * 255.0),
+ (unsigned int)([deviceColor blueComponent] * 255.0),
+ (unsigned int)(alpha * 255.0));
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case eColorID_WindowBackground:
+ aColor = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_WindowForeground:
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetBackground:
+ aColor = NS_RGB(0xdd,0xdd,0xdd);
+ break;
+ case eColorID_WidgetForeground:
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetSelectBackground:
+ aColor = NS_RGB(0x80,0x80,0x80);
+ break;
+ case eColorID_WidgetSelectForeground:
+ aColor = NS_RGB(0x00,0x00,0x80);
+ break;
+ case eColorID_Widget3DHighlight:
+ aColor = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aColor = NS_RGB(0x40,0x40,0x40);
+ break;
+ case eColorID_TextBackground:
+ aColor = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_TextForeground:
+ aColor = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_TextSelectBackground:
+ aColor = GetColorFromNSColor([NSColor selectedTextBackgroundColor]);
+ break;
+ case eColorID_highlight: // CSS2 color
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ break;
+ case eColorID__moz_menuhover:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ break;
+ case eColorID_TextSelectForeground:
+ GetColor(eColorID_TextSelectBackground, aColor);
+ if (aColor == 0x000000)
+ aColor = NS_RGB(0xff,0xff,0xff);
+ else
+ aColor = NS_DONT_CHANGE_COLOR;
+ break;
+ case eColorID_highlighttext: // CSS2 color
+ case eColorID__moz_menuhovertext:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlTextColor]);
+ break;
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_40PERCENT_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+
+ //
+ // css2 system colors http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ //
+ // It's really hard to effectively map these to the Appearance Manager properly,
+ // since they are modeled word for word after the win32 system colors and don't have any
+ // real counterparts in the Mac world. I'm sure we'll be tweaking these for
+ // years to come.
+ //
+ // Thanks to mpt26@student.canterbury.ac.nz for the hardcoded values that form the defaults
+ // if querying the Appearance Manager fails ;)
+ //
+ case eColorID__moz_mac_buttonactivetext:
+ case eColorID__moz_mac_defaultbuttontext:
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ aColor = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ }
+ // Otherwise fall through and return the regular button text:
+
+ case eColorID_buttontext:
+ case eColorID__moz_buttonhovertext:
+ aColor = GetColorFromNSColor([NSColor controlTextColor]);
+ break;
+ case eColorID_captiontext:
+ case eColorID_menutext:
+ case eColorID_infotext:
+ case eColorID__moz_menubartext:
+ aColor = GetColorFromNSColor([NSColor textColor]);
+ break;
+ case eColorID_windowtext:
+ aColor = GetColorFromNSColor([NSColor windowFrameTextColor]);
+ break;
+ case eColorID_activecaption:
+ aColor = GetColorFromNSColor([NSColor gridColor]);
+ break;
+ case eColorID_activeborder:
+ aColor = GetColorFromNSColor([NSColor keyboardFocusIndicatorColor]);
+ break;
+ case eColorID_appworkspace:
+ aColor = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_background:
+ aColor = NS_RGB(0x63,0x63,0xCE);
+ break;
+ case eColorID_buttonface:
+ case eColorID__moz_buttonhoverface:
+ aColor = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_buttonhighlight:
+ aColor = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_buttonshadow:
+ aColor = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_graytext:
+ aColor = GetColorFromNSColor([NSColor disabledControlTextColor]);
+ break;
+ case eColorID_inactiveborder:
+ aColor = GetColorFromNSColor([NSColor controlBackgroundColor]);
+ break;
+ case eColorID_inactivecaption:
+ aColor = GetColorFromNSColor([NSColor controlBackgroundColor]);
+ break;
+ case eColorID_inactivecaptiontext:
+ aColor = NS_RGB(0x45,0x45,0x45);
+ break;
+ case eColorID_scrollbar:
+ aColor = GetColorFromNSColor([NSColor scrollBarColor]);
+ break;
+ case eColorID_threeddarkshadow:
+ aColor = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_threedshadow:
+ aColor = NS_RGB(0xE0,0xE0,0xE0);
+ break;
+ case eColorID_threedface:
+ aColor = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_threedhighlight:
+ aColor = GetColorFromNSColor([NSColor highlightColor]);
+ break;
+ case eColorID_threedlightshadow:
+ aColor = NS_RGB(0xDA,0xDA,0xDA);
+ break;
+ case eColorID_menu:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlTextColor]);
+ break;
+ case eColorID_infobackground:
+ aColor = NS_RGB(0xFF,0xFF,0xC7);
+ break;
+ case eColorID_windowframe:
+ aColor = GetColorFromNSColor([NSColor gridColor]);
+ break;
+ case eColorID_window:
+ case eColorID__moz_field:
+ case eColorID__moz_combobox:
+ aColor = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID__moz_fieldtext:
+ case eColorID__moz_comboboxtext:
+ aColor = GetColorFromNSColor([NSColor controlTextColor]);
+ break;
+ case eColorID__moz_dialog:
+ aColor = GetColorFromNSColor([NSColor controlHighlightColor]);
+ break;
+ case eColorID__moz_dialogtext:
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = GetColorFromNSColor([NSColor controlTextColor]);
+ break;
+ case eColorID__moz_dragtargetzone:
+ aColor = GetColorFromNSColor([NSColor selectedControlColor]);
+ break;
+ case eColorID__moz_mac_chrome_active:
+ case eColorID__moz_mac_chrome_inactive: {
+ int grey = NativeGreyColorAsInt(toolbarFillGrey, (aID == eColorID__moz_mac_chrome_active));
+ aColor = NS_RGB(grey, grey, grey);
+ }
+ break;
+ case eColorID__moz_mac_focusring:
+ aColor = GetColorFromNSColorWithAlpha([NSColor keyboardFocusIndicatorColor], 0.48);
+ break;
+ case eColorID__moz_mac_menushadow:
+ aColor = NS_RGB(0xA3,0xA3,0xA3);
+ break;
+ case eColorID__moz_mac_menutextdisable:
+ aColor = NS_RGB(0x98,0x98,0x98);
+ break;
+ case eColorID__moz_mac_menutextselect:
+ aColor = GetColorFromNSColor([NSColor selectedMenuItemTextColor]);
+ break;
+ case eColorID__moz_mac_disabledtoolbartext:
+ aColor = GetColorFromNSColor([NSColor disabledControlTextColor]);
+ break;
+ case eColorID__moz_mac_menuselect:
+ aColor = GetColorFromNSColor([NSColor alternateSelectedControlColor]);
+ break;
+ case eColorID__moz_buttondefault:
+ aColor = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ case eColorID__moz_mac_secondaryhighlight:
+ // For inactive list selection
+ aColor = GetColorFromNSColor([NSColor secondarySelectedControlColor]);
+ break;
+ case eColorID__moz_eventreerow:
+ // Background color of even list rows.
+ aColor = GetColorFromNSColor([[NSColor controlAlternatingRowBackgroundColors] objectAtIndex:0]);
+ break;
+ case eColorID__moz_oddtreerow:
+ // Background color of odd list rows.
+ aColor = GetColorFromNSColor([[NSColor controlAlternatingRowBackgroundColors] objectAtIndex:1]);
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ // There appears to be no available system defined color. HARDCODING to the appropriate color.
+ aColor = NS_RGB(0x14,0x4F,0xAE);
+ break;
+ default:
+ NS_WARNING("Someone asked nsILookAndFeel for a color I don't know about");
+ aColor = NS_RGB(0xff,0xff,0xff);
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = 567;
+ break;
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case eIntID_SubmenuDelay:
+ aResult = 200;
+ break;
+ case eIntID_TooltipDelay:
+ aResult = 500;
+ break;
+ case eIntID_MenusCanOverlapOSBar:
+ // xul popups are not allowed to overlap the menubar.
+ aResult = 0;
+ break;
+ case eIntID_SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case eIntID_DragThresholdX:
+ case eIntID_DragThresholdY:
+ aResult = 4;
+ break;
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrow_None;
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_UseOverlayScrollbars:
+ if (!mUseOverlayScrollbarsCached) {
+ mUseOverlayScrollbars = SystemWantsOverlayScrollbars() ? 1 : 0;
+ mUseOverlayScrollbarsCached = true;
+ }
+ aResult = mUseOverlayScrollbars;
+ break;
+ case eIntID_AllowOverlayScrollbarsOverlap:
+ if (!mAllowOverlayScrollbarsOverlapCached) {
+ mAllowOverlayScrollbarsOverlap = AllowOverlayScrollbarsOverlap() ? 1 : 0;
+ mAllowOverlayScrollbarsOverlapCached = true;
+ }
+ aResult = mAllowOverlayScrollbarsOverlap;
+ break;
+ case eIntID_ScrollbarDisplayOnMouseMove:
+ aResult = 0;
+ break;
+ case eIntID_ScrollbarFadeBeginDelay:
+ aResult = 450;
+ break;
+ case eIntID_ScrollbarFadeDuration:
+ aResult = 200;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_DWMCompositor:
+ case eIntID_WindowsClassic:
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_TouchEnabled:
+ case eIntID_WindowsThemeIdentifier:
+ case eIntID_OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_MacGraphiteTheme:
+ aResult = [NSColor currentControlTint] == NSGraphiteControlTint;
+ break;
+ case eIntID_MacLionTheme:
+ aResult = 1;
+ break;
+ case eIntID_MacYosemiteTheme:
+ aResult = nsCocoaFeatures::OnYosemiteOrLater();
+ break;
+ case eIntID_AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case eIntID_TabFocusModel:
+ aResult = [NSApp isFullKeyboardAccessEnabled] ?
+ nsIContent::eTabFocus_any : nsIContent::eTabFocus_textControlsMask;
+ break;
+ case eIntID_ScrollToClick:
+ {
+ aResult = [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleScrollerPagingBehavior"];
+ }
+ break;
+ case eIntID_ChosenMenuItemsShouldBlink:
+ aResult = 1;
+ break;
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
+ break;
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+ case eIntID_SwipeAnimationEnabled:
+ aResult = 0;
+ if ([NSEvent respondsToSelector:@selector(
+ isSwipeTrackingFromScrollEventsEnabled)]) {
+ aResult = [NSEvent isSwipeTrackingFromScrollEventsEnabled] ? 1 : 0;
+ }
+ break;
+ case eIntID_ColorPickerAvailable:
+ aResult = 1;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ aResult = -6;
+ break;
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 1;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+bool nsLookAndFeel::UseOverlayScrollbars()
+{
+ return GetInt(eIntID_UseOverlayScrollbars) != 0;
+}
+
+bool nsLookAndFeel::SystemWantsOverlayScrollbars()
+{
+ return ([NSScroller respondsToSelector:@selector(preferredScrollerStyle)] &&
+ [NSScroller preferredScrollerStyle] == mozNSScrollerStyleOverlay);
+}
+
+bool nsLookAndFeel::AllowOverlayScrollbarsOverlap()
+{
+ return (UseOverlayScrollbars());
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString &aFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // hack for now
+ if (aID == eFont_Window || aID == eFont_Document) {
+ aFontStyle.style = NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight = NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+ aFontStyle.size = 14 * aDevPixPerCSSPixel;
+ aFontStyle.systemFont = true;
+
+ aFontName.AssignLiteral("sans-serif");
+ return true;
+ }
+
+ gfxPlatformMac::LookupSystemFont(aID, aFontName, aFontStyle,
+ aDevPixPerCSSPixel);
+
+ return true;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+nsTArray<LookAndFeelInt>
+nsLookAndFeel::GetIntCacheImpl()
+{
+ nsTArray<LookAndFeelInt> lookAndFeelIntCache =
+ nsXPLookAndFeel::GetIntCacheImpl();
+
+ LookAndFeelInt useOverlayScrollbars;
+ useOverlayScrollbars.id = eIntID_UseOverlayScrollbars;
+ useOverlayScrollbars.value = GetInt(eIntID_UseOverlayScrollbars);
+ lookAndFeelIntCache.AppendElement(useOverlayScrollbars);
+
+ LookAndFeelInt allowOverlayScrollbarsOverlap;
+ allowOverlayScrollbarsOverlap.id = eIntID_AllowOverlayScrollbarsOverlap;
+ allowOverlayScrollbarsOverlap.value = GetInt(eIntID_AllowOverlayScrollbarsOverlap);
+ lookAndFeelIntCache.AppendElement(allowOverlayScrollbarsOverlap);
+
+ return lookAndFeelIntCache;
+}
+
+void
+nsLookAndFeel::SetIntCacheImpl(const nsTArray<LookAndFeelInt>& aLookAndFeelIntCache)
+{
+ for (auto entry : aLookAndFeelIntCache) {
+ switch(entry.id) {
+ case eIntID_UseOverlayScrollbars:
+ mUseOverlayScrollbars = entry.value;
+ mUseOverlayScrollbarsCached = true;
+ break;
+ case eIntID_AllowOverlayScrollbarsOverlap:
+ mAllowOverlayScrollbarsOverlap = entry.value;
+ mAllowOverlayScrollbarsOverlapCached = true;
+ break;
+ }
+ }
+}
+
+void
+nsLookAndFeel::RefreshImpl()
+{
+ // We should only clear the cache if we're in the main browser process.
+ // Otherwise, we should wait for the parent to inform us of new values
+ // to cache via LookAndFeel::SetIntCache.
+ if (XRE_IsParentProcess()) {
+ mUseOverlayScrollbarsCached = false;
+ mAllowOverlayScrollbarsOverlapCached = false;
+ }
+}
diff --git a/widget/cocoa/nsMacCursor.h b/widget/cocoa/nsMacCursor.h
new file mode 100644
index 0000000000..cf9c84c7ea
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.h
@@ -0,0 +1,105 @@
+/* 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/. */
+
+#ifndef nsMacCursor_h_
+#define nsMacCursor_h_
+
+#import <Cocoa/Cocoa.h>
+#import "nsIWidget.h"
+
+/*! @class nsMacCursor
+ @abstract Represents a native Mac cursor.
+ @discussion <code>nsMacCursor</code> provides a simple API for creating and working with native Macintosh cursors.
+ Cursors can be created used without needing to be aware of the way different cursors are implemented,
+ in particular the details of managing an animated cursor are hidden.
+*/
+@interface nsMacCursor : NSObject
+{
+ @private
+ NSTimer *mTimer;
+ @protected
+ nsCursor mType;
+ int mFrameCounter;
+}
+
+/*! @method cursorWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> representing the given <code>NSCursor</code>
+ */
++ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType;
+
+/*! @method cursorWithImageNamed:hotSpot:type:
+ @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot.
+ @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method.
+ <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary
+ by operating system version.</p>
+ <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that uses the given image and hotspot
+ */
++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType;
+
+/*! @method cursorWithFrames:type:
+ @abstract Create an animated cursor by specifying the frames to use for the animation.
+ @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array
+ must be an instance of <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the
+ order they should be played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an autoreleased instance of <code>nsMacCursor</code> that will animate the given cursor frames
+ */
++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType;
+
+/*! @method cocoaCursorWithImageNamed:hotSpot:
+ @abstract Create a Cocoa NSCursor object with a Gecko image resource name and a hotspot point.
+ @discussion Create a Cocoa NSCursor object with a Gecko image resource name and a hotspot point.
+ @param imageName the name of the gecko image resource, "tiff" extension is assumed, do not append.
+ @param aPoint the point within the cursor to use as the hotspot
+ @result an autoreleased instance of <code>nsMacCursor</code> that will animate the given cursor frames
+ */
++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint;
+
+/*! @method isSet
+ @abstract Determines whether this cursor is currently active.
+ @discussion This can be helpful when the Cocoa NSCursor state can be influenced without going
+ through nsCursorManager.
+ @result whether the cursor is currently set
+ */
+- (BOOL) isSet;
+
+/*! @method set
+ @abstract Set the cursor.
+ @discussion Makes this cursor the current cursor. If the cursor is animated, the animation is started.
+ */
+- (void) set;
+
+/*! @method unset
+ @abstract Unset the cursor. The cursor will return to the default (usually the arrow cursor).
+ @discussion Unsets the cursor. If the cursor is animated, the animation is stopped.
+ */
+- (void) unset;
+
+/*! @method isAnimated
+ @abstract Tests whether this cursor is animated.
+ @discussion Use this method to determine whether a cursor is animated
+ @result YES if the cursor is animated (has more than one frame), NO if it is a simple static cursor.
+ */
+- (BOOL) isAnimated;
+
+/** @method cursorType
+ @abstract Get the cursor type for this cursor
+ @discussion This method returns the <code>nsCursor</code> constant that corresponds to this cursor, which is
+ equivalent to the CSS name for the cursor.
+ @result The nsCursor constant corresponding to this cursor, or nsCursor's 'eCursorCount' if the cursor
+ is a custom cursor loaded from a URI
+ */
+- (nsCursor) type;
+@end
+
+#endif // nsMacCursor_h_
diff --git a/widget/cocoa/nsMacCursor.mm b/widget/cocoa/nsMacCursor.mm
new file mode 100644
index 0000000000..4fcdfd3e5d
--- /dev/null
+++ b/widget/cocoa/nsMacCursor.mm
@@ -0,0 +1,382 @@
+/* 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 "nsMacCursor.h"
+#include "nsObjCExceptions.h"
+#include "nsDebug.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+/*! @category nsMacCursor (PrivateMethods)
+ @abstract Private methods internal to the nsMacCursor class.
+ @discussion <code>nsMacCursor</code> is effectively an abstract class. It does not define complete
+ behaviour in and of itself, the subclasses defined in this file provide the useful implementations.
+*/
+@interface nsMacCursor (PrivateMethods)
+
+/*! @method getNextCursorFrame
+ @abstract get the index of the next cursor frame to display.
+ @discussion Increments and returns the frame counter of an animated cursor.
+ @result The index of the next frame to display in the cursor animation
+*/
+- (int) getNextCursorFrame;
+
+/*! @method numFrames
+ @abstract Query the number of frames in this cursor's animation.
+ @discussion Returns the number of frames in this cursor's animation. Static cursors return 1.
+*/
+- (int) numFrames;
+
+/*! @method createTimer
+ @abstract Create a Timer to use to animate the cursor.
+ @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor animation.
+ This method should only be called for cursors that are animated.
+*/
+- (void) createTimer;
+
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
+ */
+- (void) destroyTimer;
+/*! @method destroyTimer
+ @abstract Destroy any timer instance associated with this cursor.
+ @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
+*/
+
+/*! @method advanceAnimatedCursor:
+ @abstract Method called by animation timer to perform animation.
+ @discussion Called by an animated cursor's associated timer to advance the animation to the next frame.
+ Determines which frame should occur next and sets the cursor to that frame.
+ @param aTimer the timer causing the animation
+*/
+- (void) advanceAnimatedCursor: (NSTimer *) aTimer;
+
+/*! @method setFrame:
+ @abstract Sets the current cursor, using an index to determine which frame in the animation to display.
+ @discussion Sets the current cursor. The frame index determines which frame is shown if the cursor is animated.
+ Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] - 1</code>. A static cursor
+ has a single frame, numbered 0.
+ @param aFrameIndex the index indicating which frame from the animation to display
+*/
+- (void) setFrame: (int) aFrameIndex;
+
+@end
+
+/*! @class nsCocoaCursor
+ @abstract Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code> instances.
+ @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances. These can be either
+ built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s created from images.
+ When more than one <code>NSCursor</code> is provided, the cursor will use these as animation frames.
+*/
+@interface nsCocoaCursor : nsMacCursor
+{
+ @private
+ NSArray *mFrames;
+ NSCursor *mLastSetCocoaCursor;
+}
+
+/*! @method initWithFrames:
+ @abstract Create an animated cursor by specifying the frames to use for the animation.
+ @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array
+ must be an instance of <code>NSCursor</code>
+ @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the
+ order they should be played.
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames
+ */
+- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType;
+
+/*! @method initWithCursor:
+ @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
+ @discussion Creates a cursor representing the given Cocoa built-in cursor.
+ @param aCursor the <code>NSCursor</code> to use
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> representing the given <code>NSCursor</code>
+*/
+- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType;
+
+/*! @method initWithImageNamed:hotSpot:
+ @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot.
+ @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method.
+ <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary
+ by operating system version.</p>
+ <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p>
+ @param aCursor the name of the image to use for the cursor
+ @param aPoint the point within the cursor to use as the hotspot
+ @param aType the corresponding <code>nsCursor</code> constant
+ @result an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot
+*/
+- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType;
+
+@end
+
+@implementation nsMacCursor
+
++ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsCOMPtr<nsIFile> resDir;
+ nsAutoCString resPath;
+ NSString* pathToImage, *pathToHiDpiImage;
+ NSImage* cursorImage, *hiDpiCursorImage;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
+ if (NS_FAILED(rv))
+ goto INIT_FAILURE;
+ resDir->AppendNative(NS_LITERAL_CSTRING("res"));
+ resDir->AppendNative(NS_LITERAL_CSTRING("cursors"));
+
+ rv = resDir->GetNativePath(resPath);
+ if (NS_FAILED(rv))
+ goto INIT_FAILURE;
+
+ pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
+ if (!pathToImage)
+ goto INIT_FAILURE;
+ pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
+ pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
+ // Add same extension to both image paths.
+ pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
+ pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
+
+ cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
+ if (!cursorImage)
+ goto INIT_FAILURE;
+
+ // Note 1: There are a few different ways to get a hidpi image via
+ // initWithContentsOfFile. We let the OS handle this here: when the
+ // file basename ends in "@2x", it will be displayed at native resolution
+ // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways.
+ //
+ // Note 2: The OS is picky, and will ignore the hidpi representation
+ // unless it is exactly twice the size of the lowdpi image.
+ hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
+ if (hiDpiCursorImage) {
+ NSImageRep *imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
+ [cursorImage addRepresentation: imageRep];
+ }
+ return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease];
+
+INIT_FAILURE:
+ NS_WARNING("Problem getting path to cursor image file!");
+ [self release];
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL) isSet
+{
+ // implemented by subclasses
+ return NO;
+}
+
+- (void) set
+{
+ if ([self isAnimated]) {
+ [self createTimer];
+ }
+ // if the cursor isn't animated or the timer creation fails for any reason...
+ if (!mTimer) {
+ [self setFrame:0];
+ }
+}
+
+- (void) unset
+{
+ [self destroyTimer];
+}
+
+- (BOOL) isAnimated
+{
+ return [self numFrames] > 1;
+}
+
+- (int) numFrames
+{
+ // subclasses need to override this to support animation
+ return 1;
+}
+
+- (int) getNextCursorFrame
+{
+ mFrameCounter = (mFrameCounter + 1) % [self numFrames];
+ return mFrameCounter;
+}
+
+- (void) createTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!mTimer) {
+ mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
+ target:self
+ selector:@selector(advanceAnimatedCursor:)
+ userInfo:nil
+ repeats:YES] retain];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) destroyTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTimer) {
+ [mTimer invalidate];
+ [mTimer release];
+ mTimer = nil;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) advanceAnimatedCursor: (NSTimer *) aTimer
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([aTimer isValid]) {
+ [self setFrame:[self getNextCursorFrame]];
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void) setFrame: (int) aFrameIndex
+{
+ // subclasses need to do something useful here
+}
+
+- (nsCursor) type {
+ return mType;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self destroyTimer];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+@implementation nsCocoaCursor
+
+- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ self = [super init];
+ NSEnumerator *it = [aCursorFrames objectEnumerator];
+ NSObject *frame = nil;
+ while ((frame = [it nextObject])) {
+ NS_ASSERTION([frame isKindOfClass:[NSCursor class]], "Invalid argument: All frames must be of type NSCursor");
+ }
+ mFrames = [aCursorFrames retain];
+ mFrameCounter = 0;
+ mType = aType;
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSArray *frame = [NSArray arrayWithObjects:aCursor, nil];
+ return [self initWithFrames:frame type:aType];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL) isSet
+{
+ return [NSCursor currentCursor] == mLastSetCocoaCursor;
+}
+
+- (void) setFrame: (int) aFrameIndex
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
+ [newCursor set];
+ mLastSetCocoaCursor = newCursor;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (int) numFrames
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [mFrames count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (NSString *) description
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mFrames description];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void) dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mFrames release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/nsMacDockSupport.h b/widget/cocoa/nsMacDockSupport.h
new file mode 100644
index 0000000000..a638b89e03
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.h
@@ -0,0 +1,41 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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 "nsIMacDockSupport.h"
+#include "nsIStandaloneNativeMenu.h"
+#include "nsITaskbarProgress.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsNativeThemeCocoa.h"
+
+class nsMacDockSupport : public nsIMacDockSupport, public nsITaskbarProgress
+{
+public:
+ nsMacDockSupport();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACDOCKSUPPORT
+ NS_DECL_NSITASKBARPROGRESS
+
+protected:
+ virtual ~nsMacDockSupport();
+
+ nsCOMPtr<nsIStandaloneNativeMenu> mDockMenu;
+ nsString mBadgeText;
+
+ NSImage *mAppIcon, *mProgressBackground;
+
+ HIRect mProgressBounds;
+ nsTaskbarProgressState mProgressState;
+ double mProgressFraction;
+ nsCOMPtr<nsITimer> mProgressTimer;
+ RefPtr<nsNativeThemeCocoa> mTheme;
+
+ static void RedrawIconCallback(nsITimer* aTimer, void* aClosure);
+
+ bool InitProgress();
+ nsresult RedrawIcon();
+};
diff --git a/widget/cocoa/nsMacDockSupport.mm b/widget/cocoa/nsMacDockSupport.mm
new file mode 100644
index 0000000000..56b37822bd
--- /dev/null
+++ b/widget/cocoa/nsMacDockSupport.mm
@@ -0,0 +1,174 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsMacDockSupport.h"
+#include "nsObjCExceptions.h"
+
+NS_IMPL_ISUPPORTS(nsMacDockSupport, nsIMacDockSupport, nsITaskbarProgress)
+
+nsMacDockSupport::nsMacDockSupport()
+: mAppIcon(nil)
+, mProgressBackground(nil)
+, mProgressState(STATE_NO_PROGRESS)
+, mProgressFraction(0.0)
+{
+ mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+}
+
+nsMacDockSupport::~nsMacDockSupport()
+{
+ if (mAppIcon) {
+ [mAppIcon release];
+ mAppIcon = nil;
+ }
+ if (mProgressBackground) {
+ [mProgressBackground release];
+ mProgressBackground = nil;
+ }
+ if (mProgressTimer) {
+ mProgressTimer->Cancel();
+ mProgressTimer = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetDockMenu(nsIStandaloneNativeMenu ** aDockMenu)
+{
+ nsCOMPtr<nsIStandaloneNativeMenu> dockMenu(mDockMenu);
+ dockMenu.forget(aDockMenu);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetDockMenu(nsIStandaloneNativeMenu * aDockMenu)
+{
+ mDockMenu = aDockMenu;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::ActivateApplication(bool aIgnoreOtherApplications)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ [[NSApplication sharedApplication] activateIgnoringOtherApps:aIgnoreOtherApplications];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetBadgeText(const nsAString& aBadgeText)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSDockTile *tile = [[NSApplication sharedApplication] dockTile];
+ mBadgeText = aBadgeText;
+ if (aBadgeText.IsEmpty())
+ [tile setBadgeLabel: nil];
+ else
+ [tile setBadgeLabel:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(mBadgeText.get())
+ length:mBadgeText.Length()]];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::GetBadgeText(nsAString& aBadgeText)
+{
+ aBadgeText = mBadgeText;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacDockSupport::SetProgressState(nsTaskbarProgressState aState,
+ uint64_t aCurrentValue,
+ uint64_t aMaxValue)
+{
+ NS_ENSURE_ARG_RANGE(aState, 0, STATE_PAUSED);
+ if (aState == STATE_NO_PROGRESS || aState == STATE_INDETERMINATE) {
+ NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
+ }
+ if (aCurrentValue > aMaxValue) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ mProgressState = aState;
+ if (aMaxValue == 0) {
+ mProgressFraction = 0;
+ } else {
+ mProgressFraction = (double)aCurrentValue / aMaxValue;
+ }
+
+ if (mProgressState == STATE_NORMAL || mProgressState == STATE_INDETERMINATE) {
+ int perSecond = 8; // Empirically determined, see bug 848792
+ mProgressTimer->InitWithFuncCallback(RedrawIconCallback, this, 1000 / perSecond,
+ nsITimer::TYPE_REPEATING_SLACK);
+ return NS_OK;
+ } else {
+ mProgressTimer->Cancel();
+ return RedrawIcon();
+ }
+}
+
+// static
+void nsMacDockSupport::RedrawIconCallback(nsITimer* aTimer, void* aClosure)
+{
+ static_cast<nsMacDockSupport*>(aClosure)->RedrawIcon();
+}
+
+// Return whether to draw progress
+bool nsMacDockSupport::InitProgress()
+{
+ if (mProgressState != STATE_NORMAL && mProgressState != STATE_INDETERMINATE) {
+ return false;
+ }
+
+ if (!mAppIcon) {
+ mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ mAppIcon = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
+ mProgressBackground = [mAppIcon copyWithZone:nil];
+ mTheme = new nsNativeThemeCocoa();
+
+ NSSize sz = [mProgressBackground size];
+ mProgressBounds = CGRectMake(sz.width * 1/32, sz.height * 3/32,
+ sz.width * 30/32, sz.height * 4/32);
+ [mProgressBackground lockFocus];
+ [[NSColor whiteColor] set];
+ NSRectFill(NSRectFromCGRect(mProgressBounds));
+ [mProgressBackground unlockFocus];
+ }
+ return true;
+}
+
+nsresult
+nsMacDockSupport::RedrawIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (InitProgress()) {
+ // TODO: - Implement ERROR and PAUSED states?
+ NSImage *icon = [mProgressBackground copyWithZone:nil];
+ bool isIndeterminate = (mProgressState != STATE_NORMAL);
+
+ [icon lockFocus];
+ CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ mTheme->DrawProgress(ctx, mProgressBounds, isIndeterminate,
+ true, mProgressFraction, 1.0, NULL);
+ [icon unlockFocus];
+ [NSApp setApplicationIconImage:icon];
+ [icon release];
+ } else {
+ [NSApp setApplicationIconImage:mAppIcon];
+ }
+
+ return NS_OK;
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMacWebAppUtils.h b/widget/cocoa/nsMacWebAppUtils.h
new file mode 100644
index 0000000000..98ef235615
--- /dev/null
+++ b/widget/cocoa/nsMacWebAppUtils.h
@@ -0,0 +1,22 @@
+/* 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/. */
+#ifndef _MAC_WEB_APP_UTILS_H_
+#define _MAC_WEB_APP_UTILS_H_
+
+#include "nsIMacWebAppUtils.h"
+
+#define NS_MACWEBAPPUTILS_CONTRACTID "@mozilla.org/widget/mac-web-app-utils;1"
+
+class nsMacWebAppUtils : public nsIMacWebAppUtils {
+public:
+ nsMacWebAppUtils() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACWEBAPPUTILS
+
+protected:
+ virtual ~nsMacWebAppUtils() {}
+};
+
+#endif //_MAC_WEB_APP_UTILS_H_
diff --git a/widget/cocoa/nsMacWebAppUtils.mm b/widget/cocoa/nsMacWebAppUtils.mm
new file mode 100644
index 0000000000..1b98cef7cc
--- /dev/null
+++ b/widget/cocoa/nsMacWebAppUtils.mm
@@ -0,0 +1,82 @@
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMacWebAppUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+#include "nsString.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+// Find the path to the app with the given bundleIdentifier, if any.
+// Note that the OS will return the path to the newest binary, if there is more than one.
+// The determination of 'newest' is complex and beyond the scope of this comment.
+
+NS_IMPL_ISUPPORTS(nsMacWebAppUtils, nsIMacWebAppUtils)
+
+NS_IMETHODIMP nsMacWebAppUtils::PathForAppWithIdentifier(const nsAString& bundleIdentifier, nsAString& outPath) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ outPath.Truncate();
+
+ nsAutoreleasePool localPool;
+
+ //note that the result of this expression might be nil, meaning no matching app was found.
+ NSString* temp = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)bundleIdentifier).get())
+ length:((nsString)bundleIdentifier).Length()]];
+
+ if (temp) {
+ // Copy out the resultant absolute path into outPath if non-nil.
+ nsCocoaUtils::GetStringForNSString(temp, outPath);
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::LaunchAppWithIdentifier(const nsAString& bundleIdentifier) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsAutoreleasePool localPool;
+
+ // Note this might return false, meaning the app wasnt launched for some reason.
+ BOOL success = [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:
+ [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)bundleIdentifier).get())
+ length:((nsString)bundleIdentifier).Length()]
+ options: (NSWorkspaceLaunchOptions)0
+ additionalEventParamDescriptor: nil
+ launchIdentifier: NULL];
+
+ return success ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsMacWebAppUtils::TrashApp(const nsAString& path, nsITrashAppCallback* aCallback) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!aCallback)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsITrashAppCallback> callback = aCallback;
+
+ NSString* tempString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)path).get())
+ length:path.Length()];
+
+ [[NSWorkspace sharedWorkspace] recycleURLs: [NSArray arrayWithObject:[NSURL fileURLWithPath:tempString]]
+ completionHandler: ^(NSDictionary *newURLs, NSError *error) {
+ nsresult rv = (error == nil) ? NS_OK : NS_ERROR_FAILURE;
+ callback->TrashAppFinished(rv);
+ }];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMenuBarX.h b/widget/cocoa/nsMenuBarX.h
new file mode 100644
index 0000000000..7cbb8ce62a
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.h
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsMenuBarX_h_
+#define nsMenuBarX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+#include "nsINativeMenuService.h"
+#include "nsString.h"
+
+class nsMenuX;
+class nsIWidget;
+class nsIContent;
+
+// The native menu service for creating native menu bars.
+class nsNativeMenuServiceX : public nsINativeMenuService
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsNativeMenuServiceX() {}
+
+ NS_IMETHOD CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode) override;
+
+protected:
+ virtual ~nsNativeMenuServiceX() {}
+};
+
+// Objective-C class used to allow us to intervene with keyboard event handling.
+// We allow mouse actions to work normally.
+@interface GeckoNSMenu : NSMenu
+{
+}
+@end
+
+// Objective-C class used as action target for menu items
+@interface NativeMenuItemTarget : NSObject
+{
+}
+-(IBAction)menuItemHit:(id)sender;
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances.
+@interface GeckoServicesNSMenuItem : NSMenuItem
+{
+}
+- (id) target;
+- (SEL) action;
+- (void) _doNothing:(id)sender;
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+@interface GeckoServicesNSMenu : NSMenu
+{
+}
+- (void)addItem:(NSMenuItem *)newItem;
+- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv;
+- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index;
+- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index;
+- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuBarX : public nsMenuGroupOwnerX, public nsChangeObserver
+{
+public:
+ nsMenuBarX();
+ virtual ~nsMenuBarX();
+
+ static NativeMenuItemTarget* sNativeEventTarget;
+ static nsMenuBarX* sLastGeckoMenuBarPainted;
+
+ // The following content nodes have been removed from the menu system.
+ // We save them here for use in command handling.
+ nsCOMPtr<nsIContent> mAboutItemContent;
+ nsCOMPtr<nsIContent> mPrefItemContent;
+ nsCOMPtr<nsIContent> mQuitItemContent;
+
+ // nsChangeObserver
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenu;}
+ nsMenuObjectTypeX MenuObjectType() override {return eMenuBarObjectType;}
+
+ // nsMenuBarX
+ nsresult Create(nsIWidget* aParent, nsIContent* aContent);
+ void SetParent(nsIWidget* aParent);
+ uint32_t GetMenuCount();
+ bool MenuContainsAppMenu();
+ nsMenuX* GetMenuAt(uint32_t aIndex);
+ nsMenuX* GetXULHelpMenu();
+ void SetSystemHelpMenu();
+ nsresult Paint();
+ void ForceUpdateNativeMenuAt(const nsAString& indexString);
+ void ForceNativeMenuReload(); // used for testing
+ static char GetLocalizedAccelKey(const char *shortcutID);
+ static void ResetNativeApplicationMenu();
+
+protected:
+ void ConstructNativeMenus();
+ void ConstructFallbackNativeMenus();
+ nsresult InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex);
+ void RemoveMenuAtIndex(uint32_t aIndex);
+ void HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode);
+ void AquifyMenuBar();
+ NSMenuItem* CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
+ int tag, NativeMenuItemTarget* target);
+ nsresult CreateApplicationMenu(nsMenuX* inMenu);
+
+ nsTArray<mozilla::UniquePtr<nsMenuX>> mMenuArray;
+ nsIWidget* mParentWindow; // [weak]
+ GeckoNSMenu* mNativeMenu; // root menu, representing entire menu bar
+};
+
+#endif // nsMenuBarX_h_
diff --git a/widget/cocoa/nsMenuBarX.mm b/widget/cocoa/nsMenuBarX.mm
new file mode 100644
index 0000000000..ff25eb81fc
--- /dev/null
+++ b/widget/cocoa/nsMenuBarX.mm
@@ -0,0 +1,979 @@
+/* -*- Mode: C++; tab-width: 2; 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 <objc/objc-runtime.h>
+
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+#include "nsChildView.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+
+#include "nsIContent.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIAppStartup.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+
+NativeMenuItemTarget* nsMenuBarX::sNativeEventTarget = nil;
+nsMenuBarX* nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
+NSMenu* sApplicationMenu = nil;
+BOOL sApplicationMenuIsFallback = NO;
+BOOL gSomeMenuBarPainted = NO;
+
+// We keep references to the first quit and pref item content nodes we find, which
+// will be from the hidden window. We use these when the document for the current
+// window does not have a quit or pref item. We don't need strong refs here because
+// these items are always strong ref'd by their owning menu bar (instance variable).
+static nsIContent* sAboutItemContent = nullptr;
+static nsIContent* sPrefItemContent = nullptr;
+static nsIContent* sQuitItemContent = nullptr;
+
+NS_IMPL_ISUPPORTS(nsNativeMenuServiceX, nsINativeMenuService)
+
+NS_IMETHODIMP nsNativeMenuServiceX::CreateNativeMenuBar(nsIWidget* aParent, nsIContent* aMenuBarNode)
+{
+ NS_ASSERTION(NS_IsMainThread(), "Attempting to create native menu bar on wrong thread!");
+
+ RefPtr<nsMenuBarX> mb = new nsMenuBarX();
+ if (!mb)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mb->Create(aParent, aMenuBarNode);
+}
+
+nsMenuBarX::nsMenuBarX()
+: nsMenuGroupOwnerX(), mParentWindow(nullptr)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mNativeMenu = [[GeckoNSMenu alloc] initWithTitle:@"MainMenuBar"];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsMenuBarX::~nsMenuBarX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (nsMenuBarX::sLastGeckoMenuBarPainted == this)
+ nsMenuBarX::sLastGeckoMenuBarPainted = nullptr;
+
+ // the quit/pref items of a random window might have been used if there was no
+ // hidden window, thus we need to invalidate the weak references.
+ if (sAboutItemContent == mAboutItemContent)
+ sAboutItemContent = nullptr;
+ if (sQuitItemContent == mQuitItemContent)
+ sQuitItemContent = nullptr;
+ if (sPrefItemContent == mPrefItemContent)
+ sPrefItemContent = nullptr;
+
+ // make sure we unregister ourselves as a content observer
+ if (mContent) {
+ UnregisterForContentChanges(mContent);
+ }
+
+ // We have to manually clear the array here because clearing causes menu items
+ // to call back into the menu bar to unregister themselves. We don't want to
+ // depend on member variable ordering to ensure that the array gets cleared
+ // before the registration hash table is destroyed.
+ mMenuArray.Clear();
+
+ [mNativeMenu release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuBarX::Create(nsIWidget* aParent, nsIContent* aContent)
+{
+ if (!aParent)
+ return NS_ERROR_INVALID_ARG;
+
+ mParentWindow = aParent;
+ mContent = aContent;
+
+ if (mContent) {
+ AquifyMenuBar();
+
+ nsresult rv = nsMenuGroupOwnerX::Create(mContent);
+ if (NS_FAILED(rv))
+ return rv;
+
+ RegisterForContentChanges(mContent, this);
+ ConstructNativeMenus();
+ } else {
+ ConstructFallbackNativeMenus();
+ }
+
+ // Give this to the parent window. The parent takes ownership.
+ static_cast<nsCocoaWindow*>(mParentWindow)->SetMenuBar(this);
+
+ return NS_OK;
+}
+
+void nsMenuBarX::ConstructNativeMenus()
+{
+ uint32_t count = mContent->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *menuContent = mContent->GetChildAt(i);
+ if (menuContent &&
+ menuContent->IsXULElement(nsGkAtoms::menu)) {
+ nsMenuX* newMenu = new nsMenuX();
+ if (newMenu) {
+ nsresult rv = newMenu->Create(this, this, menuContent);
+ if (NS_SUCCEEDED(rv))
+ InsertMenuAtIndex(newMenu, GetMenuCount());
+ else
+ delete newMenu;
+ }
+ }
+ }
+}
+
+void nsMenuBarX::ConstructFallbackNativeMenus()
+{
+ if (sApplicationMenu) {
+ // Menu has already been built.
+ return;
+ }
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/fallbackMenubar.properties", getter_AddRefs(stringBundle));
+
+ if (!stringBundle) {
+ return;
+ }
+
+ nsXPIDLString labelUTF16;
+ nsXPIDLString keyUTF16;
+
+ const char16_t* labelProp = u"quitMenuitem.label";
+ const char16_t* keyProp = u"quitMenuitem.key";
+
+ stringBundle->GetStringFromName(labelProp, getter_Copies(labelUTF16));
+ stringBundle->GetStringFromName(keyProp, getter_Copies(keyUTF16));
+
+ NSString* labelStr = [NSString stringWithUTF8String:
+ NS_ConvertUTF16toUTF8(labelUTF16).get()];
+ NSString* keyStr= [NSString stringWithUTF8String:
+ NS_ConvertUTF16toUTF8(keyUTF16).get()];
+
+ if (!nsMenuBarX::sNativeEventTarget) {
+ nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
+ }
+
+ sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+ NSMenuItem* quitMenuItem = [[[NSMenuItem alloc] initWithTitle:labelStr
+ action:@selector(menuItemHit:)
+ keyEquivalent:keyStr] autorelease];
+ [quitMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [quitMenuItem setTag:eCommand_ID_Quit];
+ [sApplicationMenu addItem:quitMenuItem];
+ sApplicationMenuIsFallback = YES;
+}
+
+uint32_t nsMenuBarX::GetMenuCount()
+{
+ return mMenuArray.Length();
+}
+
+bool nsMenuBarX::MenuContainsAppMenu()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return ([mNativeMenu numberOfItems] > 0 &&
+ [[mNativeMenu itemAtIndex:0] submenu] == sApplicationMenu);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
+}
+
+nsresult nsMenuBarX::InsertMenuAtIndex(nsMenuX* aMenu, uint32_t aIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // If we've only yet created a fallback global Application menu (using
+ // ContructFallbackNativeMenus()), destroy it before recreating it properly.
+ if (sApplicationMenu && sApplicationMenuIsFallback) {
+ ResetNativeApplicationMenu();
+ }
+ // If we haven't created a global Application menu yet, do it.
+ if (!sApplicationMenu) {
+ nsresult rv = NS_OK; // avoid warning about rv being unused
+ rv = CreateApplicationMenu(aMenu);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't create Application menu");
+
+ // Hook the new Application menu up to the menu bar.
+ NSMenu* mainMenu = [NSApp mainMenu];
+ NS_ASSERTION([mainMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+ [[mainMenu itemAtIndex:0] setSubmenu:sApplicationMenu];
+ }
+
+ // add menu to array that owns our menus
+ mMenuArray.InsertElementAt(aIndex, aMenu);
+
+ // hook up submenus
+ nsIContent* menuContent = aMenu->Content();
+ if (menuContent->GetChildCount() > 0 &&
+ !nsMenuUtilsX::NodeIsHiddenOrCollapsed(menuContent)) {
+ int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(this, aMenu);
+ if (MenuContainsAppMenu())
+ insertionIndex++;
+ [mNativeMenu insertItem:aMenu->NativeMenuItem() atIndex:insertionIndex];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsMenuBarX::RemoveMenuAtIndex(uint32_t aIndex)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mMenuArray.Length() <= aIndex) {
+ NS_ERROR("Attempting submenu removal with bad index!");
+ return;
+ }
+
+ // Our native menu and our internal menu object array might be out of sync.
+ // This happens, for example, when a submenu is hidden. Because of this we
+ // should not assume that a native submenu is hooked up.
+ NSMenuItem* nativeMenuItem = mMenuArray[aIndex]->NativeMenuItem();
+ int nativeMenuItemIndex = [mNativeMenu indexOfItem:nativeMenuItem];
+ if (nativeMenuItemIndex != -1)
+ [mNativeMenu removeItemAtIndex:nativeMenuItemIndex];
+
+ mMenuArray.RemoveElementAt(aIndex);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuBarX::ObserveAttributeChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ nsIAtom* aAttribute)
+{
+}
+
+void nsMenuBarX::ObserveContentRemoved(nsIDocument* aDocument,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ RemoveMenuAtIndex(aIndexInContainer);
+}
+
+void nsMenuBarX::ObserveContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild)
+{
+ nsMenuX* newMenu = new nsMenuX();
+ if (newMenu) {
+ nsresult rv = newMenu->Create(this, this, aChild);
+ if (NS_SUCCEEDED(rv))
+ InsertMenuAtIndex(newMenu, aContainer->IndexOf(aChild));
+ else
+ delete newMenu;
+ }
+}
+
+void nsMenuBarX::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return;
+
+ nsMenuX* currentMenu = NULL;
+ int targetIndex = [[indexes objectAtIndex:0] intValue];
+ int visible = 0;
+ uint32_t length = mMenuArray.Length();
+ // first find a menu in the menu bar
+ for (unsigned int i = 0; i < length; i++) {
+ nsMenuX* menu = mMenuArray[i].get();
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+ visible++;
+ if (visible == (targetIndex + 1)) {
+ currentMenu = menu;
+ break;
+ }
+ }
+ }
+
+ if (!currentMenu)
+ return;
+
+ // fake open/close to cause lazy update to happen so submenus populate
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ targetIndex = [[indexes objectAtIndex:i] intValue];
+ visible = 0;
+ length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu)
+ return;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+}
+
+// Calling this forces a full reload of the menu system, reloading all native
+// menus and their items.
+// Without this testing is hard because changes to the DOM affect the native
+// menu system lazily.
+void nsMenuBarX::ForceNativeMenuReload()
+{
+ // tear down everything
+ while (GetMenuCount() > 0)
+ RemoveMenuAtIndex(0);
+
+ // construct everything
+ ConstructNativeMenus();
+}
+
+nsMenuX* nsMenuBarX::GetMenuAt(uint32_t aIndex)
+{
+ if (mMenuArray.Length() <= aIndex) {
+ NS_ERROR("Requesting menu at invalid index!");
+ return NULL;
+ }
+ return mMenuArray[aIndex].get();
+}
+
+nsMenuX* nsMenuBarX::GetXULHelpMenu()
+{
+ // The Help menu is usually (always?) the last one, so we start there and
+ // count back.
+ for (int32_t i = GetMenuCount() - 1; i >= 0; --i) {
+ nsMenuX* aMenu = GetMenuAt(i);
+ if (aMenu && nsMenuX::IsXULHelpMenu(aMenu->Content()))
+ return aMenu;
+ }
+ return nil;
+}
+
+// On SnowLeopard and later we must tell the OS which is our Help menu.
+// Otherwise it will only add Spotlight for Help (the Search item) to our
+// Help menu if its label/title is "Help" -- i.e. if the menu is in English.
+// This resolves bugs 489196 and 539317.
+void nsMenuBarX::SetSystemHelpMenu()
+{
+ nsMenuX* xulHelpMenu = GetXULHelpMenu();
+ if (xulHelpMenu) {
+ NSMenu* helpMenu = (NSMenu*)xulHelpMenu->NativeData();
+ if (helpMenu)
+ [NSApp setHelpMenu:helpMenu];
+ }
+}
+
+nsresult nsMenuBarX::Paint()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Don't try to optimize anything in this painting by checking
+ // sLastGeckoMenuBarPainted because the menubar can be manipulated by
+ // native dialogs and sheet code and other things besides this paint method.
+
+ // We have to keep the same menu item for the Application menu so we keep
+ // passing it along.
+ NSMenu* outgoingMenu = [NSApp mainMenu];
+ NS_ASSERTION([outgoingMenu numberOfItems] > 0, "Main menu does not have any items, something is terribly wrong!");
+
+ NSMenuItem* appMenuItem = [[outgoingMenu itemAtIndex:0] retain];
+ [outgoingMenu removeItemAtIndex:0];
+ [mNativeMenu insertItem:appMenuItem atIndex:0];
+ [appMenuItem release];
+
+ // Set menu bar and event target.
+ [NSApp setMainMenu:mNativeMenu];
+ SetSystemHelpMenu();
+ nsMenuBarX::sLastGeckoMenuBarPainted = this;
+
+ gSomeMenuBarPainted = YES;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Returns the 'key' attribute of the 'shortcutID' object (if any) in the
+// currently active menubar's DOM document. 'shortcutID' should be the id
+// (i.e. the name) of a component that defines a commonly used (and
+// localized) cmd+key shortcut, and belongs to a keyset containing similar
+// objects. For example "key_selectAll". Returns a value that can be
+// compared to the first character of [NSEvent charactersIgnoringModifiers]
+// when [NSEvent modifierFlags] == NSCommandKeyMask.
+char nsMenuBarX::GetLocalizedAccelKey(const char *shortcutID)
+{
+ if (!sLastGeckoMenuBarPainted)
+ return 0;
+
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mContent->OwnerDoc()));
+ if (!domDoc)
+ return 0;
+
+ NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID);
+ nsCOMPtr<nsIDOMElement> shortcutElement;
+ domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement));
+ nsCOMPtr<nsIContent> shortcutContent = do_QueryInterface(shortcutElement);
+ if (!shortcutContent)
+ return 0;
+
+ nsAutoString key;
+ shortcutContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
+ NS_LossyConvertUTF16toASCII keyASC(key.get());
+ const char *keyASCPtr = keyASC.get();
+ if (!keyASCPtr)
+ return 0;
+ // If keyID's 'key' attribute isn't exactly one character long, it's not
+ // what we're looking for.
+ if (strlen(keyASCPtr) != sizeof(char))
+ return 0;
+ // Make sure retval is lower case.
+ char retval = tolower(keyASCPtr[0]);
+
+ return retval;
+}
+
+/* static */
+void nsMenuBarX::ResetNativeApplicationMenu()
+{
+ [sApplicationMenu removeAllItems];
+ [sApplicationMenu release];
+ sApplicationMenu = nil;
+ sApplicationMenuIsFallback = NO;
+}
+
+// Hide the item in the menu by setting the 'hidden' attribute. Returns it in |outHiddenNode| so
+// the caller can hang onto it if they so choose. It is acceptable to pass nsull
+// for |outHiddenNode| if the caller doesn't care about the hidden node.
+void nsMenuBarX::HideItem(nsIDOMDocument* inDoc, const nsAString & inID, nsIContent** outHiddenNode)
+{
+ nsCOMPtr<nsIDOMElement> menuItem;
+ inDoc->GetElementById(inID, getter_AddRefs(menuItem));
+ nsCOMPtr<nsIContent> menuContent(do_QueryInterface(menuItem));
+ if (menuContent) {
+ menuContent->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden, NS_LITERAL_STRING("true"), false);
+ if (outHiddenNode) {
+ *outHiddenNode = menuContent.get();
+ NS_IF_ADDREF(*outHiddenNode);
+ }
+ }
+}
+
+// Do what is necessary to conform to the Aqua guidelines for menus.
+void nsMenuBarX::AquifyMenuBar()
+{
+ nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(mContent->GetComposedDoc()));
+ if (domDoc) {
+ // remove the "About..." item and its separator
+ HideItem(domDoc, NS_LITERAL_STRING("aboutSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("aboutName"), getter_AddRefs(mAboutItemContent));
+ if (!sAboutItemContent)
+ sAboutItemContent = mAboutItemContent;
+
+ // remove quit item and its separator
+ HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_FileQuitItem"), getter_AddRefs(mQuitItemContent));
+ if (!sQuitItemContent)
+ sQuitItemContent = mQuitItemContent;
+
+ // remove prefs item and its separator, but save off the pref content node
+ // so we can invoke its command later.
+ HideItem(domDoc, NS_LITERAL_STRING("menu_PrefsSeparator"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_preferences"), getter_AddRefs(mPrefItemContent));
+ if (!sPrefItemContent)
+ sPrefItemContent = mPrefItemContent;
+
+ // hide items that we use for the Application menu
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_services"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_app"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_hide_others"), nullptr);
+ HideItem(domDoc, NS_LITERAL_STRING("menu_mac_show_all"), nullptr);
+ }
+}
+
+// for creating menu items destined for the Application menu
+NSMenuItem* nsMenuBarX::CreateNativeAppMenuItem(nsMenuX* inMenu, const nsAString& nodeID, SEL action,
+ int tag, NativeMenuItemTarget* target)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsCOMPtr<nsIDocument> doc = inMenu->Content()->GetUncomposedDoc();
+ if (!doc) {
+ return nil;
+ }
+
+ nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(doc));
+ if (!domdoc) {
+ return nil;
+ }
+
+ // Get information from the gecko menu item
+ nsAutoString label;
+ nsAutoString modifiers;
+ nsAutoString key;
+ nsCOMPtr<nsIDOMElement> menuItem;
+ domdoc->GetElementById(nodeID, getter_AddRefs(menuItem));
+ if (menuItem) {
+ menuItem->GetAttribute(NS_LITERAL_STRING("label"), label);
+ menuItem->GetAttribute(NS_LITERAL_STRING("modifiers"), modifiers);
+ menuItem->GetAttribute(NS_LITERAL_STRING("key"), key);
+ }
+ else {
+ return nil;
+ }
+
+ // Get more information about the key equivalent. Start by
+ // finding the key node we need.
+ NSString* keyEquiv = nil;
+ unsigned int macKeyModifiers = 0;
+ if (!key.IsEmpty()) {
+ nsCOMPtr<nsIDOMElement> keyElement;
+ domdoc->GetElementById(key, getter_AddRefs(keyElement));
+ if (keyElement) {
+ nsCOMPtr<nsIContent> keyContent (do_QueryInterface(keyElement));
+ // first grab the key equivalent character
+ nsAutoString keyChar(NS_LITERAL_STRING(" "));
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
+ if (!keyChar.EqualsLiteral(" ")) {
+ keyEquiv = [[NSString stringWithCharacters:reinterpret_cast<const unichar*>(keyChar.get())
+ length:keyChar.Length()] lowercaseString];
+ }
+ // now grab the key equivalent modifiers
+ nsAutoString modifiersStr;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+ uint8_t geckoModifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
+ macKeyModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(geckoModifiers);
+ }
+ }
+ // get the label into NSString-form
+ NSString* labelString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(label.get())
+ length:label.Length()];
+
+ if (!labelString)
+ labelString = @"";
+ if (!keyEquiv)
+ keyEquiv = @"";
+
+ // put together the actual NSMenuItem
+ NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:labelString action:action keyEquivalent:keyEquiv];
+
+ [newMenuItem setTag:tag];
+ [newMenuItem setTarget:target];
+ [newMenuItem setKeyEquivalentModifierMask:macKeyModifiers];
+
+ MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:this];
+ [newMenuItem setRepresentedObject:info];
+ [info release];
+
+ return newMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// build the Application menu shared by all menu bars
+nsresult nsMenuBarX::CreateApplicationMenu(nsMenuX* inMenu)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // At this point, the application menu is the application menu from
+ // the nib in cocoa widgets. We do not have a way to create an application
+ // menu manually, so we grab the one from the nib and use that.
+ sApplicationMenu = [[[[NSApp mainMenu] itemAtIndex:0] submenu] retain];
+
+/*
+ We support the following menu items here:
+
+ Menu Item DOM Node ID Notes
+
+ ========================
+ = About This App = <- aboutName
+ ========================
+ = Preferences... = <- menu_preferences
+ ========================
+ = Services > = <- menu_mac_services <- (do not define key equivalent)
+ ========================
+ = Hide App = <- menu_mac_hide_app
+ = Hide Others = <- menu_mac_hide_others
+ = Show All = <- menu_mac_show_all
+ ========================
+ = Quit = <- menu_FileQuitItem
+ ========================
+
+ If any of them are ommitted from the application's DOM, we just don't add
+ them. We always add a "Quit" item, but if an app developer does not provide a
+ DOM node with the right ID for the Quit item, we add it in English. App
+ developers need only add each node with a label and a key equivalent (if they
+ want one). Other attributes are optional. Like so:
+
+ <menuitem id="menu_preferences"
+ label="&preferencesCmdMac.label;"
+ key="open_prefs_key"/>
+
+ We need to use this system for localization purposes, until we have a better way
+ to define the Application menu to be used on Mac OS X.
+*/
+
+ if (sApplicationMenu) {
+ // This code reads attributes we are going to care about from the DOM elements
+
+ NSMenuItem *itemBeingAdded = nil;
+ BOOL addAboutSeparator = FALSE;
+
+ // Add the About menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("aboutName"), @selector(menuItemHit:),
+ eCommand_ID_About, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addAboutSeparator = TRUE;
+ }
+
+ // Add separator if either the About item or software update item exists
+ if (addAboutSeparator)
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add the Preferences menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_preferences"), @selector(menuItemHit:),
+ eCommand_ID_Prefs, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ // Add separator after Preferences menu
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+ }
+
+ // Add Services menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_services"), nil,
+ 0, nil);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+
+ // set this menu item up as the Mac OS X Services menu
+ NSMenu* servicesMenu = [[GeckoServicesNSMenu alloc] initWithTitle:@""];
+ [itemBeingAdded setSubmenu:servicesMenu];
+ [NSApp setServicesMenu:servicesMenu];
+
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ // Add separator after Services menu
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+ }
+
+ BOOL addHideShowSeparator = FALSE;
+
+ // Add menu item to hide this application
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_app"), @selector(menuItemHit:),
+ eCommand_ID_HideApp, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add menu item to hide other applications
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_hide_others"), @selector(menuItemHit:),
+ eCommand_ID_HideOthers, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add menu item to show all applications
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_mac_show_all"), @selector(menuItemHit:),
+ eCommand_ID_ShowAll, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+
+ addHideShowSeparator = TRUE;
+ }
+
+ // Add a separator after the hide/show menus if at least one exists
+ if (addHideShowSeparator)
+ [sApplicationMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add quit menu item
+ itemBeingAdded = CreateNativeAppMenuItem(inMenu, NS_LITERAL_STRING("menu_FileQuitItem"), @selector(menuItemHit:),
+ eCommand_ID_Quit, nsMenuBarX::sNativeEventTarget);
+ if (itemBeingAdded) {
+ [sApplicationMenu addItem:itemBeingAdded];
+ [itemBeingAdded release];
+ itemBeingAdded = nil;
+ }
+ else {
+ // the current application does not have a DOM node for "Quit". Add one
+ // anyway, in English.
+ NSMenuItem* defaultQuitItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(menuItemHit:)
+ keyEquivalent:@"q"] autorelease];
+ [defaultQuitItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [defaultQuitItem setTag:eCommand_ID_Quit];
+ [sApplicationMenu addItem:defaultQuitItem];
+ }
+ }
+
+ return (sApplicationMenu) ? NS_OK : NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void nsMenuBarX::SetParent(nsIWidget* aParent)
+{
+ mParentWindow = aParent;
+}
+
+//
+// Objective-C class used to allow us to have keyboard commands
+// look like they are doing something but actually do nothing.
+// We allow mouse actions to work normally.
+//
+
+// Controls whether or not native menu items should invoke their commands.
+static BOOL gMenuItemsExecuteCommands = YES;
+
+@implementation GeckoNSMenu
+
+// Keyboard commands should not cause menu items to invoke their
+// commands when there is a key window because we'd rather send
+// the keyboard command to the window. We still have the menus
+// go through the mechanics so they'll give the proper visual
+// feedback.
+- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
+{
+ // We've noticed that Mac OS X expects this check in subclasses before
+ // calling NSMenu's "performKeyEquivalent:".
+ //
+ // There is no case in which we'd need to do anything or return YES
+ // when we have no items so we can just do this check first.
+ if ([self numberOfItems] <= 0) {
+ return NO;
+ }
+
+ NSWindow *keyWindow = [NSApp keyWindow];
+
+ // If there is no key window then just behave normally. This
+ // probably means that this menu is associated with Gecko's
+ // hidden window.
+ if (!keyWindow) {
+ return [super performKeyEquivalent:theEvent];
+ }
+
+ NSResponder *firstResponder = [keyWindow firstResponder];
+
+ gMenuItemsExecuteCommands = NO;
+ [super performKeyEquivalent:theEvent];
+ gMenuItemsExecuteCommands = YES; // return to default
+
+ // Return YES if we invoked a command and there is now no key window or we changed
+ // the first responder. In this case we do not want to propagate the event because
+ // we don't want it handled again.
+ if (![NSApp keyWindow] || [[NSApp keyWindow] firstResponder] != firstResponder) {
+ return YES;
+ }
+
+ // Return NO so that we can handle the event via NSView's "keyDown:".
+ return NO;
+}
+
+@end
+
+//
+// Objective-C class used as action target for menu items
+//
+
+@implementation NativeMenuItemTarget
+
+// called when some menu item in this menu gets hit
+-(IBAction)menuItemHit:(id)sender
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gMenuItemsExecuteCommands) {
+ return;
+ }
+
+ int tag = [sender tag];
+
+ nsMenuGroupOwnerX* menuGroupOwner = nullptr;
+ nsMenuBarX* menuBar = nullptr;
+ MenuItemInfo* info = [sender representedObject];
+
+ if (info) {
+ menuGroupOwner = [info menuGroupOwner];
+ if (!menuGroupOwner) {
+ return;
+ }
+ if (menuGroupOwner->MenuObjectType() == eMenuBarObjectType) {
+ menuBar = static_cast<nsMenuBarX*>(menuGroupOwner);
+ }
+ }
+
+ // Do special processing if this is for an app-global command.
+ if (tag == eCommand_ID_About) {
+ nsIContent* mostSpecificContent = sAboutItemContent;
+ if (menuBar && menuBar->mAboutItemContent)
+ mostSpecificContent = menuBar->mAboutItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ }
+ else if (tag == eCommand_ID_Prefs) {
+ nsIContent* mostSpecificContent = sPrefItemContent;
+ if (menuBar && menuBar->mPrefItemContent)
+ mostSpecificContent = menuBar->mPrefItemContent;
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ return;
+ }
+ else if (tag == eCommand_ID_HideApp) {
+ [NSApp hide:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_HideOthers) {
+ [NSApp hideOtherApplications:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_ShowAll) {
+ [NSApp unhideAllApplications:sender];
+ return;
+ }
+ else if (tag == eCommand_ID_Quit) {
+ nsIContent* mostSpecificContent = sQuitItemContent;
+ if (menuBar && menuBar->mQuitItemContent)
+ mostSpecificContent = menuBar->mQuitItemContent;
+ // If we have some content for quit we execute it. Otherwise we send a native app terminate
+ // message. If you want to stop a quit from happening, provide quit content and return
+ // the event as unhandled.
+ if (mostSpecificContent) {
+ nsMenuUtilsX::DispatchCommandTo(mostSpecificContent);
+ }
+ else {
+ nsCOMPtr<nsIAppStartup> appStartup = do_GetService(NS_APPSTARTUP_CONTRACTID);
+ if (appStartup) {
+ appStartup->Quit(nsIAppStartup::eAttemptQuit);
+ }
+ }
+ return;
+ }
+
+ // given the commandID, look it up in our hashtable and dispatch to
+ // that menu item.
+ if (menuGroupOwner) {
+ nsMenuItemX* menuItem = menuGroupOwner->GetMenuItemForCommandID(static_cast<uint32_t>(tag));
+ if (menuItem)
+ menuItem->DoCommand();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// Objective-C class used for menu items on the Services menu to allow Gecko
+// to override their standard behavior in order to stop key equivalents from
+// firing in certain instances. When gMenuItemsExecuteCommands is NO, we return
+// a dummy target and action instead of the actual target and action.
+
+@implementation GeckoServicesNSMenuItem
+
+- (id) target
+{
+ id realTarget = [super target];
+ if (gMenuItemsExecuteCommands)
+ return realTarget;
+ else
+ return realTarget ? self : nil;
+}
+
+- (SEL) action
+{
+ SEL realAction = [super action];
+ if (gMenuItemsExecuteCommands)
+ return realAction;
+ else
+ return realAction ? @selector(_doNothing:) : NULL;
+}
+
+- (void) _doNothing:(id)sender
+{
+}
+
+@end
+
+// Objective-C class used as the Services menu so that Gecko can override the
+// standard behavior of the Services menu in order to stop key equivalents
+// from firing in certain instances.
+
+@implementation GeckoServicesNSMenu
+
+- (void)addItem:(NSMenuItem *)newItem
+{
+ [self _overrideClassOfMenuItem:newItem];
+ [super addItem:newItem];
+}
+
+- (NSMenuItem *)addItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv
+{
+ NSMenuItem * newItem = [super addItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void)insertItem:(NSMenuItem *)newItem atIndex:(NSInteger)index
+{
+ [self _overrideClassOfMenuItem:newItem];
+ [super insertItem:newItem atIndex:index];
+}
+
+- (NSMenuItem *)insertItemWithTitle:(NSString *)aString action:(SEL)aSelector keyEquivalent:(NSString *)keyEquiv atIndex:(NSInteger)index
+{
+ NSMenuItem * newItem = [super insertItemWithTitle:aString action:aSelector keyEquivalent:keyEquiv atIndex:index];
+ [self _overrideClassOfMenuItem:newItem];
+ return newItem;
+}
+
+- (void) _overrideClassOfMenuItem:(NSMenuItem *)menuItem
+{
+ if ([menuItem class] == [NSMenuItem class])
+ object_setClass(menuItem, [GeckoServicesNSMenuItem class]);
+}
+
+@end
diff --git a/widget/cocoa/nsMenuBaseX.h b/widget/cocoa/nsMenuBaseX.h
new file mode 100644
index 0000000000..5b9f89c560
--- /dev/null
+++ b/widget/cocoa/nsMenuBaseX.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsMenuBaseX_h_
+#define nsMenuBaseX_h_
+
+#import <Foundation/Foundation.h>
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+
+enum nsMenuObjectTypeX {
+ eMenuBarObjectType,
+ eSubmenuObjectType,
+ eMenuItemObjectType,
+ eStandaloneNativeMenuObjectType,
+};
+
+// All menu objects subclass this.
+// Menu bars are owned by their top-level nsIWidgets.
+// All other objects are memory-managed based on the DOM.
+// Content removal deletes them immediately and nothing else should.
+// Do not attempt to hold strong references to them or delete them.
+class nsMenuObjectX
+{
+public:
+ virtual ~nsMenuObjectX() { }
+ virtual nsMenuObjectTypeX MenuObjectType()=0;
+ virtual void* NativeData()=0;
+ nsIContent* Content() { return mContent; }
+
+ /**
+ * Called when an icon of a menu item somewhere in this menu has updated.
+ * Menu objects with parents need to propagate the notification to their
+ * parent.
+ */
+ virtual void IconUpdated() {}
+
+protected:
+ nsCOMPtr<nsIContent> mContent;
+};
+
+
+//
+// Object stored as "representedObject" for all menu items
+//
+
+class nsMenuGroupOwnerX;
+
+@interface MenuItemInfo : NSObject
+{
+ nsMenuGroupOwnerX * mMenuGroupOwner;
+}
+
+- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner;
+- (nsMenuGroupOwnerX *) menuGroupOwner;
+- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner;
+
+@end
+
+
+// Special command IDs that we know Mac OS X does not use for anything else.
+// We use these in place of carbon's IDs for these commands in order to stop
+// Carbon from messing with our event handlers. See bug 346883.
+
+enum {
+ eCommand_ID_About = 1,
+ eCommand_ID_Prefs = 2,
+ eCommand_ID_Quit = 3,
+ eCommand_ID_HideApp = 4,
+ eCommand_ID_HideOthers = 5,
+ eCommand_ID_ShowAll = 6,
+ eCommand_ID_Update = 7,
+ eCommand_ID_Last = 8
+};
+
+#endif // nsMenuBaseX_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.h b/widget/cocoa/nsMenuGroupOwnerX.h
new file mode 100644
index 0000000000..657f420b56
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsMenuGroupOwnerX_h_
+#define nsMenuGroupOwnerX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsMenuBaseX.h"
+#include "nsIMutationObserver.h"
+#include "nsHashKeys.h"
+#include "nsDataHashtable.h"
+#include "nsString.h"
+
+class nsMenuItemX;
+class nsChangeObserver;
+class nsIWidget;
+class nsIContent;
+
+class nsMenuGroupOwnerX : public nsMenuObjectX, public nsIMutationObserver
+{
+public:
+ nsMenuGroupOwnerX();
+
+ nsresult Create(nsIContent * aContent);
+
+ void RegisterForContentChanges(nsIContent* aContent,
+ nsChangeObserver* aMenuObject);
+ void UnregisterForContentChanges(nsIContent* aContent);
+ uint32_t RegisterForCommand(nsMenuItemX* aItem);
+ void UnregisterCommand(uint32_t aCommandID);
+ nsMenuItemX* GetMenuItemForCommandID(uint32_t inCommandID);
+ void AddMenuItemInfoToSet(MenuItemInfo* info);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMUTATIONOBSERVER
+
+protected:
+ virtual ~nsMenuGroupOwnerX();
+
+ nsChangeObserver* LookupContentChangeObserver(nsIContent* aContent);
+
+ uint32_t mCurrentCommandID; // unique command id (per menu-bar) to
+ // give to next item that asks
+
+ // stores observers for content change notification
+ nsDataHashtable<nsPtrHashKey<nsIContent>, nsChangeObserver *> mContentToObserverTable;
+
+ // stores mapping of command IDs to menu objects
+ nsDataHashtable<nsUint32HashKey, nsMenuItemX *> mCommandToMenuObjectTable;
+
+ // Stores references to all the MenuItemInfo objects created with weak
+ // references to us. They may live longer than we do, so when we're
+ // destroyed we need to clear all their weak references. This avoids
+ // crashes in -[NativeMenuItemTarget menuItemHit:]. See bug 1131473.
+ NSMutableSet* mInfoSet;
+};
+
+#endif // nsMenuGroupOwner_h_
diff --git a/widget/cocoa/nsMenuGroupOwnerX.mm b/widget/cocoa/nsMenuGroupOwnerX.mm
new file mode 100644
index 0000000000..661a52bd80
--- /dev/null
+++ b/widget/cocoa/nsMenuGroupOwnerX.mm
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsMenuGroupOwnerX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsObjCExceptions.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+
+#include "nsINode.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsMenuGroupOwnerX, nsIMutationObserver)
+
+
+nsMenuGroupOwnerX::nsMenuGroupOwnerX()
+: mCurrentCommandID(eCommand_ID_Last)
+{
+ mInfoSet = [[NSMutableSet setWithCapacity:10] retain];
+}
+
+
+nsMenuGroupOwnerX::~nsMenuGroupOwnerX()
+{
+ MOZ_ASSERT(mContentToObserverTable.Count() == 0, "have outstanding mutation observers!\n");
+
+ // The MenuItemInfo objects in mInfoSet may live longer than we do. So when
+ // we get destroyed we need to invalidate all their mMenuGroupOwner pointers.
+ NSEnumerator* counter = [mInfoSet objectEnumerator];
+ MenuItemInfo* info;
+ while ((info = (MenuItemInfo*) [counter nextObject])) {
+ [info setMenuGroupOwner:nil];
+ }
+ [mInfoSet release];
+}
+
+
+nsresult nsMenuGroupOwnerX::Create(nsIContent* aContent)
+{
+ if (!aContent)
+ return NS_ERROR_INVALID_ARG;
+
+ mContent = aContent;
+
+ return NS_OK;
+}
+
+
+//
+// nsIMutationObserver
+//
+
+
+void nsMenuGroupOwnerX::CharacterDataWillChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+
+void nsMenuGroupOwnerX::CharacterDataChanged(nsIDocument* aDocument,
+ nsIContent* aContent,
+ CharacterDataChangeInfo* aInfo)
+{
+}
+
+
+void nsMenuGroupOwnerX::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t /* unused */)
+{
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ ContentInserted(aDocument, aContainer, cur, 0);
+ }
+}
+
+
+void nsMenuGroupOwnerX::NodeWillBeDestroyed(const nsINode * aNode)
+{
+}
+
+
+void nsMenuGroupOwnerX::AttributeWillChange(nsIDocument* aDocument,
+ dom::Element* aContent,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aNewValue)
+{
+}
+
+void nsMenuGroupOwnerX::NativeAnonymousChildListChange(nsIDocument* aDocument,
+ nsIContent* aContent,
+ bool aIsRemove)
+{
+}
+
+void nsMenuGroupOwnerX::AttributeChanged(nsIDocument* aDocument,
+ dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aElement);
+ if (obs)
+ obs->ObserveAttributeChanged(aDocument, aElement, aAttribute);
+}
+
+
+void nsMenuGroupOwnerX::ContentRemoved(nsIDocument * aDocument,
+ nsIContent * aContainer,
+ nsIContent * aChild,
+ int32_t aIndexInContainer,
+ nsIContent * aPreviousSibling)
+{
+ if (!aContainer) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
+ if (obs)
+ obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
+ else if (aContainer != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs)
+ obs->ObserveContentRemoved(aDocument, aChild, aIndexInContainer);
+ }
+ }
+}
+
+
+void nsMenuGroupOwnerX::ContentInserted(nsIDocument * aDocument,
+ nsIContent * aContainer,
+ nsIContent * aChild,
+ int32_t /* unused */)
+{
+ if (!aContainer) {
+ return;
+ }
+
+ nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
+ nsChangeObserver* obs = LookupContentChangeObserver(aContainer);
+ if (obs)
+ obs->ObserveContentInserted(aDocument, aContainer, aChild);
+ else if (aContainer != mContent) {
+ // We do a lookup on the parent container in case things were removed
+ // under a "menupopup" item. That is basically a wrapper for the contents
+ // of a "menu" node.
+ nsCOMPtr<nsIContent> parent = aContainer->GetParent();
+ if (parent) {
+ obs = LookupContentChangeObserver(parent);
+ if (obs)
+ obs->ObserveContentInserted(aDocument, aContainer, aChild);
+ }
+ }
+}
+
+
+void nsMenuGroupOwnerX::ParentChainChanged(nsIContent *aContent)
+{
+}
+
+
+// For change management, we don't use a |nsSupportsHashtable| because
+// we know that the lifetime of all these items is bounded by the
+// lifetime of the menubar. No need to add any more strong refs to the
+// picture because the containment hierarchy already uses strong refs.
+void nsMenuGroupOwnerX::RegisterForContentChanges(nsIContent *aContent,
+ nsChangeObserver *aMenuObject)
+{
+ if (!mContentToObserverTable.Contains(aContent)) {
+ aContent->AddMutationObserver(this);
+ }
+ mContentToObserverTable.Put(aContent, aMenuObject);
+}
+
+
+void nsMenuGroupOwnerX::UnregisterForContentChanges(nsIContent *aContent)
+{
+ if (mContentToObserverTable.Contains(aContent)) {
+ aContent->RemoveMutationObserver(this);
+ }
+ mContentToObserverTable.Remove(aContent);
+}
+
+
+nsChangeObserver* nsMenuGroupOwnerX::LookupContentChangeObserver(nsIContent* aContent)
+{
+ nsChangeObserver * result;
+ if (mContentToObserverTable.Get(aContent, &result))
+ return result;
+ else
+ return nullptr;
+}
+
+
+// Given a menu item, creates a unique 4-character command ID and
+// maps it to the item. Returns the id for use by the client.
+uint32_t nsMenuGroupOwnerX::RegisterForCommand(nsMenuItemX* inMenuItem)
+{
+ // no real need to check for uniqueness. We always start afresh with each
+ // window at 1. Even if we did get close to the reserved Apple command id's,
+ // those don't start until at least ' ', which is integer 538976288. If
+ // we have that many menu items in one window, I think we have other
+ // problems.
+
+ // make id unique
+ ++mCurrentCommandID;
+
+ mCommandToMenuObjectTable.Put(mCurrentCommandID, inMenuItem);
+
+ return mCurrentCommandID;
+}
+
+
+// Removes the mapping between the given 4-character command ID
+// and its associated menu item.
+void nsMenuGroupOwnerX::UnregisterCommand(uint32_t inCommandID)
+{
+ mCommandToMenuObjectTable.Remove(inCommandID);
+}
+
+
+nsMenuItemX* nsMenuGroupOwnerX::GetMenuItemForCommandID(uint32_t inCommandID)
+{
+ nsMenuItemX * result;
+ if (mCommandToMenuObjectTable.Get(inCommandID, &result))
+ return result;
+ else
+ return nullptr;
+}
+
+void nsMenuGroupOwnerX::AddMenuItemInfoToSet(MenuItemInfo* info)
+{
+ [mInfoSet addObject:info];
+}
diff --git a/widget/cocoa/nsMenuItemIconX.h b/widget/cocoa/nsMenuItemIconX.h
new file mode 100644
index 0000000000..7352a94e2a
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+#ifndef nsMenuItemIconX_h_
+#define nsMenuItemIconX_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "imgINotificationObserver.h"
+
+class nsIURI;
+class nsIContent;
+class imgRequestProxy;
+class nsMenuObjectX;
+
+#import <Cocoa/Cocoa.h>
+
+class nsMenuItemIconX : public imgINotificationObserver
+{
+public:
+ nsMenuItemIconX(nsMenuObjectX* aMenuItem,
+ nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem);
+private:
+ virtual ~nsMenuItemIconX();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_IMGINOTIFICATIONOBSERVER
+
+ // SetupIcon succeeds if it was able to set up the icon, or if there should
+ // be no icon, in which case it clears any existing icon but still succeeds.
+ nsresult SetupIcon();
+
+ // GetIconURI fails if the item should not have any icon.
+ nsresult GetIconURI(nsIURI** aIconURI);
+
+ // LoadIcon will set a placeholder image and start a load request for the
+ // icon. The request may not complete until after LoadIcon returns.
+ nsresult LoadIcon(nsIURI* aIconURI);
+
+ // Unless we take precautions, we may outlive the object that created us
+ // (mMenuObject, which owns our native menu item (mNativeMenuItem)).
+ // Destroy() should be called from mMenuObject's destructor to prevent
+ // this from happening. See bug 499600.
+ void Destroy();
+
+protected:
+ nsresult OnFrameComplete(imgIRequest* aRequest);
+
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<imgRequestProxy> mIconRequest;
+ nsMenuObjectX* mMenuObject; // [weak]
+ nsIntRect mImageRegionRect;
+ bool mLoadedIcon;
+ bool mSetIcon;
+ NSMenuItem* mNativeMenuItem; // [weak]
+};
+
+#endif // nsMenuItemIconX_h_
diff --git a/widget/cocoa/nsMenuItemIconX.mm b/widget/cocoa/nsMenuItemIconX.mm
new file mode 100644
index 0000000000..7589c279e5
--- /dev/null
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -0,0 +1,466 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+/*
+ * Retrieves and displays icons in native menu items on Mac OS X.
+ */
+
+/* exception_defines.h defines 'try' to 'if (true)' which breaks objective-c
+ exceptions and produces errors like: error: unexpected '@' in program'.
+ If we define __EXCEPTIONS exception_defines.h will avoid doing this.
+
+ See bug 666609 for more information.
+
+ We use <limits> to get the libstdc++ version. */
+#include <limits>
+#if __GLIBCXX__ <= 20070719
+#ifndef __EXCEPTIONS
+#define __EXCEPTIONS
+#endif
+#endif
+
+#include "nsMenuItemIconX.h"
+#include "nsObjCExceptions.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsNameSpaceManager.h"
+#include "nsGkAtoms.h"
+#include "nsIDOMElement.h"
+#include "nsICSSDeclaration.h"
+#include "nsIDOMCSSValue.h"
+#include "nsIDOMCSSPrimitiveValue.h"
+#include "nsIDOMRect.h"
+#include "nsThreadUtils.h"
+#include "nsToolkit.h"
+#include "nsNetUtil.h"
+#include "imgLoader.h"
+#include "imgRequestProxy.h"
+#include "nsMenuItemX.h"
+#include "gfxPlatform.h"
+#include "imgIContainer.h"
+#include "nsCocoaUtils.h"
+#include "nsContentUtils.h"
+#include "nsIContentPolicy.h"
+
+using mozilla::dom::Element;
+using mozilla::gfx::SourceSurface;
+
+static const uint32_t kIconWidth = 16;
+static const uint32_t kIconHeight = 16;
+
+typedef NS_STDCALL_FUNCPROTO(nsresult, GetRectSideMethod, nsIDOMRect,
+ GetBottom, (nsIDOMCSSPrimitiveValue**));
+
+NS_IMPL_ISUPPORTS(nsMenuItemIconX, imgINotificationObserver)
+
+nsMenuItemIconX::nsMenuItemIconX(nsMenuObjectX* aMenuItem,
+ nsIContent* aContent,
+ NSMenuItem* aNativeMenuItem)
+: mContent(aContent)
+, mMenuObject(aMenuItem)
+, mLoadedIcon(false)
+, mSetIcon(false)
+, mNativeMenuItem(aNativeMenuItem)
+{
+ // printf("Creating icon for menu item %d, menu %d, native item is %d\n", aMenuItem, aMenu, aNativeMenuItem);
+}
+
+nsMenuItemIconX::~nsMenuItemIconX()
+{
+ if (mIconRequest)
+ mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+}
+
+// Called from mMenuObjectX's destructor, to prevent us from outliving it
+// (as might otherwise happen if calls to our imgINotificationObserver methods
+// are still outstanding). mMenuObjectX owns our nNativeMenuItem.
+void nsMenuItemIconX::Destroy()
+{
+ if (mIconRequest) {
+ mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ mMenuObject = nullptr;
+ mNativeMenuItem = nil;
+}
+
+nsresult
+nsMenuItemIconX::SetupIcon()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Still don't have one, then something is wrong, get out of here.
+ if (!mNativeMenuItem) {
+ NS_ERROR("No native menu item");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> iconURI;
+ nsresult rv = GetIconURI(getter_AddRefs(iconURI));
+ if (NS_FAILED(rv)) {
+ // There is no icon for this menu item. An icon might have been set
+ // earlier. Clear it.
+ [mNativeMenuItem setImage:nil];
+
+ return NS_OK;
+ }
+
+ rv = LoadIcon(iconURI);
+ if (NS_FAILED(rv)) {
+ // There is no icon for this menu item, as an error occurred while loading it.
+ // An icon might have been set earlier or the place holder icon may have
+ // been set. Clear it.
+ [mNativeMenuItem setImage:nil];
+ }
+ return rv;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+static int32_t
+GetDOMRectSide(nsIDOMRect* aRect, GetRectSideMethod aMethod)
+{
+ nsCOMPtr<nsIDOMCSSPrimitiveValue> dimensionValue;
+ (aRect->*aMethod)(getter_AddRefs(dimensionValue));
+ if (!dimensionValue)
+ return -1;
+
+ uint16_t primitiveType;
+ nsresult rv = dimensionValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv) || primitiveType != nsIDOMCSSPrimitiveValue::CSS_PX)
+ return -1;
+
+ float dimension = 0;
+ rv = dimensionValue->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX,
+ &dimension);
+ if (NS_FAILED(rv))
+ return -1;
+
+ return NSToIntRound(dimension);
+}
+
+nsresult
+nsMenuItemIconX::GetIconURI(nsIURI** aIconURI)
+{
+ if (!mMenuObject)
+ return NS_ERROR_FAILURE;
+
+ // Mac native menu items support having both a checkmark and an icon
+ // simultaneously, but this is unheard of in the cross-platform toolkit,
+ // seemingly because the win32 theme is unable to cope with both at once.
+ // The downside is that it's possible to get a menu item marked with a
+ // native checkmark and a checkmark for an icon. Head off that possibility
+ // by pretending that no icon exists if this is a checkable menu item.
+ if (mMenuObject->MenuObjectType() == eMenuItemObjectType) {
+ nsMenuItemX* menuItem = static_cast<nsMenuItemX*>(mMenuObject);
+ if (menuItem->GetMenuItemType() != eRegularMenuItemType)
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // First, look at the content node's "image" attribute.
+ nsAutoString imageURIString;
+ bool hasImageAttr = mContent->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::image,
+ imageURIString);
+
+ nsresult rv;
+ nsCOMPtr<nsIDOMCSSValue> cssValue;
+ nsCOMPtr<nsICSSDeclaration> cssStyleDecl;
+ nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue;
+ uint16_t primitiveType;
+ if (!hasImageAttr) {
+ // If the content node has no "image" attribute, get the
+ // "list-style-image" property from CSS.
+ nsCOMPtr<nsIDocument> document = mContent->GetComposedDoc();
+ if (!document)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = document->GetInnerWindow();
+ if (!window)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<Element> domElement = do_QueryInterface(mContent);
+ if (!domElement)
+ return NS_ERROR_FAILURE;
+
+ ErrorResult dummy;
+ cssStyleDecl = window->GetComputedStyle(*domElement, EmptyString(), dummy);
+ dummy.SuppressException();
+ if (!cssStyleDecl)
+ return NS_ERROR_FAILURE;
+
+ NS_NAMED_LITERAL_STRING(listStyleImage, "list-style-image");
+ rv = cssStyleDecl->GetPropertyCSSValue(listStyleImage,
+ getter_AddRefs(cssValue));
+ if (NS_FAILED(rv)) return rv;
+
+ primitiveValue = do_QueryInterface(cssValue);
+ if (!primitiveValue) return NS_ERROR_FAILURE;
+
+ rv = primitiveValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv)) return rv;
+ if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_URI)
+ return NS_ERROR_FAILURE;
+
+ rv = primitiveValue->GetStringValue(imageURIString);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Empty the mImageRegionRect initially as the image region CSS could
+ // have been changed and now have an error or have been removed since the
+ // last GetIconURI call.
+ mImageRegionRect.SetEmpty();
+
+ // If this menu item shouldn't have an icon, the string will be empty,
+ // and NS_NewURI will fail.
+ nsCOMPtr<nsIURI> iconURI;
+ rv = NS_NewURI(getter_AddRefs(iconURI), imageURIString);
+ if (NS_FAILED(rv)) return rv;
+
+ *aIconURI = iconURI;
+ NS_ADDREF(*aIconURI);
+
+ if (!hasImageAttr) {
+ // Check if the icon has a specified image region so that it can be
+ // cropped appropriately before being displayed.
+ NS_NAMED_LITERAL_STRING(imageRegion, "-moz-image-region");
+ rv = cssStyleDecl->GetPropertyCSSValue(imageRegion,
+ getter_AddRefs(cssValue));
+ // Just return NS_OK if there if there is a failure due to no
+ // moz-image region specified so the whole icon will be drawn anyway.
+ if (NS_FAILED(rv)) return NS_OK;
+
+ primitiveValue = do_QueryInterface(cssValue);
+ if (!primitiveValue) return NS_OK;
+
+ rv = primitiveValue->GetPrimitiveType(&primitiveType);
+ if (NS_FAILED(rv)) return NS_OK;
+ if (primitiveType != nsIDOMCSSPrimitiveValue::CSS_RECT)
+ return NS_OK;
+
+ nsCOMPtr<nsIDOMRect> imageRegionRect;
+ rv = primitiveValue->GetRectValue(getter_AddRefs(imageRegionRect));
+ if (NS_FAILED(rv)) return NS_OK;
+
+ if (imageRegionRect) {
+ // Return NS_ERROR_FAILURE if the image region is invalid so the image
+ // is not drawn, and behavior is similar to XUL menus.
+ int32_t bottom = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetBottom);
+ int32_t right = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetRight);
+ int32_t top = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetTop);
+ int32_t left = GetDOMRectSide(imageRegionRect, &nsIDOMRect::GetLeft);
+
+ if (top < 0 || left < 0 || bottom <= top || right <= left)
+ return NS_ERROR_FAILURE;
+
+ mImageRegionRect.SetRect(left, top, right - left, bottom - top);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuItemIconX::LoadIcon(nsIURI* aIconURI)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mIconRequest) {
+ // Another icon request is already in flight. Kill it.
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+
+ mLoadedIcon = false;
+
+ if (!mContent) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIDocument> document = mContent->OwnerDoc();
+
+ nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup();
+ if (!loadGroup) return NS_ERROR_FAILURE;
+
+ RefPtr<imgLoader> loader = nsContentUtils::GetImgLoaderForDocument(document);
+ if (!loader) return NS_ERROR_FAILURE;
+
+ if (!mSetIcon) {
+ // Set a completely transparent 16x16 image as the icon on this menu item
+ // as a placeholder. This keeps the menu item text displayed in the same
+ // position that it will be displayed when the real icon is loaded, and
+ // prevents it from jumping around or looking misaligned.
+
+ static bool sInitializedPlaceholder;
+ static NSImage* sPlaceholderIconImage;
+ if (!sInitializedPlaceholder) {
+ sInitializedPlaceholder = true;
+
+ // Note that we only create the one and reuse it forever, so this is not a leak.
+ sPlaceholderIconImage = [[NSImage alloc] initWithSize:NSMakeSize(kIconWidth, kIconHeight)];
+ }
+
+ if (!sPlaceholderIconImage) return NS_ERROR_FAILURE;
+
+ if (mNativeMenuItem)
+ [mNativeMenuItem setImage:sPlaceholderIconImage];
+ }
+
+ nsresult rv = loader->LoadImage(aIconURI, nullptr, nullptr,
+ mozilla::net::RP_Default,
+ nullptr, loadGroup, this,
+ nullptr, nullptr, nsIRequest::LOAD_NORMAL, nullptr,
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE, EmptyString(),
+ getter_AddRefs(mIconRequest));
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+//
+// imgINotificationObserver
+//
+
+NS_IMETHODIMP
+nsMenuItemIconX::Notify(imgIRequest* aRequest,
+ int32_t aType,
+ const nsIntRect* aData)
+{
+ if (aType == imgINotificationObserver::LOAD_COMPLETE) {
+ // Make sure the image loaded successfully.
+ uint32_t status = imgIRequest::STATUS_ERROR;
+ if (NS_FAILED(aRequest->GetImageStatus(&status)) ||
+ (status & imgIRequest::STATUS_ERROR)) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<imgIContainer> image;
+ aRequest->GetImage(getter_AddRefs(image));
+ MOZ_ASSERT(image);
+
+ // Ask the image to decode at its intrinsic size.
+ int32_t width = 0, height = 0;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ image->RequestDecodeForSize(nsIntSize(width, height), imgIContainer::FLAG_NONE);
+ }
+
+ if (aType == imgINotificationObserver::FRAME_COMPLETE) {
+ return OnFrameComplete(aRequest);
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ if (mIconRequest && mIconRequest == aRequest) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuItemIconX::OnFrameComplete(imgIRequest* aRequest)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (aRequest != mIconRequest)
+ return NS_ERROR_FAILURE;
+
+ // Only support one frame.
+ if (mLoadedIcon)
+ return NS_OK;
+
+ if (!mNativeMenuItem)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<imgIContainer> imageContainer;
+ aRequest->GetImage(getter_AddRefs(imageContainer));
+ if (!imageContainer) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t origWidth = 0, origHeight = 0;
+ imageContainer->GetWidth(&origWidth);
+ imageContainer->GetHeight(&origHeight);
+
+ // If the image region is invalid, don't draw the image to almost match
+ // the behavior of other platforms.
+ if (!mImageRegionRect.IsEmpty() &&
+ (mImageRegionRect.XMost() > origWidth ||
+ mImageRegionRect.YMost() > origHeight)) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mImageRegionRect.IsEmpty()) {
+ mImageRegionRect.SetRect(0, 0, origWidth, origHeight);
+ }
+
+ RefPtr<SourceSurface> surface =
+ imageContainer->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ CGImageRef origImage = NULL;
+ nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &origImage);
+ if (NS_FAILED(rv) || !origImage) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+
+ bool createSubImage = !(mImageRegionRect.x == 0 && mImageRegionRect.y == 0 &&
+ mImageRegionRect.width == origWidth && mImageRegionRect.height == origHeight);
+
+ CGImageRef finalImage = origImage;
+ if (createSubImage) {
+ // if mImageRegionRect is set using CSS, we need to slice a piece out of the overall
+ // image to use as the icon
+ finalImage = ::CGImageCreateWithImageInRect(origImage,
+ ::CGRectMake(mImageRegionRect.x,
+ mImageRegionRect.y,
+ mImageRegionRect.width,
+ mImageRegionRect.height));
+ ::CGImageRelease(origImage);
+ if (!finalImage) {
+ [mNativeMenuItem setImage:nil];
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ NSImage *newImage = nil;
+ rv = nsCocoaUtils::CreateNSImageFromCGImage(finalImage, &newImage);
+ if (NS_FAILED(rv) || !newImage) {
+ [mNativeMenuItem setImage:nil];
+ ::CGImageRelease(finalImage);
+ return NS_ERROR_FAILURE;
+ }
+
+ [newImage setSize:NSMakeSize(kIconWidth, kIconHeight)];
+ [mNativeMenuItem setImage:newImage];
+
+ [newImage release];
+ ::CGImageRelease(finalImage);
+
+ mLoadedIcon = true;
+ mSetIcon = true;
+
+ if (mMenuObject) {
+ mMenuObject->IconUpdated();
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsMenuItemX.h b/widget/cocoa/nsMenuItemX.h
new file mode 100644
index 0000000000..67ae32c99c
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsMenuItemX_h_
+#define nsMenuItemX_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsChangeObserver.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsString;
+class nsMenuItemIconX;
+class nsMenuX;
+
+enum {
+ knsMenuItemNoModifier = 0,
+ knsMenuItemShiftModifier = (1 << 0),
+ knsMenuItemAltModifier = (1 << 1),
+ knsMenuItemControlModifier = (1 << 2),
+ knsMenuItemCommandModifier = (1 << 3)
+};
+
+enum EMenuItemType {
+ eRegularMenuItemType = 0,
+ eCheckboxMenuItemType,
+ eRadioMenuItemType,
+ eSeparatorMenuItemType
+};
+
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuItemX : public nsMenuObjectX,
+ public nsChangeObserver
+{
+public:
+ nsMenuItemX();
+ virtual ~nsMenuItemX();
+
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenuItem;}
+ nsMenuObjectTypeX MenuObjectType() override {return eMenuItemObjectType;}
+
+ // nsMenuItemX
+ nsresult Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
+ nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
+ nsresult SetChecked(bool aIsChecked);
+ EMenuItemType GetMenuItemType();
+ void DoCommand();
+ nsresult DispatchDOMEvent(const nsString &eventName, bool* preventDefaultCalled);
+ void SetupIcon();
+
+protected:
+ void UncheckRadioSiblings(nsIContent* inCheckedElement);
+ void SetKeyEquiv();
+
+ EMenuItemType mType;
+ // nsMenuItemX objects should always have a valid native menu item.
+ NSMenuItem* mNativeMenuItem; // [strong]
+ nsMenuX* mMenuParent; // [weak]
+ nsMenuGroupOwnerX* mMenuGroupOwner; // [weak]
+ nsCOMPtr<nsIContent> mCommandContent;
+ // The icon object should never outlive its creating nsMenuItemX object.
+ RefPtr<nsMenuItemIconX> mIcon;
+ bool mIsChecked;
+};
+
+#endif // nsMenuItemX_h_
diff --git a/widget/cocoa/nsMenuItemX.mm b/widget/cocoa/nsMenuItemX.mm
new file mode 100644
index 0000000000..114b69f430
--- /dev/null
+++ b/widget/cocoa/nsMenuItemX.mm
@@ -0,0 +1,369 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsMenuItemX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemIconX.h"
+#include "nsMenuUtilsX.h"
+#include "nsCocoaUtils.h"
+
+#include "nsObjCExceptions.h"
+
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+
+#include "mozilla/dom/Element.h"
+#include "nsIWidget.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMEvent.h"
+
+nsMenuItemX::nsMenuItemX()
+{
+ mType = eRegularMenuItemType;
+ mNativeMenuItem = nil;
+ mMenuParent = nullptr;
+ mMenuGroupOwner = nullptr;
+ mIsChecked = false;
+
+ MOZ_COUNT_CTOR(nsMenuItemX);
+}
+
+nsMenuItemX::~nsMenuItemX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent the icon object from outliving us.
+ if (mIcon)
+ mIcon->Destroy();
+
+ // autorelease the native menu item so that anything else happening to this
+ // object happens before the native menu item actually dies
+ [mNativeMenuItem autorelease];
+
+ if (mContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mContent);
+ if (mCommandContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
+
+ MOZ_COUNT_DTOR(nsMenuItemX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuItemX::Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
+ nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mType = aItemType;
+ mMenuParent = aParent;
+ mContent = aNode;
+
+ mMenuGroupOwner = aMenuGroupOwner;
+ NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one!");
+
+ mMenuGroupOwner->RegisterForContentChanges(mContent, this);
+
+ nsIDocument *doc = mContent->GetUncomposedDoc();
+
+ // if we have a command associated with this menu item, register for changes
+ // to the command DOM node
+ if (doc) {
+ nsAutoString ourCommand;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, ourCommand);
+
+ if (!ourCommand.IsEmpty()) {
+ nsIContent *commandElement = doc->GetElementById(ourCommand);
+
+ if (commandElement) {
+ mCommandContent = commandElement;
+ // register to observe the command DOM element
+ mMenuGroupOwner->RegisterForContentChanges(mCommandContent, this);
+ }
+ }
+ }
+
+ // decide enabled state based on command content if it exists, otherwise do it based
+ // on our own content
+ bool isEnabled;
+ if (mCommandContent)
+ isEnabled = !mCommandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
+ else
+ isEnabled = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters);
+
+ // set up the native menu item
+ if (mType == eSeparatorMenuItemType) {
+ mNativeMenuItem = [[NSMenuItem separatorItem] retain];
+ }
+ else {
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(aLabel);
+ mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
+
+ [mNativeMenuItem setEnabled:(BOOL)isEnabled];
+
+ SetChecked(mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters));
+ SetKeyEquiv();
+ }
+
+ mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
+ if (!mIcon)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuItemX::SetChecked(bool aIsChecked)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mIsChecked = aIsChecked;
+
+ // update the content model. This will also handle unchecking our siblings
+ // if we are a radiomenu
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
+ mIsChecked ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), true);
+
+ // update native menu item
+ if (mIsChecked)
+ [mNativeMenuItem setState:NSOnState];
+ else
+ [mNativeMenuItem setState:NSOffState];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+EMenuItemType nsMenuItemX::GetMenuItemType()
+{
+ return mType;
+}
+
+// Executes the "cached" javaScript command.
+// Returns NS_OK if the command was executed properly, otherwise an error code.
+void nsMenuItemX::DoCommand()
+{
+ // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
+ if (mType == eCheckboxMenuItemType ||
+ (mType == eRadioMenuItemType && !mIsChecked)) {
+ if (!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
+ nsGkAtoms::_false, eCaseMatters))
+ SetChecked(!mIsChecked);
+ /* the AttributeChanged code will update all the internal state */
+ }
+
+ nsMenuUtilsX::DispatchCommandTo(mContent);
+}
+
+nsresult nsMenuItemX::DispatchDOMEvent(const nsString &eventName, bool *preventDefaultCalled)
+{
+ if (!mContent)
+ return NS_ERROR_FAILURE;
+
+ // get owner document for content
+ nsCOMPtr<nsIDocument> parentDoc = mContent->OwnerDoc();
+
+ // get interface for creating DOM events from content owner document
+ nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(parentDoc);
+ if (!domDoc) {
+ NS_WARNING("Failed to QI parent nsIDocument to nsIDOMDocument");
+ return NS_ERROR_FAILURE;
+ }
+
+ // create DOM event
+ nsCOMPtr<nsIDOMEvent> event;
+ nsresult rv = domDoc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to create nsIDOMEvent");
+ return rv;
+ }
+ event->InitEvent(eventName, true, true);
+
+ // mark DOM event as trusted
+ event->SetTrusted(true);
+
+ // send DOM event
+ rv = mContent->DispatchEvent(event, preventDefaultCalled);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to send DOM event via EventTarget");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// Walk the sibling list looking for nodes with the same name and
+// uncheck them all.
+void nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent)
+{
+ nsAutoString myGroupName;
+ inCheckedContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, myGroupName);
+ if (!myGroupName.Length()) // no groupname, nothing to do
+ return;
+
+ nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
+ if (!parent)
+ return;
+
+ // loop over siblings
+ uint32_t count = parent->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *sibling = parent->GetChildAt(i);
+ if (sibling) {
+ if (sibling != inCheckedContent) { // skip this node
+ // if the current sibling is in the same group, clear it
+ if (sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+ myGroupName, eCaseMatters))
+ sibling->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, NS_LITERAL_STRING("false"), true);
+ }
+ }
+ }
+}
+
+void nsMenuItemX::SetKeyEquiv()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Set key shortcut and modifiers
+ nsAutoString keyValue;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyValue);
+ if (!keyValue.IsEmpty() && mContent->GetUncomposedDoc()) {
+ nsIContent *keyContent = mContent->GetUncomposedDoc()->GetElementById(keyValue);
+ if (keyContent) {
+ nsAutoString keyChar;
+ bool hasKey = keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyChar);
+
+ if (!hasKey || keyChar.IsEmpty()) {
+ nsAutoString keyCodeName;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeName);
+ uint32_t charCode =
+ nsCocoaUtils::ConvertGeckoNameToMacCharCode(keyCodeName);
+ if (charCode) {
+ keyChar.Assign(charCode);
+ }
+ else {
+ keyChar.Assign(NS_LITERAL_STRING(" "));
+ }
+ }
+
+ nsAutoString modifiersStr;
+ keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
+ uint8_t modifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
+
+ unsigned int macModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(modifiers);
+ [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers];
+
+ NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)keyChar.get()
+ length:keyChar.Length()] lowercaseString];
+ if ([keyEquivalent isEqualToString:@" "])
+ [mNativeMenuItem setKeyEquivalent:@""];
+ else
+ [mNativeMenuItem setKeyEquivalent:keyEquivalent];
+
+ return;
+ }
+ }
+
+ // if the key was removed, clear the key
+ [mNativeMenuItem setKeyEquivalent:@""];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+//
+// nsChangeObserver
+//
+
+void
+nsMenuItemX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!aContent)
+ return;
+
+ if (aContent == mContent) { // our own content node changed
+ if (aAttribute == nsGkAtoms::checked) {
+ // if we're a radio menu, uncheck our sibling radio items. No need to
+ // do any of this if we're just a normal check menu.
+ if (mType == eRadioMenuItemType) {
+ if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters))
+ UncheckRadioSiblings(mContent);
+ }
+ mMenuParent->SetRebuild(true);
+ }
+ else if (aAttribute == nsGkAtoms::hidden ||
+ aAttribute == nsGkAtoms::collapsed ||
+ aAttribute == nsGkAtoms::label) {
+ mMenuParent->SetRebuild(true);
+ }
+ else if (aAttribute == nsGkAtoms::key) {
+ SetKeyEquiv();
+ }
+ else if (aAttribute == nsGkAtoms::image) {
+ SetupIcon();
+ }
+ else if (aAttribute == nsGkAtoms::disabled) {
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ }
+ else if (aContent == mCommandContent) {
+ // the only thing that really matters when the menu isn't showing is the
+ // enabled state since it enables/disables keyboard commands
+ if (aAttribute == nsGkAtoms::disabled) {
+ // first we sync our menu item DOM node with the command DOM node
+ nsAutoString commandDisabled;
+ nsAutoString menuDisabled;
+ aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled);
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::disabled, menuDisabled);
+ if (!commandDisabled.Equals(menuDisabled)) {
+ // The menu's disabled state needs to be updated to match the command.
+ if (commandDisabled.IsEmpty())
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
+ else
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, commandDisabled, true);
+ }
+ // now we sync our native menu item with the command DOM node
+ if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, nsGkAtoms::_true, eCaseMatters))
+ [mNativeMenuItem setEnabled:NO];
+ else
+ [mNativeMenuItem setEnabled:YES];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuItemX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, int32_t aIndexInContainer)
+{
+ if (aChild == mCommandContent) {
+ mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
+ mCommandContent = nullptr;
+ }
+
+ mMenuParent->SetRebuild(true);
+}
+
+void nsMenuItemX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
+ nsIContent *aChild)
+{
+ mMenuParent->SetRebuild(true);
+}
+
+void nsMenuItemX::SetupIcon()
+{
+ if (mIcon)
+ mIcon->SetupIcon();
+}
diff --git a/widget/cocoa/nsMenuUtilsX.h b/widget/cocoa/nsMenuUtilsX.h
new file mode 100644
index 0000000000..1571cdfb0a
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsMenuUtilsX_h_
+#define nsMenuUtilsX_h_
+
+#include "nscore.h"
+#include "nsMenuBaseX.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIContent;
+class nsString;
+class nsMenuBarX;
+
+// Namespace containing utility functions used in our native menu implementation.
+namespace nsMenuUtilsX
+{
+ void DispatchCommandTo(nsIContent* aTargetContent);
+ NSString* GetTruncatedCocoaLabel(const nsString& itemLabel);
+ uint8_t GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute);
+ unsigned int MacModifiersForGeckoModifiers(uint8_t geckoModifiers);
+ nsMenuBarX* GetHiddenWindowMenuBar(); // returned object is not retained
+ NSMenuItem* GetStandardEditMenuItem(); // returned object is not retained
+ bool NodeIsHiddenOrCollapsed(nsIContent* inContent);
+ int CalculateNativeInsertionPoint(nsMenuObjectX* aParent, nsMenuObjectX* aChild);
+} // namespace nsMenuUtilsX
+
+#endif // nsMenuUtilsX_h_
diff --git a/widget/cocoa/nsMenuUtilsX.mm b/widget/cocoa/nsMenuUtilsX.mm
new file mode 100644
index 0000000000..db64717127
--- /dev/null
+++ b/widget/cocoa/nsMenuUtilsX.mm
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 2; 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 "mozilla/dom/Event.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsStandaloneNativeMenu.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+#include "nsCocoaWindow.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMXULCommandEvent.h"
+#include "nsPIDOMWindow.h"
+#include "nsQueryObject.h"
+
+using namespace mozilla;
+
+void nsMenuUtilsX::DispatchCommandTo(nsIContent* aTargetContent)
+{
+ NS_PRECONDITION(aTargetContent, "null ptr");
+
+ nsIDocument* doc = aTargetContent->OwnerDoc();
+ if (doc) {
+ ErrorResult rv;
+ RefPtr<dom::Event> event =
+ doc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"), rv);
+ nsCOMPtr<nsIDOMXULCommandEvent> command = do_QueryObject(event);
+
+ // FIXME: Should probably figure out how to init this with the actual
+ // pressed keys, but this is a big old edge case anyway. -dwh
+ if (command &&
+ NS_SUCCEEDED(command->InitCommandEvent(NS_LITERAL_STRING("command"),
+ true, true,
+ doc->GetInnerWindow(), 0,
+ false, false, false,
+ false, nullptr))) {
+ event->SetTrusted(true);
+ bool dummy;
+ aTargetContent->DispatchEvent(event, &dummy);
+ }
+ }
+}
+
+NSString* nsMenuUtilsX::GetTruncatedCocoaLabel(const nsString& itemLabel)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // We want to truncate long strings to some reasonable pixel length but there is no
+ // good API for doing that which works for all OS versions and architectures. For now
+ // we'll do nothing for consistency and depend on good user interface design to limit
+ // string lengths.
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(itemLabel.get())
+ length:itemLabel.Length()];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+uint8_t nsMenuUtilsX::GeckoModifiersForNodeAttribute(const nsString& modifiersAttribute)
+{
+ uint8_t modifiers = knsMenuItemNoModifier;
+ char* str = ToNewCString(modifiersAttribute);
+ char* newStr;
+ char* token = strtok_r(str, ", \t", &newStr);
+ while (token != NULL) {
+ if (strcmp(token, "shift") == 0)
+ modifiers |= knsMenuItemShiftModifier;
+ else if (strcmp(token, "alt") == 0)
+ modifiers |= knsMenuItemAltModifier;
+ else if (strcmp(token, "control") == 0)
+ modifiers |= knsMenuItemControlModifier;
+ else if ((strcmp(token, "accel") == 0) ||
+ (strcmp(token, "meta") == 0)) {
+ modifiers |= knsMenuItemCommandModifier;
+ }
+ token = strtok_r(newStr, ", \t", &newStr);
+ }
+ free(str);
+
+ return modifiers;
+}
+
+unsigned int nsMenuUtilsX::MacModifiersForGeckoModifiers(uint8_t geckoModifiers)
+{
+ unsigned int macModifiers = 0;
+
+ if (geckoModifiers & knsMenuItemShiftModifier)
+ macModifiers |= NSShiftKeyMask;
+ if (geckoModifiers & knsMenuItemAltModifier)
+ macModifiers |= NSAlternateKeyMask;
+ if (geckoModifiers & knsMenuItemControlModifier)
+ macModifiers |= NSControlKeyMask;
+ if (geckoModifiers & knsMenuItemCommandModifier)
+ macModifiers |= NSCommandKeyMask;
+
+ return macModifiers;
+}
+
+nsMenuBarX* nsMenuUtilsX::GetHiddenWindowMenuBar()
+{
+ nsIWidget* hiddenWindowWidgetNoCOMPtr = nsCocoaUtils::GetHiddenWindowWidget();
+ if (hiddenWindowWidgetNoCOMPtr)
+ return static_cast<nsCocoaWindow*>(hiddenWindowWidgetNoCOMPtr)->GetMenuBar();
+ else
+ return nullptr;
+}
+
+// It would be nice if we could localize these edit menu names.
+NSMenuItem* nsMenuUtilsX::GetStandardEditMenuItem()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // In principle we should be able to allocate this once and then always
+ // return the same object. But weird interactions happen between native
+ // app-modal dialogs and Gecko-modal dialogs that open above them. So what
+ // we return here isn't always released before it needs to be added to
+ // another menu. See bmo bug 468393.
+ NSMenuItem* standardEditMenuItem =
+ [[[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""] autorelease];
+ NSMenu* standardEditMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
+ [standardEditMenuItem setSubmenu:standardEditMenu];
+ [standardEditMenu release];
+
+ // Add Undo
+ NSMenuItem* undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"];
+ [standardEditMenu addItem:undoItem];
+ [undoItem release];
+
+ // Add Redo
+ NSMenuItem* redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"];
+ [standardEditMenu addItem:redoItem];
+ [redoItem release];
+
+ // Add separator
+ [standardEditMenu addItem:[NSMenuItem separatorItem]];
+
+ // Add Cut
+ NSMenuItem* cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
+ [standardEditMenu addItem:cutItem];
+ [cutItem release];
+
+ // Add Copy
+ NSMenuItem* copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
+ [standardEditMenu addItem:copyItem];
+ [copyItem release];
+
+ // Add Paste
+ NSMenuItem* pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
+ [standardEditMenu addItem:pasteItem];
+ [pasteItem release];
+
+ // Add Delete
+ NSMenuItem* deleteItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""];
+ [standardEditMenu addItem:deleteItem];
+ [deleteItem release];
+
+ // Add Select All
+ NSMenuItem* selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
+ [standardEditMenu addItem:selectAllItem];
+ [selectAllItem release];
+
+ return standardEditMenuItem;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+bool nsMenuUtilsX::NodeIsHiddenOrCollapsed(nsIContent* inContent)
+{
+ return (inContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters) ||
+ inContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::collapsed,
+ nsGkAtoms::_true, eCaseMatters));
+}
+
+// Determines how many items are visible among the siblings in a menu that are
+// before the given child. This will not count the application menu.
+int nsMenuUtilsX::CalculateNativeInsertionPoint(nsMenuObjectX* aParent,
+ nsMenuObjectX* aChild)
+{
+ int insertionPoint = 0;
+ nsMenuObjectTypeX parentType = aParent->MenuObjectType();
+ if (parentType == eMenuBarObjectType) {
+ nsMenuBarX* menubarParent = static_cast<nsMenuBarX*>(aParent);
+ uint32_t numMenus = menubarParent->GetMenuCount();
+ for (uint32_t i = 0; i < numMenus; i++) {
+ nsMenuX* currMenu = menubarParent->GetMenuAt(i);
+ if (currMenu == aChild)
+ return insertionPoint; // we found ourselves, break out
+ if (currMenu && [currMenu->NativeMenuItem() menu])
+ insertionPoint++;
+ }
+ }
+ else if (parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ nsMenuX* menuParent;
+ if (parentType == eSubmenuObjectType)
+ menuParent = static_cast<nsMenuX*>(aParent);
+ else
+ menuParent = static_cast<nsStandaloneNativeMenu*>(aParent)->GetMenuXObject();
+
+ uint32_t numItems = menuParent->GetItemCount();
+ for (uint32_t i = 0; i < numItems; i++) {
+ // Using GetItemAt instead of GetVisibleItemAt to avoid O(N^2)
+ nsMenuObjectX* currItem = menuParent->GetItemAt(i);
+ if (currItem == aChild)
+ return insertionPoint; // we found ourselves, break out
+ NSMenuItem* nativeItem = nil;
+ nsMenuObjectTypeX currItemType = currItem->MenuObjectType();
+ if (currItemType == eSubmenuObjectType)
+ nativeItem = static_cast<nsMenuX*>(currItem)->NativeMenuItem();
+ else
+ nativeItem = (NSMenuItem*)(currItem->NativeData());
+ if ([nativeItem menu])
+ insertionPoint++;
+ }
+ }
+ return insertionPoint;
+}
diff --git a/widget/cocoa/nsMenuX.h b/widget/cocoa/nsMenuX.h
new file mode 100644
index 0000000000..7b5146a0b8
--- /dev/null
+++ b/widget/cocoa/nsMenuX.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsMenuX_h_
+#define nsMenuX_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsMenuBaseX.h"
+#include "nsMenuBarX.h"
+#include "nsMenuGroupOwnerX.h"
+#include "nsCOMPtr.h"
+#include "nsChangeObserver.h"
+
+class nsMenuX;
+class nsMenuItemIconX;
+class nsMenuItemX;
+class nsIWidget;
+
+// MenuDelegate is used to receive Cocoa notifications for setting
+// up carbon events. Protocol is defined as of 10.6 SDK.
+@interface MenuDelegate : NSObject < NSMenuDelegate >
+{
+ nsMenuX* mGeckoMenu; // weak ref
+}
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu;
+@end
+
+// Once instantiated, this object lives until its DOM node or its parent window is destroyed.
+// Do not hold references to this, they can become invalid any time the DOM node can be destroyed.
+class nsMenuX : public nsMenuObjectX,
+ public nsChangeObserver
+{
+public:
+ nsMenuX();
+ virtual ~nsMenuX();
+
+ // If > 0, the OS is indexing all the app's menus (triggered by opening
+ // Help menu on Leopard and higher). There are some things that are
+ // unsafe to do while this is happening.
+ static int32_t sIndexingMenuLevel;
+
+ NS_DECL_CHANGEOBSERVER
+
+ // nsMenuObjectX
+ void* NativeData() override {return (void*)mNativeMenu;}
+ nsMenuObjectTypeX MenuObjectType() override {return eSubmenuObjectType;}
+ void IconUpdated() override { mParent->IconUpdated(); }
+
+ // nsMenuX
+ nsresult Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode);
+ uint32_t GetItemCount();
+ nsMenuObjectX* GetItemAt(uint32_t aPos);
+ nsresult GetVisibleItemCount(uint32_t &aCount);
+ nsMenuObjectX* GetVisibleItemAt(uint32_t aPos);
+ nsEventStatus MenuOpened();
+ void MenuClosed();
+ void SetRebuild(bool aMenuEvent);
+ NSMenuItem* NativeMenuItem();
+ nsresult SetupIcon();
+
+ static bool IsXULHelpMenu(nsIContent* aMenuContent);
+
+protected:
+ void MenuConstruct();
+ nsresult RemoveAll();
+ nsresult SetEnabled(bool aIsEnabled);
+ nsresult GetEnabled(bool* aIsEnabled);
+ void GetMenuPopupContent(nsIContent** aResult);
+ bool OnOpen();
+ bool OnClose();
+ nsresult AddMenuItem(nsMenuItemX* aMenuItem);
+ nsMenuX* AddMenu(mozilla::UniquePtr<nsMenuX> aMenu);
+ void LoadMenuItem(nsIContent* inMenuItemContent);
+ void LoadSubMenu(nsIContent* inMenuContent);
+ GeckoNSMenu* CreateMenuWithGeckoString(nsString& menuTitle);
+
+ nsTArray<mozilla::UniquePtr<nsMenuObjectX>> mMenuObjectsArray;
+ nsString mLabel;
+ uint32_t mVisibleItemsCount; // cache
+ nsMenuObjectX* mParent; // [weak]
+ nsMenuGroupOwnerX* mMenuGroupOwner; // [weak]
+ // The icon object should never outlive its creating nsMenuX object.
+ RefPtr<nsMenuItemIconX> mIcon;
+ GeckoNSMenu* mNativeMenu; // [strong]
+ MenuDelegate* mMenuDelegate; // [strong]
+ // nsMenuX objects should always have a valid native menu item.
+ NSMenuItem* mNativeMenuItem; // [strong]
+ bool mIsEnabled;
+ bool mDestroyHandlerCalled;
+ bool mNeedsRebuild;
+ bool mConstructed;
+ bool mVisible;
+ bool mXBLAttached;
+};
+
+#endif // nsMenuX_h_
diff --git a/widget/cocoa/nsMenuX.mm b/widget/cocoa/nsMenuX.mm
new file mode 100644
index 0000000000..757221eac6
--- /dev/null
+++ b/widget/cocoa/nsMenuX.mm
@@ -0,0 +1,1051 @@
+/* -*- Mode: C++; tab-width: 2; 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 <dlfcn.h>
+
+#include "nsMenuX.h"
+#include "nsMenuItemX.h"
+#include "nsMenuUtilsX.h"
+#include "nsMenuItemIconX.h"
+#include "nsStandaloneNativeMenu.h"
+
+#include "nsObjCExceptions.h"
+
+#include "nsToolkit.h"
+#include "nsCocoaUtils.h"
+#include "nsCOMPtr.h"
+#include "prinrval.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "plstr.h"
+#include "nsGkAtoms.h"
+#include "nsCRT.h"
+#include "nsBaseWidget.h"
+
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocumentObserver.h"
+#include "nsIComponentManager.h"
+#include "nsIRollupListener.h"
+#include "nsIDOMElement.h"
+#include "nsBindingManager.h"
+#include "nsIServiceManager.h"
+#include "nsXULPopupManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "jsapi.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIScriptContext.h"
+#include "nsIXPConnect.h"
+
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+
+static bool gConstructingMenu = false;
+static bool gMenuMethodsSwizzled = false;
+
+int32_t nsMenuX::sIndexingMenuLevel = 0;
+
+
+//
+// Objective-C class used for representedObject
+//
+
+@implementation MenuItemInfo
+
+- (id) initWithMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
+{
+ if ((self = [super init]) != nil) {
+ [self setMenuGroupOwner:aMenuGroupOwner];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ [self setMenuGroupOwner:nullptr];
+ [super dealloc];
+}
+
+- (nsMenuGroupOwnerX *) menuGroupOwner
+{
+ return mMenuGroupOwner;
+}
+
+- (void) setMenuGroupOwner:(nsMenuGroupOwnerX *)aMenuGroupOwner
+{
+ // weak reference as the nsMenuGroupOwnerX owns all of its sub-objects
+ mMenuGroupOwner = aMenuGroupOwner;
+ if (aMenuGroupOwner) {
+ aMenuGroupOwner->AddMenuItemInfoToSet(self);
+ }
+}
+
+@end
+
+
+//
+// nsMenuX
+//
+
+nsMenuX::nsMenuX()
+: mVisibleItemsCount(0), mParent(nullptr), mMenuGroupOwner(nullptr),
+ mNativeMenu(nil), mNativeMenuItem(nil), mIsEnabled(true),
+ mDestroyHandlerCalled(false), mNeedsRebuild(true),
+ mConstructed(false), mVisible(true), mXBLAttached(false)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!gMenuMethodsSwizzled) {
+ nsToolkit::SwizzleMethods([NSMenu class], @selector(_addItem:toTable:),
+ @selector(nsMenuX_NSMenu_addItem:toTable:), true);
+ nsToolkit::SwizzleMethods([NSMenu class], @selector(_removeItem:fromTable:),
+ @selector(nsMenuX_NSMenu_removeItem:fromTable:), true);
+ // On SnowLeopard the Shortcut framework (which contains the
+ // SCTGRLIndex class) is loaded on demand, whenever the user first opens
+ // a menu (which normally hasn't happened yet). So we need to load it
+ // here explicitly.
+ dlopen("/System/Library/PrivateFrameworks/Shortcut.framework/Shortcut", RTLD_LAZY);
+ Class SCTGRLIndexClass = ::NSClassFromString(@"SCTGRLIndex");
+ nsToolkit::SwizzleMethods(SCTGRLIndexClass, @selector(indexMenuBarDynamically),
+ @selector(nsMenuX_SCTGRLIndex_indexMenuBarDynamically));
+
+ gMenuMethodsSwizzled = true;
+ }
+
+ mMenuDelegate = [[MenuDelegate alloc] initWithGeckoMenu:this];
+
+ if (!nsMenuBarX::sNativeEventTarget)
+ nsMenuBarX::sNativeEventTarget = [[NativeMenuItemTarget alloc] init];
+
+ MOZ_COUNT_CTOR(nsMenuX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsMenuX::~nsMenuX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Prevent the icon object from outliving us.
+ if (mIcon)
+ mIcon->Destroy();
+
+ RemoveAll();
+
+ [mNativeMenu setDelegate:nil];
+ [mNativeMenu release];
+ [mMenuDelegate release];
+ // autorelease the native menu item so that anything else happening to this
+ // object happens before the native menu item actually dies
+ [mNativeMenuItem autorelease];
+
+ // alert the change notifier we don't care no more
+ if (mContent)
+ mMenuGroupOwner->UnregisterForContentChanges(mContent);
+
+ MOZ_COUNT_DTOR(nsMenuX);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult nsMenuX::Create(nsMenuObjectX* aParent, nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mContent = aNode;
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
+ mNativeMenu = CreateMenuWithGeckoString(mLabel);
+
+ // register this menu to be notified when changes are made to our content object
+ mMenuGroupOwner = aMenuGroupOwner; // weak ref
+ NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one");
+ mMenuGroupOwner->RegisterForContentChanges(mContent, this);
+
+ mParent = aParent;
+ // our parent could be either a menu bar (if we're toplevel) or a menu (if we're a submenu)
+
+#ifdef DEBUG
+ nsMenuObjectTypeX parentType =
+#endif
+ mParent->MenuObjectType();
+ NS_ASSERTION((parentType == eMenuBarObjectType || parentType == eSubmenuObjectType || parentType == eStandaloneNativeMenuObjectType),
+ "Menu parent not a menu bar, menu, or native menu!");
+
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent))
+ mVisible = false;
+ if (mContent->GetChildCount() == 0)
+ mVisible = false;
+
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
+ mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
+ [mNativeMenuItem setSubmenu:mNativeMenu];
+
+ SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+
+ // We call MenuConstruct here because keyboard commands are dependent upon
+ // native menu items being created. If we only call MenuConstruct when a menu
+ // is actually selected, then we can't access keyboard commands until the
+ // menu gets selected, which is bad.
+ MenuConstruct();
+
+ mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsMenuX::AddMenuItem(nsMenuItemX* aMenuItem)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!aMenuItem)
+ return NS_ERROR_INVALID_ARG;
+
+ mMenuObjectsArray.AppendElement(aMenuItem);
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(aMenuItem->Content()))
+ return NS_OK;
+ ++mVisibleItemsCount;
+
+ NSMenuItem* newNativeMenuItem = (NSMenuItem*)aMenuItem->NativeData();
+
+ // add the menu item to this menu
+ [mNativeMenu addItem:newNativeMenuItem];
+
+ // set up target/action
+ [newNativeMenuItem setTarget:nsMenuBarX::sNativeEventTarget];
+ [newNativeMenuItem setAction:@selector(menuItemHit:)];
+
+ // set its command. we get the unique command id from the menubar
+ [newNativeMenuItem setTag:mMenuGroupOwner->RegisterForCommand(aMenuItem)];
+ MenuItemInfo * info = [[MenuItemInfo alloc] initWithMenuGroupOwner:mMenuGroupOwner];
+ [newNativeMenuItem setRepresentedObject:info];
+ [info release];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsMenuX* nsMenuX::AddMenu(UniquePtr<nsMenuX> aMenu)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ // aMenu transfers ownership to mMenuObjectsArray and becomes nullptr, so
+ // we need to keep a raw pointer to access it conveniently.
+ nsMenuX* menu = aMenu.get();
+ mMenuObjectsArray.AppendElement(Move(aMenu));
+
+ if (nsMenuUtilsX::NodeIsHiddenOrCollapsed(menu->Content())) {
+ return menu;
+ }
+
+ ++mVisibleItemsCount;
+
+ // We have to add a menu item and then associate the menu with it
+ NSMenuItem* newNativeMenuItem = menu->NativeMenuItem();
+ if (newNativeMenuItem) {
+ [mNativeMenu addItem:newNativeMenuItem];
+ [newNativeMenuItem setSubmenu:(NSMenu*)menu->NativeData()];
+ }
+
+ return menu;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+// Includes all items, including hidden/collapsed ones
+uint32_t nsMenuX::GetItemCount()
+{
+ return mMenuObjectsArray.Length();
+}
+
+// Includes all items, including hidden/collapsed ones
+nsMenuObjectX* nsMenuX::GetItemAt(uint32_t aPos)
+{
+ if (aPos >= (uint32_t)mMenuObjectsArray.Length())
+ return NULL;
+
+ return mMenuObjectsArray[aPos].get();
+}
+
+// Only includes visible items
+nsresult nsMenuX::GetVisibleItemCount(uint32_t &aCount)
+{
+ aCount = mVisibleItemsCount;
+ return NS_OK;
+}
+
+// Only includes visible items. Note that this is provides O(N) access
+// If you need to iterate or search, consider using GetItemAt and doing your own filtering
+nsMenuObjectX* nsMenuX::GetVisibleItemAt(uint32_t aPos)
+{
+
+ uint32_t count = mMenuObjectsArray.Length();
+ if (aPos >= mVisibleItemsCount || aPos >= count)
+ return NULL;
+
+ // If there are no invisible items, can provide direct access
+ if (mVisibleItemsCount == count)
+ return mMenuObjectsArray[aPos].get();
+
+ // Otherwise, traverse the array until we find the the item we're looking for.
+ nsMenuObjectX* item;
+ uint32_t visibleNodeIndex = 0;
+ for (uint32_t i = 0; i < count; i++) {
+ item = mMenuObjectsArray[i].get();
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(item->Content())) {
+ if (aPos == visibleNodeIndex) {
+ // we found the visible node we're looking for, return it
+ return item;
+ }
+ visibleNodeIndex++;
+ }
+ }
+
+ return NULL;
+}
+
+nsresult nsMenuX::RemoveAll()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (mNativeMenu) {
+ // clear command id's
+ int itemCount = [mNativeMenu numberOfItems];
+ for (int i = 0; i < itemCount; i++)
+ mMenuGroupOwner->UnregisterCommand((uint32_t)[[mNativeMenu itemAtIndex:i] tag]);
+ // get rid of Cocoa menu items
+ for (int i = [mNativeMenu numberOfItems] - 1; i >= 0; i--)
+ [mNativeMenu removeItemAtIndex:i];
+ }
+
+ mMenuObjectsArray.Clear();
+ mVisibleItemsCount = 0;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsEventStatus nsMenuX::MenuOpened()
+{
+ // Open the node.
+ mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true);
+
+ // Fire a handler. If we're told to stop, don't build the menu at all
+ bool keepProcessing = OnOpen();
+
+ if (!mNeedsRebuild || !keepProcessing)
+ return nsEventStatus_eConsumeNoDefault;
+
+ if (!mConstructed || mNeedsRebuild) {
+ if (mNeedsRebuild)
+ RemoveAll();
+
+ MenuConstruct();
+ mConstructed = true;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShown, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ return nsEventStatus_eConsumeNoDefault;
+}
+
+void nsMenuX::MenuClosed()
+{
+ if (mConstructed) {
+ // Don't close if a handler tells us to stop.
+ if (!OnClose())
+ return;
+
+ if (mNeedsRebuild)
+ mConstructed = false;
+
+ mContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::open, true);
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHidden, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ mDestroyHandlerCalled = true;
+ mConstructed = false;
+ }
+}
+
+void nsMenuX::MenuConstruct()
+{
+ mConstructed = false;
+ gConstructingMenu = true;
+
+ // reset destroy handler flag so that we'll know to fire it next time this menu goes away.
+ mDestroyHandlerCalled = false;
+
+ //printf("nsMenuX::MenuConstruct called for %s = %d \n", NS_LossyConvertUTF16toASCII(mLabel).get(), mNativeMenu);
+
+ // Retrieve our menupopup.
+ nsCOMPtr<nsIContent> menuPopup;
+ GetMenuPopupContent(getter_AddRefs(menuPopup));
+ if (!menuPopup) {
+ gConstructingMenu = false;
+ return;
+ }
+
+ // bug 365405: Manually wrap the menupopup node to make sure it's bounded
+ if (!mXBLAttached) {
+ nsresult rv;
+ nsCOMPtr<nsIXPConnect> xpconnect =
+ do_GetService(nsIXPConnect::GetCID(), &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsIDocument* ownerDoc = menuPopup->OwnerDoc();
+ dom::AutoJSAPI jsapi;
+ if (ownerDoc && jsapi.Init(ownerDoc->GetInnerWindow())) {
+ JSContext* cx = jsapi.cx();
+ JS::RootedObject ignoredObj(cx);
+ xpconnect->WrapNative(cx, JS::CurrentGlobalOrNull(cx), menuPopup,
+ NS_GET_IID(nsISupports), ignoredObj.address());
+ mXBLAttached = true;
+ }
+ }
+ }
+
+ // Iterate over the kids
+ uint32_t count = menuPopup->GetChildCount();
+ for (uint32_t i = 0; i < count; i++) {
+ nsIContent *child = menuPopup->GetChildAt(i);
+ if (child) {
+ // depending on the type, create a menu item, separator, or submenu
+ if (child->IsAnyOfXULElements(nsGkAtoms::menuitem,
+ nsGkAtoms::menuseparator)) {
+ LoadMenuItem(child);
+ } else if (child->IsXULElement(nsGkAtoms::menu)) {
+ LoadSubMenu(child);
+ }
+ }
+ } // for each menu item
+
+ gConstructingMenu = false;
+ mNeedsRebuild = false;
+ // printf("Done building, mMenuObjectsArray.Count() = %d \n", mMenuObjectsArray.Count());
+}
+
+void nsMenuX::SetRebuild(bool aNeedsRebuild)
+{
+ if (!gConstructingMenu)
+ mNeedsRebuild = aNeedsRebuild;
+}
+
+nsresult nsMenuX::SetEnabled(bool aIsEnabled)
+{
+ if (aIsEnabled != mIsEnabled) {
+ // we always want to rebuild when this changes
+ mIsEnabled = aIsEnabled;
+ [mNativeMenuItem setEnabled:(BOOL)mIsEnabled];
+ }
+ return NS_OK;
+}
+
+nsresult nsMenuX::GetEnabled(bool* aIsEnabled)
+{
+ NS_ENSURE_ARG_POINTER(aIsEnabled);
+ *aIsEnabled = mIsEnabled;
+ return NS_OK;
+}
+
+GeckoNSMenu* nsMenuX::CreateMenuWithGeckoString(nsString& menuTitle)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSString* title = [NSString stringWithCharacters:(UniChar*)menuTitle.get() length:menuTitle.Length()];
+ GeckoNSMenu* myMenu = [[GeckoNSMenu alloc] initWithTitle:title];
+ [myMenu setDelegate:mMenuDelegate];
+
+ // We don't want this menu to auto-enable menu items because then Cocoa
+ // overrides our decisions and things get incorrectly enabled/disabled.
+ [myMenu setAutoenablesItems:NO];
+
+ // we used to install Carbon event handlers here, but since NSMenu* doesn't
+ // create its underlying MenuRef until just before display, we delay until
+ // that happens. Now we install the event handlers when Cocoa notifies
+ // us that a menu is about to display - see the Cocoa MenuDelegate class.
+
+ return myMenu;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void nsMenuX::LoadMenuItem(nsIContent* inMenuItemContent)
+{
+ if (!inMenuItemContent)
+ return;
+
+ nsAutoString menuitemName;
+ inMenuItemContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, menuitemName);
+
+ // printf("menuitem %s \n", NS_LossyConvertUTF16toASCII(menuitemName).get());
+
+ EMenuItemType itemType = eRegularMenuItemType;
+ if (inMenuItemContent->IsXULElement(nsGkAtoms::menuseparator)) {
+ itemType = eSeparatorMenuItemType;
+ }
+ else {
+ static nsIContent::AttrValuesArray strings[] =
+ {&nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr};
+ switch (inMenuItemContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
+ strings, eCaseMatters)) {
+ case 0: itemType = eCheckboxMenuItemType; break;
+ case 1: itemType = eRadioMenuItemType; break;
+ }
+ }
+
+ // Create the item.
+ nsMenuItemX* menuItem = new nsMenuItemX();
+ if (!menuItem)
+ return;
+
+ nsresult rv = menuItem->Create(this, menuitemName, itemType, mMenuGroupOwner, inMenuItemContent);
+ if (NS_FAILED(rv)) {
+ delete menuItem;
+ return;
+ }
+
+ AddMenuItem(menuItem);
+
+ // This needs to happen after the nsIMenuItem object is inserted into
+ // our item array in AddMenuItem()
+ menuItem->SetupIcon();
+}
+
+void nsMenuX::LoadSubMenu(nsIContent* inMenuContent)
+{
+ auto menu = MakeUnique<nsMenuX>();
+ if (!menu)
+ return;
+
+ nsresult rv = menu->Create(this, mMenuGroupOwner, inMenuContent);
+ if (NS_FAILED(rv))
+ return;
+
+ // |menu|'s ownership is transfer to AddMenu but, if it is successfully
+ // added, we can access it via the returned raw pointer.
+ nsMenuX* menu_ptr = AddMenu(Move(menu));
+
+ // This needs to happen after the nsIMenu object is inserted into
+ // our item array in AddMenu()
+ if (menu_ptr) {
+ menu_ptr->SetupIcon();
+ }
+}
+
+// This menu is about to open. Returns TRUE if we should keep processing the event,
+// FALSE if the handler wants to stop the opening of the menu.
+bool nsMenuX::OnOpen()
+{
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupShowing, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+ if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
+ return false;
+
+ // If the open is going to succeed we need to walk our menu items, checking to
+ // see if any of them have a command attribute. If so, several attributes
+ // must potentially be updated.
+
+ // Get new popup content first since it might have changed as a result of the
+ // eXULPopupShowing event above.
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+ if (!popupContent)
+ return true;
+
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ pm->UpdateMenuItems(popupContent);
+ }
+
+ return true;
+}
+
+// Returns TRUE if we should keep processing the event, FALSE if the handler
+// wants to stop the closing of the menu.
+bool nsMenuX::OnClose()
+{
+ if (mDestroyHandlerCalled)
+ return true;
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, eXULPopupHiding, nullptr,
+ WidgetMouseEvent::eReal);
+
+ nsCOMPtr<nsIContent> popupContent;
+ GetMenuPopupContent(getter_AddRefs(popupContent));
+
+ nsresult rv = NS_OK;
+ nsIContent* dispatchTo = popupContent ? popupContent : mContent;
+ rv = dispatchTo->DispatchDOMEvent(&event, nullptr, nullptr, &status);
+
+ mDestroyHandlerCalled = true;
+
+ if (NS_FAILED(rv) || status == nsEventStatus_eConsumeNoDefault)
+ return false;
+
+ return true;
+}
+
+// Find the |menupopup| child in the |popup| representing this menu. It should be one
+// of a very few children so we won't be iterating over a bazillion menu items to find
+// it (so the strcmp won't kill us).
+void nsMenuX::GetMenuPopupContent(nsIContent** aResult)
+{
+ if (!aResult)
+ return;
+ *aResult = nullptr;
+
+ // Check to see if we are a "menupopup" node (if we are a native menu).
+ {
+ int32_t dummy;
+ nsCOMPtr<nsIAtom> tag = mContent->OwnerDoc()->BindingManager()->ResolveTag(mContent, &dummy);
+ if (tag == nsGkAtoms::menupopup) {
+ *aResult = mContent;
+ NS_ADDREF(*aResult);
+ return;
+ }
+ }
+
+ // Otherwise check our child nodes.
+
+ uint32_t count = mContent->GetChildCount();
+
+ for (uint32_t i = 0; i < count; i++) {
+ int32_t dummy;
+ nsIContent *child = mContent->GetChildAt(i);
+ nsCOMPtr<nsIAtom> tag = child->OwnerDoc()->BindingManager()->ResolveTag(child, &dummy);
+ if (tag == nsGkAtoms::menupopup) {
+ *aResult = child;
+ NS_ADDREF(*aResult);
+ return;
+ }
+ }
+}
+
+NSMenuItem* nsMenuX::NativeMenuItem()
+{
+ return mNativeMenuItem;
+}
+
+bool nsMenuX::IsXULHelpMenu(nsIContent* aMenuContent)
+{
+ bool retval = false;
+ if (aMenuContent) {
+ nsAutoString id;
+ aMenuContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, id);
+ if (id.Equals(NS_LITERAL_STRING("helpMenu")))
+ retval = true;
+ }
+ return retval;
+}
+
+//
+// nsChangeObserver
+//
+
+void nsMenuX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent,
+ nsIAtom *aAttribute)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // ignore the |open| attribute, which is by far the most common
+ if (gConstructingMenu || (aAttribute == nsGkAtoms::open))
+ return;
+
+ nsMenuObjectTypeX parentType = mParent->MenuObjectType();
+
+ if (aAttribute == nsGkAtoms::disabled) {
+ SetEnabled(!mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters));
+ }
+ else if (aAttribute == nsGkAtoms::label) {
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, mLabel);
+
+ // invalidate my parent. If we're a submenu parent, we have to rebuild
+ // the parent menu in order for the changes to be picked up. If we're
+ // a regular menu, just change the title and redraw the menubar.
+ if (parentType == eMenuBarObjectType) {
+ // reuse the existing menu, to avoid rebuilding the root menu bar.
+ NS_ASSERTION(mNativeMenu, "nsMenuX::AttributeChanged: invalid menu handle.");
+ NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(mLabel);
+ [mNativeMenu setTitle:newCocoaLabelString];
+ }
+ else if (parentType == eSubmenuObjectType) {
+ static_cast<nsMenuX*>(mParent)->SetRebuild(true);
+ }
+ else if (parentType == eStandaloneNativeMenuObjectType) {
+ static_cast<nsStandaloneNativeMenu*>(mParent)->GetMenuXObject()->SetRebuild(true);
+ }
+ }
+ else if (aAttribute == nsGkAtoms::hidden || aAttribute == nsGkAtoms::collapsed) {
+ SetRebuild(true);
+
+ bool contentIsHiddenOrCollapsed = nsMenuUtilsX::NodeIsHiddenOrCollapsed(mContent);
+
+ // don't do anything if the state is correct already
+ if (contentIsHiddenOrCollapsed != mVisible)
+ return;
+
+ if (contentIsHiddenOrCollapsed) {
+ if (parentType == eMenuBarObjectType ||
+ parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ // An exception will get thrown if we try to remove an item that isn't
+ // in the menu.
+ if ([parentMenu indexOfItem:mNativeMenuItem] != -1)
+ [parentMenu removeItem:mNativeMenuItem];
+ mVisible = false;
+ }
+ }
+ else {
+ if (parentType == eMenuBarObjectType ||
+ parentType == eSubmenuObjectType ||
+ parentType == eStandaloneNativeMenuObjectType) {
+ int insertionIndex = nsMenuUtilsX::CalculateNativeInsertionPoint(mParent, this);
+ if (parentType == eMenuBarObjectType) {
+ // Before inserting we need to figure out if we should take the native
+ // application menu into account.
+ nsMenuBarX* mb = static_cast<nsMenuBarX*>(mParent);
+ if (mb->MenuContainsAppMenu())
+ insertionIndex++;
+ }
+ NSMenu* parentMenu = (NSMenu*)mParent->NativeData();
+ [parentMenu insertItem:mNativeMenuItem atIndex:insertionIndex];
+ [mNativeMenuItem setSubmenu:mNativeMenu];
+ mVisible = true;
+ }
+ }
+ }
+ else if (aAttribute == nsGkAtoms::image) {
+ SetupIcon();
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void nsMenuX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild,
+ int32_t aIndexInContainer)
+{
+ if (gConstructingMenu)
+ return;
+
+ SetRebuild(true);
+ mMenuGroupOwner->UnregisterForContentChanges(aChild);
+}
+
+void nsMenuX::ObserveContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
+ nsIContent *aChild)
+{
+ if (gConstructingMenu)
+ return;
+
+ SetRebuild(true);
+}
+
+nsresult nsMenuX::SetupIcon()
+{
+ // In addition to out-of-memory, menus that are children of the menu bar
+ // will not have mIcon set.
+ if (!mIcon)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return mIcon->SetupIcon();
+}
+
+//
+// MenuDelegate Objective-C class, used to set up Carbon events
+//
+
+@implementation MenuDelegate
+
+- (id)initWithGeckoMenu:(nsMenuX*)geckoMenu
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ NS_ASSERTION(geckoMenu, "Cannot initialize native menu delegate with NULL gecko menu! Will crash!");
+ mGeckoMenu = geckoMenu;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item
+{
+ if (!menu || !item || !mGeckoMenu)
+ return;
+
+ nsMenuObjectX* target = mGeckoMenu->GetVisibleItemAt((uint32_t)[menu indexOfItem:item]);
+ if (target && (target->MenuObjectType() == eMenuItemObjectType)) {
+ nsMenuItemX* targetMenuItem = static_cast<nsMenuItemX*>(target);
+ bool handlerCalledPreventDefault; // but we don't actually care
+ targetMenuItem->DispatchDOMEvent(NS_LITERAL_STRING("DOMMenuItemActive"), &handlerCalledPreventDefault);
+ }
+}
+
+- (void)menuWillOpen:(NSMenu *)menu
+{
+ if (!mGeckoMenu)
+ return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0)
+ return;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (rollupWidget) {
+ rollupListener->Rollup(0, true, nullptr, nullptr);
+ [menu cancelTracking];
+ return;
+ }
+ }
+ mGeckoMenu->MenuOpened();
+}
+
+- (void)menuDidClose:(NSMenu *)menu
+{
+ if (!mGeckoMenu)
+ return;
+
+ // Don't do anything while the OS is (re)indexing our menus (on Leopard and
+ // higher). This stops the Help menu from being able to search in our
+ // menus, but it also resolves many other problems.
+ if (nsMenuX::sIndexingMenuLevel > 0)
+ return;
+
+ mGeckoMenu->MenuClosed();
+}
+
+@end
+
+// OS X Leopard (at least as of 10.5.2) has an obscure bug triggered by some
+// behavior that's present in Mozilla.org browsers but not (as best I can
+// tell) in Apple products like Safari. (It's not yet clear exactly what this
+// behavior is.)
+//
+// The bug is that sometimes you crash on quit in nsMenuX::RemoveAll(), on a
+// call to [NSMenu removeItemAtIndex:]. The crash is caused by trying to
+// access a deleted NSMenuItem object (sometimes (perhaps always?) by trying
+// to send it a _setChangedFlags: message). Though this object was deleted
+// some time ago, it remains registered as a potential target for a particular
+// key equivalent. So when [NSMenu removeItemAtIndex:] removes the current
+// target for that same key equivalent, the OS tries to "activate" the
+// previous target.
+//
+// The underlying reason appears to be that NSMenu's _addItem:toTable: and
+// _removeItem:fromTable: methods (which are used to keep a hashtable of
+// registered key equivalents) don't properly "retain" and "release"
+// NSMenuItem objects as they are added to and removed from the hashtable.
+//
+// Our (hackish) workaround is to shadow the OS's hashtable with another
+// hastable of our own (gShadowKeyEquivDB), and use it to "retain" and
+// "release" NSMenuItem objects as needed. This resolves bmo bugs 422287 and
+// 423669. When (if) Apple fixes this bug, we can remove this workaround.
+
+static NSMutableDictionary *gShadowKeyEquivDB = nil;
+
+// Class for values in gShadowKeyEquivDB.
+
+@interface KeyEquivDBItem : NSObject
+{
+ NSMenuItem *mItem;
+ NSMutableSet *mTables;
+}
+
+- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable;
+- (BOOL)hasTable:(NSMapTable *)aTable;
+- (int)addTable:(NSMapTable *)aTable;
+- (int)removeTable:(NSMapTable *)aTable;
+
+@end
+
+@implementation KeyEquivDBItem
+
+- (id)initWithItem:(NSMenuItem *)aItem table:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!gShadowKeyEquivDB)
+ gShadowKeyEquivDB = [[NSMutableDictionary alloc] init];
+ self = [super init];
+ if (aItem && aTable) {
+ mTables = [[NSMutableSet alloc] init];
+ mItem = [aItem retain];
+ [mTables addObject:[NSValue valueWithPointer:aTable]];
+ } else {
+ mTables = nil;
+ mItem = nil;
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mTables)
+ [mTables release];
+ if (mItem)
+ [mItem release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)hasTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ return [mTables member:[NSValue valueWithPointer:aTable]] ? YES : NO;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+// Does nothing if aTable (its index value) is already present in mTables.
+- (int)addTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable)
+ [mTables addObject:[NSValue valueWithPointer:aTable]];
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+- (int)removeTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (aTable) {
+ NSValue *objectToRemove =
+ [mTables member:[NSValue valueWithPointer:aTable]];
+ if (objectToRemove)
+ [mTables removeObject:objectToRemove];
+ }
+ return [mTables count];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
+}
+
+@end
+
+@interface NSMenu (MethodSwizzling)
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable;
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable;
+@end
+
+@implementation NSMenu (MethodSwizzling)
+
++ (void)nsMenuX_NSMenu_addItem:(NSMenuItem *)aItem toTable:(NSMapTable *)aTable
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue *key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem) {
+ [shadowItem addTable:aTable];
+ } else {
+ shadowItem = [[KeyEquivDBItem alloc] initWithItem:aItem table:aTable];
+ [gShadowKeyEquivDB setObject:shadowItem forKey:key];
+ // Release after [NSMutableDictionary setObject:forKey:] retains it (so
+ // that it will get dealloced when removeObjectForKey: is called).
+ [shadowItem release];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+
+ [self nsMenuX_NSMenu_addItem:aItem toTable:aTable];
+}
+
++ (void)nsMenuX_NSMenu_removeItem:(NSMenuItem *)aItem fromTable:(NSMapTable *)aTable
+{
+ [self nsMenuX_NSMenu_removeItem:aItem fromTable:aTable];
+
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (aItem && aTable) {
+ NSValue *key = [NSValue valueWithPointer:aItem];
+ KeyEquivDBItem *shadowItem = [gShadowKeyEquivDB objectForKey:key];
+ if (shadowItem && [shadowItem hasTable:aTable]) {
+ if (![shadowItem removeTable:aTable])
+ [gShadowKeyEquivDB removeObjectForKey:key];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+// This class is needed to keep track of when the OS is (re)indexing all of
+// our menus. This appears to only happen on Leopard and higher, and can
+// be triggered by opening the Help menu. Some operations are unsafe while
+// this is happening -- notably the calls to [[NSImage alloc]
+// initWithSize:imageRect.size] and [newImage lockFocus] in nsMenuItemIconX::
+// OnStopFrame(). But we don't yet have a complete list, and Apple doesn't
+// yet have any documentation on this subject. (Apple also doesn't yet have
+// any documented way to find the information we seek here.) The "original"
+// of this class (the one whose indexMenuBarDynamically method we hook) is
+// defined in the Shortcut framework in /System/Library/PrivateFrameworks.
+@interface NSObject (SCTGRLIndexMethodSwizzling)
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically;
+@end
+
+@implementation NSObject (SCTGRLIndexMethodSwizzling)
+
+- (void)nsMenuX_SCTGRLIndex_indexMenuBarDynamically
+{
+ // This method appears to be called (once) whenever the OS (re)indexes our
+ // menus. sIndexingMenuLevel is a int32_t just in case it might be
+ // reentered. As it's running, it spawns calls to two undocumented
+ // HIToolbox methods (_SimulateMenuOpening() and _SimulateMenuClosed()),
+ // which "simulate" the opening and closing of our menus without actually
+ // displaying them.
+ ++nsMenuX::sIndexingMenuLevel;
+ [self nsMenuX_SCTGRLIndex_indexMenuBarDynamically];
+ --nsMenuX::sIndexingMenuLevel;
+}
+
+@end
diff --git a/widget/cocoa/nsNativeThemeCocoa.h b/widget/cocoa/nsNativeThemeCocoa.h
new file mode 100644
index 0000000000..23f2bc4d32
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsNativeThemeCocoa_h_
+#define nsNativeThemeCocoa_h_
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsIAtom.h"
+#include "nsNativeTheme.h"
+
+@class CellDrawView;
+@class NSProgressBarCell;
+@class ContextAwareSearchFieldCell;
+class nsDeviceContext;
+struct SegmentedControlRenderSettings;
+
+namespace mozilla {
+class EventStates;
+} // namespace mozilla
+
+class nsNativeThemeCocoa : private nsNativeTheme,
+ public nsITheme
+{
+public:
+ enum {
+ eThemeGeometryTypeTitlebar = eThemeGeometryTypeUnknown + 1,
+ eThemeGeometryTypeToolbar,
+ eThemeGeometryTypeToolbox,
+ eThemeGeometryTypeWindowButtons,
+ eThemeGeometryTypeFullscreenButton,
+ eThemeGeometryTypeMenu,
+ eThemeGeometryTypeHighlightedMenuItem,
+ eThemeGeometryTypeVibrancyLight,
+ eThemeGeometryTypeVibrancyDark,
+ eThemeGeometryTypeTooltip,
+ eThemeGeometryTypeSheet,
+ eThemeGeometryTypeSourceList,
+ eThemeGeometryTypeSourceListSelection,
+ eThemeGeometryTypeActiveSourceListSelection
+ };
+
+ nsNativeThemeCocoa();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+ NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ mozilla::LayoutDeviceIntSize* aResult, bool* aIsOverridable) override;
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+ NS_IMETHOD ThemeChanged() override;
+ bool ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, uint8_t aWidgetType) override;
+ bool WidgetIsContainer(uint8_t aWidgetType) override;
+ bool ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+ bool ThemeNeedsComboboxDropmarker() override;
+ virtual bool WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) override;
+ virtual bool NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+ virtual bool WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame, uint8_t aWidgetType,
+ nscolor* aColor) override;
+ virtual ThemeGeometryType ThemeGeometryTypeForWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType) override;
+ virtual Transparency GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) override;
+
+ void DrawProgress(CGContextRef context, const HIRect& inBoxRect,
+ bool inIsIndeterminate, bool inIsHorizontal,
+ double inValue, double inMaxValue, nsIFrame* aFrame);
+
+ static void DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped);
+
+protected:
+ virtual ~nsNativeThemeCocoa();
+
+ nsIntMargin DirectionAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame);
+ nsIFrame* SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter);
+ CGRect SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
+ nsIFrame* aCurrent, nsIFrame* aRight);
+ bool IsWindowSheet(nsIFrame* aFrame);
+
+ // HITheme drawing routines
+ void DrawFrame(CGContextRef context, HIThemeFrameKind inKind,
+ const HIRect& inBoxRect, bool inReadOnly,
+ mozilla::EventStates inState);
+ void DrawMeter(CGContextRef context, const HIRect& inBoxRect,
+ nsIFrame* aFrame);
+ void DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, nsIFrame* aFrame,
+ const SegmentedControlRenderSettings& aSettings);
+ void DrawTabPanel(CGContextRef context, const HIRect& inBoxRect, nsIFrame* aFrame);
+ void DrawScale(CGContextRef context, const HIRect& inBoxRect,
+ mozilla::EventStates inState, bool inDirection,
+ bool inIsReverse, int32_t inCurrentValue, int32_t inMinValue,
+ int32_t inMaxValue, nsIFrame* aFrame);
+ void DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
+ const HIRect& inBoxRect, bool inSelected,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame, mozilla::EventStates inState);
+ void DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame, float aOriginalHeight);
+ void DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
+ mozilla::EventStates inState, nsIFrame* aFrame,
+ const NSSize& aIconSize, NSString* aImageName,
+ bool aCenterHorizontally);
+ void DrawButton(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, bool inIsDefault,
+ ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame);
+ void DrawDropdown(CGContextRef context, const HIRect& inBoxRect,
+ mozilla::EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame);
+ void DrawSpinButtons(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState, nsIFrame* aFrame);
+ void DrawSpinButton(CGContextRef context, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ mozilla::EventStates inState,
+ nsIFrame* aFrame, uint8_t aWidgetType);
+ void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
+ NSWindow* aWindow);
+ void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame *aFrame);
+ void DrawResizer(CGContextRef cgContext, const HIRect& aRect, nsIFrame *aFrame);
+
+ // Scrollbars
+ void GetScrollbarPressStates(nsIFrame *aFrame,
+ mozilla::EventStates aButtonStates[]);
+ nsIFrame* GetParentScrollbarFrame(nsIFrame *aFrame);
+ bool IsParentScrollbarRolledOver(nsIFrame* aFrame);
+
+private:
+ NSButtonCell* mDisclosureButtonCell;
+ NSButtonCell* mHelpButtonCell;
+ NSButtonCell* mPushButtonCell;
+ NSButtonCell* mRadioButtonCell;
+ NSButtonCell* mCheckboxCell;
+ ContextAwareSearchFieldCell* mSearchFieldCell;
+ NSPopUpButtonCell* mDropdownCell;
+ NSComboBoxCell* mComboBoxCell;
+ NSProgressBarCell* mProgressBarCell;
+ NSLevelIndicatorCell* mMeterBarCell;
+ CellDrawView* mCellDrawView;
+};
+
+#endif // nsNativeThemeCocoa_h_
diff --git a/widget/cocoa/nsNativeThemeCocoa.mm b/widget/cocoa/nsNativeThemeCocoa.mm
new file mode 100644
index 0000000000..056c453f2a
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -0,0 +1,3961 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsNativeThemeCocoa.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "nsChildView.h"
+#include "nsDeviceContext.h"
+#include "nsLayoutUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsNumberControlFrame.h"
+#include "nsRangeFrame.h"
+#include "nsRenderingContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsThemeConstants.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "nsIAtom.h"
+#include "nsNameSpaceManager.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaWindow.h"
+#include "nsNativeThemeColors.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLMeterElement.h"
+#include "nsLookAndFeel.h"
+#include "VibrancyManager.h"
+
+#include "gfxContext.h"
+#include "gfxQuartzSurface.h"
+#include "gfxQuartzNativeDrawing.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::dom::HTMLMeterElement;
+
+#define DRAW_IN_FRAME_DEBUG 0
+#define SCROLLBARS_VISUAL_DEBUG 0
+
+// private Quartz routines needed here
+extern "C" {
+ CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
+ CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
+ typedef CFTypeRef CUIRendererRef;
+ void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options, CFDictionaryRef* result);
+}
+
+// Workaround for NSCell control tint drawing
+// Without this workaround, NSCells are always drawn with the clear control tint
+// as long as they're not attached to an NSControl which is a subview of an active window.
+// XXXmstange Why doesn't Webkit need this?
+@implementation NSCell (ControlTintWorkaround)
+- (int)_realControlTint { return [self controlTint]; }
+@end
+
+// The purpose of this class is to provide objects that can be used when drawing
+// NSCells using drawWithFrame:inView: without causing any harm. The only
+// messages that will be sent to such an object are "isFlipped" and
+// "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
+// on 10.4 (see bug 465069); currentEditor (which isn't even a method of
+// NSView) will be called when drawing search fields, and we only provide it in
+// order to prevent "unrecognized selector" exceptions.
+// There's no need to pass the actual NSView that we're drawing into to
+// drawWithFrame:inView:. What's more, doing so even causes unnecessary
+// invalidations as soon as we draw a focusring!
+@interface CellDrawView : NSView
+
+@end;
+
+@implementation CellDrawView
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (NSText*)currentEditor
+{
+ return nil;
+}
+
+@end
+
+// These two classes don't actually add any behavior over NSButtonCell. Their
+// purpose is to make it easy to distinguish NSCell objects that are used for
+// drawing radio buttons / checkboxes from other cell types.
+// The class names are made up, there are no classes with these names in AppKit.
+// The reason we need them is that calling [cell setButtonType:NSRadioButton]
+// doesn't leave an easy-to-check "marker" on the cell object - there is no
+// -[NSButtonCell buttonType] method.
+@interface RadioButtonCell : NSButtonCell
+@end;
+@implementation RadioButtonCell @end;
+@interface CheckboxCell : NSButtonCell
+@end;
+@implementation CheckboxCell @end;
+
+static void
+DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
+{
+ if ([aCell showsFirstResponder]) {
+ CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSaveGState(cgContext);
+
+ // It's important to set the focus ring style before we enter the
+ // transparency layer so that the transparency layer only contains
+ // the normal button mask without the focus ring, and the conversion
+ // to the focus ring shape happens only when the transparency layer is
+ // ended.
+ NSSetFocusRingStyle(NSFocusRingOnly);
+
+ // We need to draw the whole button into a transparency layer because
+ // many button types are composed of multiple parts, and if these parts
+ // were drawn while the focus ring style was active, each individual part
+ // would produce a focus ring for itself. But we only want one focus ring
+ // for the whole button. The transparency layer is a way to merge the
+ // individual button parts together before the focus ring shape is
+ // calculated.
+ CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
+ [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
+ CGContextEndTransparencyLayer(cgContext);
+
+ CGContextRestoreGState(cgContext);
+ }
+}
+
+static bool
+FocusIsDrawnByDrawWithFrame(NSCell* aCell)
+{
+#if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
+ // When building with the 10.8 SDK or higher, focus rings don't draw as part
+ // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
+ // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
+ // See the NSButtonCell section under
+ // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
+ return false;
+#else
+ if (!nsCocoaFeatures::OnYosemiteOrLater()) {
+ // When building with the 10.7 SDK or lower, focus rings always draw as
+ // part of -[NSCell drawWithFrame:inView:] if the build is run on 10.9 or
+ // lower.
+ return true;
+ }
+
+ // On 10.10, whether the focus ring is drawn as part of
+ // -[NSCell drawWithFrame:inView:] depends on the cell type.
+ // Radio buttons and checkboxes draw their own focus rings, other cell
+ // types need -[NSCell drawFocusRingMaskWithFrame:inView:].
+ return [aCell isKindOfClass:[RadioButtonCell class]] ||
+ [aCell isKindOfClass:[CheckboxCell class]];
+#endif
+}
+
+static void
+DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
+{
+ [aCell drawWithFrame:aWithFrame inView:aInView];
+
+ if (!FocusIsDrawnByDrawWithFrame(aCell)) {
+ DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
+ }
+}
+
+/**
+ * NSProgressBarCell is used to draw progress bars of any size.
+ */
+@interface NSProgressBarCell : NSCell
+{
+ /*All instance variables are private*/
+ double mValue;
+ double mMax;
+ bool mIsIndeterminate;
+ bool mIsHorizontal;
+}
+
+- (void)setValue:(double)value;
+- (double)value;
+- (void)setMax:(double)max;
+- (double)max;
+- (void)setIndeterminate:(bool)aIndeterminate;
+- (bool)isIndeterminate;
+- (void)setHorizontal:(bool)aIsHorizontal;
+- (bool)isHorizontal;
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
+@end
+
+@implementation NSProgressBarCell
+
+- (void)setMax:(double)aMax
+{
+ mMax = aMax;
+}
+
+- (double)max
+{
+ return mMax;
+}
+
+- (void)setValue:(double)aValue
+{
+ mValue = aValue;
+}
+
+- (double)value
+{
+ return mValue;
+}
+
+- (void)setIndeterminate:(bool)aIndeterminate
+{
+ mIsIndeterminate = aIndeterminate;
+}
+
+- (bool)isIndeterminate
+{
+ return mIsIndeterminate;
+}
+
+- (void)setHorizontal:(bool)aIsHorizontal
+{
+ mIsHorizontal = aIsHorizontal;
+}
+
+- (bool)isHorizontal
+{
+ return mIsHorizontal;
+}
+
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
+{
+ CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+
+ HIThemeTrackDrawInfo tdi;
+
+ tdi.version = 0;
+ tdi.min = 0;
+
+ tdi.value = INT32_MAX * (mValue / mMax);
+ tdi.max = INT32_MAX;
+ tdi.bounds = NSRectToCGRect(cellFrame);
+ tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
+ tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive
+ : kThemeTrackActive;
+
+ NSControlSize size = [self controlSize];
+ if (size == NSRegularControlSize) {
+ tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar
+ : kThemeLargeProgressBar;
+ } else {
+ NS_ASSERTION(size == NSSmallControlSize,
+ "We shouldn't have another size than small and regular for the moment");
+ tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
+ : kThemeMediumProgressBar;
+ }
+
+ int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
+ int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
+ tdi.trackInfo.progress.phase = uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) /
+ milliSecondsPerStep);
+
+ HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
+}
+
+@end
+
+@interface ContextAwareSearchFieldCell : NSSearchFieldCell
+{
+ nsIFrame* mContext;
+}
+
+// setContext: stores the searchfield nsIFrame so that it can be consulted
+// during painting. Please reset this by calling setContext:nullptr as soon as
+// you're done with painting because we don't want to keep a dangling pointer.
+- (void)setContext:(nsIFrame*)aContext;
+@end
+
+@implementation ContextAwareSearchFieldCell
+
+- (id)initTextCell:(NSString*)aString
+{
+ if ((self = [super initTextCell:aString])) {
+ mContext = nullptr;
+ }
+ return self;
+}
+
+- (void)setContext:(nsIFrame*)aContext
+{
+ mContext = aContext;
+}
+
+static BOOL IsToolbarStyleContainer(nsIFrame* aFrame)
+{
+ nsIContent* content = aFrame->GetContent();
+ if (!content)
+ return NO;
+
+ if (content->IsAnyOfXULElements(nsGkAtoms::toolbar,
+ nsGkAtoms::toolbox,
+ nsGkAtoms::statusbar))
+ return YES;
+
+ switch (aFrame->StyleDisplay()->mAppearance) {
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+- (BOOL)_isToolbarMode
+{
+ // On 10.7, searchfields have two different styles, depending on whether
+ // the searchfield is on top of of window chrome. This function is called on
+ // 10.7 during drawing in order to determine which style to use.
+ for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) {
+ if (IsToolbarStyleContainer(frame)) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+@end
+
+// Workaround for Bug 542048
+// On 64-bit, NSSearchFieldCells don't draw focus rings.
+#if defined(__x86_64__)
+
+@interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end
+
+@implementation SearchFieldCellWithFocusRing
+
+- (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
+{
+ [super drawWithFrame:rect inView:controlView];
+
+ if (FocusIsDrawnByDrawWithFrame(self)) {
+ // For some reason, -[NSSearchFieldCell drawWithFrame:inView] doesn't draw a
+ // focus ring in 64 bit mode, no matter what SDK is used or what OS X version
+ // we're running on. But if FocusIsDrawnByDrawWithFrame(self), then our
+ // caller expects us to draw a focus ring. So we just do that here.
+ DrawFocusRingForCellIfNeeded(self, rect, controlView);
+ }
+}
+
+- (void)drawFocusRingMaskWithFrame:(NSRect)rect inView:(NSView*)controlView
+{
+ // By default this draws nothing. I don't know why.
+ // We just draw the search field again. It's a great mask shape for its own
+ // focus ring.
+ [super drawWithFrame:rect inView:controlView];
+}
+
+@end
+
+#endif
+
+#define HITHEME_ORIENTATION kHIThemeOrientationNormal
+
+static CGFloat kMaxFocusRingWidth = 0; // initialized by the nsNativeThemeCocoa constructor
+
+// These enums are for indexing into the margin array.
+enum {
+ leopardOSorlater = 0, // 10.6 - 10.9
+ yosemiteOSorlater = 1 // 10.10+
+};
+
+enum {
+ miniControlSize,
+ smallControlSize,
+ regularControlSize
+};
+
+enum {
+ leftMargin,
+ topMargin,
+ rightMargin,
+ bottomMargin
+};
+
+static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
+ if (cocoaControlSize == NSMiniControlSize)
+ return miniControlSize;
+ else if (cocoaControlSize == NSSmallControlSize)
+ return smallControlSize;
+ else
+ return regularControlSize;
+}
+
+static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
+ if (enumControlSize == miniControlSize)
+ return NSMiniControlSize;
+ else if (enumControlSize == smallControlSize)
+ return NSSmallControlSize;
+ else
+ return NSRegularControlSize;
+}
+
+static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize)
+{
+ if (aControlSize == NSRegularControlSize)
+ return @"regular";
+ else if (aControlSize == NSSmallControlSize)
+ return @"small";
+ else
+ return @"mini";
+}
+
+static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
+{
+ if (!marginSet)
+ return;
+
+ static int osIndex = nsCocoaFeatures::OnYosemiteOrLater() ?
+ yosemiteOSorlater : leopardOSorlater;
+ size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
+ const float* buttonMargins = marginSet[osIndex][controlSize];
+ rect->origin.x -= buttonMargins[leftMargin];
+ rect->origin.y -= buttonMargins[bottomMargin];
+ rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
+ rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
+}
+
+static ChildView* ChildViewForFrame(nsIFrame* aFrame)
+{
+ if (!aFrame)
+ return nil;
+
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ if (!widget)
+ return nil;
+
+ NSWindow* window = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ return [window isKindOfClass:[BaseWindow class]] ? [(BaseWindow*)window mainChildView] : nil;
+}
+
+static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
+ nsIWidget** aTopLevelWidget = NULL)
+{
+ if (!aFrame)
+ return nil;
+
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ if (!widget)
+ return nil;
+
+ nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
+ if (aTopLevelWidget)
+ *aTopLevelWidget = topLevelWidget;
+
+ return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
+}
+
+static NSSize
+WindowButtonsSize(nsIFrame* aFrame)
+{
+ NSWindow* window = NativeWindowForFrame(aFrame);
+ if (!window) {
+ // Return fallback values.
+ return NSMakeSize(54, 16);
+ }
+
+ NSRect buttonBox = NSZeroRect;
+ NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
+ if (closeButton) {
+ buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
+ }
+ NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
+ if (minimizeButton) {
+ buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
+ }
+ NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
+ if (zoomButton) {
+ buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
+ }
+ return buttonBox.size;
+}
+
+static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
+{
+ nsIWidget* topLevelWidget = NULL;
+ NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
+ if (!topLevelWidget || !win)
+ return YES;
+
+ // XUL popups, e.g. the toolbar customization popup, can't become key windows,
+ // but controls in these windows should still get the active look.
+ if (topLevelWidget->WindowType() == eWindowType_popup)
+ return YES;
+ if ([win isSheet])
+ return [win isKeyWindow];
+ return [win isMainWindow] && ![win attachedSheet];
+}
+
+// Toolbar controls and content controls respond to different window
+// activeness states.
+static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl)
+{
+ if (aIsToolbarControl)
+ return [NativeWindowForFrame(aFrame) isMainWindow];
+ return FrameIsInActiveWindow(aFrame);
+}
+
+static bool IsInSourceList(nsIFrame* aFrame) {
+ for (nsIFrame* frame = aFrame->GetParent(); frame; frame = frame->GetParent()) {
+ if (frame->StyleDisplay()->mAppearance == NS_THEME_MAC_SOURCE_LIST) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
+
+nsNativeThemeCocoa::nsNativeThemeCocoa()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ kMaxFocusRingWidth = nsCocoaFeatures::OnYosemiteOrLater() ? 7 : 4;
+
+ // provide a local autorelease pool, as this is called during startup
+ // before the main event-loop pool is in place
+ nsAutoreleasePool pool;
+
+ mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
+ [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
+ [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
+ [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
+ [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mPushButtonCell setButtonType:NSMomentaryPushInButton];
+ [mPushButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mRadioButtonCell = [[RadioButtonCell alloc] initTextCell:@""];
+ [mRadioButtonCell setButtonType:NSRadioButton];
+
+ mCheckboxCell = [[CheckboxCell alloc] initTextCell:@""];
+ [mCheckboxCell setButtonType:NSSwitchButton];
+ [mCheckboxCell setAllowsMixedState:YES];
+
+#if defined(__x86_64__)
+ mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
+#else
+ mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""];
+#endif
+ [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
+ [mSearchFieldCell setBezeled:YES];
+ [mSearchFieldCell setEditable:YES];
+ [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
+
+ mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
+
+ mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
+ [mComboBoxCell setBezeled:YES];
+ [mComboBoxCell setEditable:YES];
+ [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
+
+ mProgressBarCell = [[NSProgressBarCell alloc] init];
+
+ mMeterBarCell = [[NSLevelIndicatorCell alloc]
+ initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
+
+ mCellDrawView = [[CellDrawView alloc] init];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsNativeThemeCocoa::~nsNativeThemeCocoa()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mMeterBarCell release];
+ [mProgressBarCell release];
+ [mDisclosureButtonCell release];
+ [mHelpButtonCell release];
+ [mPushButtonCell release];
+ [mRadioButtonCell release];
+ [mCheckboxCell release];
+ [mSearchFieldCell release];
+ [mDropdownCell release];
+ [mComboBoxCell release];
+ [mCellDrawView release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Limit on the area of the target rect (in pixels^2) in
+// DrawCellWithScaling() and DrawButton() and above which we
+// don't draw the object into a bitmap buffer. This is to avoid crashes in
+// [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
+// CGContextDrawImage(), and also to avoid very poor drawing performance in
+// CGContextDrawImage() when it scales the bitmap (particularly if xscale or
+// yscale is less than but near 1 -- e.g. 0.9). This value was determined
+// by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
+// different amounts of RAM.
+#define BITMAP_MAX_AREA 500000
+
+static int
+GetBackingScaleFactorForRendering(CGContextRef cgContext)
+{
+ CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
+ CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
+ float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
+ fabs(transformedUserSpacePixel.size.height));
+ return maxScale > 1.0 ? 2 : 1;
+}
+
+/*
+ * Draw the given NSCell into the given cgContext.
+ *
+ * destRect - the size and position of the resulting control rectangle
+ * controlSize - the NSControlSize which will be given to the NSCell before
+ * asking it to render
+ * naturalSize - The natural dimensions of this control.
+ * If the control rect size is not equal to either of these, a scale
+ * will be applied to the context so that rendering the control at the
+ * natural size will result in it filling the destRect space.
+ * If a control has no natural dimensions in either/both axes, pass 0.0f.
+ * minimumSize - The minimum dimensions of this control.
+ * If the control rect size is less than the minimum for a given axis,
+ * a scale will be applied to the context so that the minimum is used
+ * for drawing. If a control has no minimum dimensions in either/both
+ * axes, pass 0.0f.
+ * marginSet - an array of margins; a multidimensional array of [2][3][4],
+ * with the first dimension being the OS version (Tiger or Leopard),
+ * the second being the control size (mini, small, regular), and the third
+ * being the 4 margin values (left, top, right, bottom).
+ * view - The NSView that we're drawing into. As far as I can tell, it doesn't
+ * matter if this is really the right view; it just has to return YES when
+ * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
+ * mirrorHorizontal - whether to mirror the cell horizontally
+ */
+static void DrawCellWithScaling(NSCell *cell,
+ CGContextRef cgContext,
+ const HIRect& destRect,
+ NSControlSize controlSize,
+ NSSize naturalSize,
+ NSSize minimumSize,
+ const float marginSet[][3][4],
+ NSView* view,
+ BOOL mirrorHorizontal)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
+
+ if (naturalSize.width != 0.0f)
+ drawRect.size.width = naturalSize.width;
+ if (naturalSize.height != 0.0f)
+ drawRect.size.height = naturalSize.height;
+
+ // Keep aspect ratio when scaling if one dimension is free.
+ if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
+ drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
+ if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
+ drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
+
+ // Honor minimum sizes.
+ if (drawRect.size.width < minimumSize.width)
+ drawRect.size.width = minimumSize.width;
+ if (drawRect.size.height < minimumSize.height)
+ drawRect.size.height = minimumSize.height;
+
+ [NSGraphicsContext saveGraphicsState];
+
+ // Only skip the buffer if the area of our cell (in pixels^2) is too large.
+ if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
+ // Inflate the rect Gecko gave us by the margin for the control.
+ InflateControlRect(&drawRect, controlSize, marginSet);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+
+ DrawCellIncludingFocusRing(cell, drawRect, view);
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+ else {
+ float w = ceil(drawRect.size.width);
+ float h = ceil(drawRect.size.height);
+ NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
+
+ // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
+ InflateControlRect(&tmpRect, controlSize, marginSet);
+
+ // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
+ w += kMaxFocusRingWidth * 2.0;
+ h += kMaxFocusRingWidth * 2.0;
+
+ int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGContextRef ctx = CGBitmapContextCreate(NULL,
+ (int) w * backingScaleFactor, (int) h * backingScaleFactor,
+ 8, (int) w * backingScaleFactor * 4,
+ rgb, kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(rgb);
+
+ // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
+ // This is the first flip transform, applied to cgContext.
+ CGContextScaleCTM(cgContext, 1.0f, -1.0f);
+ CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
+ if (mirrorHorizontal) {
+ CGContextScaleCTM(cgContext, -1.0f, 1.0f);
+ CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
+ }
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
+
+ CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
+
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
+
+ // This is the second flip transform, applied to ctx.
+ CGContextScaleCTM(ctx, 1.0f, -1.0f);
+ CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
+
+ DrawCellIncludingFocusRing(cell, tmpRect, view);
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ CGImageRef img = CGBitmapContextCreateImage(ctx);
+
+ // Drop the image into the original destination rectangle, scaling to fit
+ // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
+ // doesn't extend beyond the overflow rect
+ float xscale = destRect.size.width / drawRect.size.width;
+ float yscale = destRect.size.height / drawRect.size.height;
+ float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
+ float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
+ CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
+ destRect.origin.y - scaledFocusRingY,
+ destRect.size.width + scaledFocusRingX * 2,
+ destRect.size.height + scaledFocusRingY * 2),
+ img);
+
+ CGImageRelease(img);
+ CGContextRelease(ctx);
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, destRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+struct CellRenderSettings {
+ // The natural dimensions of the control.
+ // If a control has no natural dimensions in either/both axes, set to 0.0f.
+ NSSize naturalSizes[3];
+
+ // The minimum dimensions of the control.
+ // If a control has no minimum dimensions in either/both axes, set to 0.0f.
+ NSSize minimumSizes[3];
+
+ // A three-dimensional array,
+ // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
+ // the second being the control size (mini, small, regular), and the third
+ // being the 4 margin values (left, top, right, bottom).
+ float margins[2][3][4];
+};
+
+/*
+ * This is a helper method that returns the required NSControlSize given a size
+ * and the size of the three controls plus a tolerance.
+ * size - The width or the height of the element to draw.
+ * sizes - An array with the all the width/height of the element for its
+ * different sizes.
+ * tolerance - The tolerance as passed to DrawCellWithSnapping.
+ * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
+ */
+static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance)
+{
+ for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
+ if (sizes[i] == 0) {
+ continue;
+ }
+
+ CGFloat next = 0;
+ // Find next value.
+ for (uint32_t j = i+1; j <= regularControlSize; ++j) {
+ if (sizes[j] != 0) {
+ next = sizes[j];
+ break;
+ }
+ }
+
+ // If it's the latest value, we pick it.
+ if (next == 0) {
+ return CocoaSizeForEnum(i);
+ }
+
+ if (size <= sizes[i] + tolerance && size < next) {
+ return CocoaSizeForEnum(i);
+ }
+ }
+
+ // If we are here, that means sizes[] was an array with only empty values
+ // or the algorithm above is wrong.
+ // The former can happen but the later would be wrong.
+ NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
+ "We found no control! We shouldn't be there!");
+ return CocoaSizeForEnum(regularControlSize);
+}
+
+/*
+ * Draw the given NSCell into the given cgContext with a nice control size.
+ *
+ * This function is similar to DrawCellWithScaling, but it decides what
+ * control size to use based on the destRect's size.
+ * Scaling is only applied when the difference between the destRect's size
+ * and the next smaller natural size is greater than snapTolerance. Otherwise
+ * it snaps to the next smaller control size without scaling because unscaled
+ * controls look nicer.
+ */
+static void DrawCellWithSnapping(NSCell *cell,
+ CGContextRef cgContext,
+ const HIRect& destRect,
+ const CellRenderSettings settings,
+ float verticalAlignFactor,
+ NSView* view,
+ BOOL mirrorHorizontal,
+ float snapTolerance = 2.0f)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
+ const NSSize *sizes = settings.naturalSizes;
+ const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
+ const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
+ const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
+
+ HIRect drawRect = destRect;
+
+ CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width };
+ NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
+ CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height };
+ NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
+
+ NSControlSize controlSize = NSRegularControlSize;
+ size_t sizeIndex = 0;
+
+ // At some sizes, don't scale but snap.
+ const NSControlSize smallerControlSize =
+ EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
+ controlSizeX : controlSizeY;
+ const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
+ const NSSize size = sizes[smallerControlSizeIndex];
+ float diffWidth = size.width ? rectWidth - size.width : 0.0f;
+ float diffHeight = size.height ? rectHeight - size.height : 0.0f;
+ if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
+ diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
+ // Snap to the smaller control size.
+ controlSize = smallerControlSize;
+ sizeIndex = smallerControlSizeIndex;
+ MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
+
+ // Resize and center the drawRect.
+ if (sizes[sizeIndex].width) {
+ drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
+ drawRect.size.width = sizes[sizeIndex].width;
+ }
+ if (sizes[sizeIndex].height) {
+ drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
+ drawRect.size.height = sizes[sizeIndex].height;
+ }
+ } else {
+ // Use the larger control size.
+ controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
+ controlSizeX : controlSizeY;
+ sizeIndex = EnumSizeForCocoaSize(controlSize);
+ }
+
+ [cell setControlSize:controlSize];
+
+ MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
+ const NSSize minimumSize = settings.minimumSizes[sizeIndex];
+ DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
+ minimumSize, settings.margins, view, mirrorHorizontal);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@interface NSWindow(CoreUIRendererPrivate)
++ (CUIRendererRef)coreUIRenderer;
+@end
+
+static id
+GetAquaAppearance()
+{
+ // We only need NSAppearance on 10.10 and up.
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
+ if (NSAppearanceClass &&
+ [NSAppearanceClass respondsToSelector:@selector(appearanceNamed:)]) {
+ return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
+ withObject:@"NSAppearanceNameAqua"];
+ }
+ }
+ return nil;
+}
+
+@interface NSObject(NSAppearanceCoreUIRendering)
+- (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
+@end
+
+static void
+RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions, bool aSkipAreaCheck = false)
+{
+ id appearance = GetAquaAppearance();
+
+ if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
+ return;
+ }
+
+ if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
+ // Render through NSAppearance on Mac OS 10.10 and up. This will call
+ // CUIDraw with a CoreUI renderer that will give us the correct 10.10
+ // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
+ // renders 10.9-style widgets on 10.10.
+ [appearance _drawInRect:aRect context:cgContext options:aOptions];
+ } else {
+ // 10.9 and below
+ CUIRendererRef renderer = [NSWindow respondsToSelector:@selector(coreUIRenderer)]
+ ? [NSWindow coreUIRenderer] : nil;
+ CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
+ }
+}
+
+static float VerticalAlignFactor(nsIFrame *aFrame)
+{
+ if (!aFrame)
+ return 0.5f; // default: center
+
+ const nsStyleCoord& va = aFrame->StyleDisplay()->mVerticalAlign;
+ uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated)
+ ? va.GetIntValue()
+ : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
+ switch (intval) {
+ case NS_STYLE_VERTICAL_ALIGN_TOP:
+ case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
+ return 0.0f;
+
+ case NS_STYLE_VERTICAL_ALIGN_SUB:
+ case NS_STYLE_VERTICAL_ALIGN_SUPER:
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
+ return 0.5f;
+
+ case NS_STYLE_VERTICAL_ALIGN_BASELINE:
+ case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
+ case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
+ return 1.0f;
+
+ default:
+ NS_NOTREACHED("invalid vertical-align");
+ return 0.5f;
+ }
+}
+
+// These are the sizes that Gecko needs to request to draw if it wants
+// to get a standard-sized Aqua radio button drawn. Note that the rects
+// that draw these are actually a little bigger.
+static const CellRenderSettings radioSettings = {
+ {
+ NSMakeSize(11, 11), // mini
+ NSMakeSize(13, 13), // small
+ NSMakeSize(16, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 1, 1, 1}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 2}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+static const CellRenderSettings checkboxSettings = {
+ {
+ NSMakeSize(11, 11), // mini
+ NSMakeSize(13, 13), // small
+ NSMakeSize(16, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 1, 0, 0}, // mini
+ {0, 1, 0, 1}, // small
+ {0, 1, 0, 1} // regular
+ },
+ { // Yosemite
+ {0, 1, 0, 0}, // mini
+ {0, 1, 0, 1}, // small
+ {0, 1, 0, 1} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
+ const HIRect& inBoxRect, bool inSelected,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
+ NSCellStateValue state = inSelected ? NSOnState : NSOffState;
+
+ // Check if we have an indeterminate checkbox
+ if (inCheckbox && GetIndeterminate(aFrame))
+ state = NSMixedState;
+
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
+ [cell setState:state];
+ [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
+
+ // Ensure that the control is square.
+ float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
+ HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
+ inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
+ length, length);
+
+ DrawCellWithSnapping(cell, cgContext, drawRect,
+ inCheckbox ? checkboxSettings : radioSettings,
+ VerticalAlignFactor(aFrame), mCellDrawView, NO);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings searchFieldSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(32, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame, EventStates inState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ ContextAwareSearchFieldCell* cell = mSearchFieldCell;
+ [cell setContext:aFrame];
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ // NOTE: this could probably use inState
+ [cell setShowsFirstResponder:IsFocused(aFrame)];
+
+ // When using the 10.11 SDK, the default string will be shown if we don't
+ // set the placeholder string.
+ [cell setPlaceholderString:@""];
+
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
+ VerticalAlignFactor(aFrame), mCellDrawView,
+ IsFrameRTL(aFrame));
+
+ [cell setContext:nullptr];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
+static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
+static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
+static NSString* kCheckmarkImage = @"MenuOnState";
+static NSString* kMenuarrowRightImage = @"MenuSubmenu";
+static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
+static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
+static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
+static const CGFloat kMenuIconIndent = 6.0f;
+
+void
+nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
+ EventStates inState, nsIFrame* aFrame,
+ const NSSize& aIconSize, NSString* aImageName,
+ bool aCenterHorizontally)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Adjust size and position of our drawRect.
+ CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - aIconSize.width);
+ CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - aIconSize.height);
+ CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
+ CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
+ CGRect drawRect = CGRectMake(
+ aRect.origin.x + (aCenterHorizontally ? ceil(paddingX / 2) :
+ IsFrameRTL(aFrame) ? paddingEndX : paddingStartX),
+ aRect.origin.y + ceil(paddingY / 2),
+ aIconSize.width, aIconSize.height);
+
+ NSString* state = IsDisabled(aFrame, inState) ? @"disabled" :
+ (CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ? @"pressed" : @"normal");
+
+ NSString* imageName = aImageName;
+ if (!nsCocoaFeatures::OnElCapitanOrLater()) {
+ // Pre-10.11, image names are prefixed with "image."
+ imageName = [@"image." stringByAppendingString:aImageName];
+ }
+
+ RenderWithCoreUI(drawRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
+ imageName, @"imageNameKey",
+ state, @"state",
+ @"image", @"widget",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil]);
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, drawRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
+static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
+
+static const CellRenderSettings pushButtonSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(26, 0), // small
+ NSMakeSize(30, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {4, 0, 4, 1}, // small
+ {5, 0, 5, 2} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {4, 0, 4, 1}, // small
+ {5, 0, 5, 2} // regular
+ }
+ }
+};
+
+// The height at which we start doing square buttons instead of rounded buttons
+// Rounded buttons look bad if drawn at a height greater than 26, so at that point
+// we switch over to doing square buttons which looks fine at any size.
+#define DO_SQUARE_BUTTON_HEIGHT 26
+
+void
+nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame, float aOriginalHeight)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ BOOL isDisabled = IsDisabled(aFrame, inState);
+
+ NSButtonCell* cell = (aWidgetType == NS_THEME_BUTTON) ? mPushButtonCell :
+ (aWidgetType == NS_THEME_MAC_HELP_BUTTON) ? mHelpButtonCell : mDisclosureButtonCell;
+ [cell setEnabled:!isDisabled];
+ [cell setHighlighted:isActive &&
+ inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
+ [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
+
+ if (aWidgetType != NS_THEME_BUTTON) { // Help button or disclosure button.
+ NSSize buttonSize = NSMakeSize(0, 0);
+ if (aWidgetType == NS_THEME_MAC_HELP_BUTTON) {
+ buttonSize = kHelpButtonSize;
+ } else { // Disclosure button.
+ buttonSize = kDisclosureButtonSize;
+ [cell setState:(aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED) ? NSOffState : NSOnState];
+ }
+
+ DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
+ NSZeroSize, buttonSize, NULL, mCellDrawView,
+ false); // Don't mirror icon in RTL.
+ } else {
+ // If the button is tall enough, draw the square button style so that
+ // buttons with non-standard content look good. Otherwise draw normal
+ // rounded aqua buttons.
+ // This comparison is done based on the height that is calculated without
+ // the top, because the snapped height can be affected by the top of the
+ // rect and that may result in different height depending on the top value.
+ if (aOriginalHeight > DO_SQUARE_BUTTON_HEIGHT) {
+ [cell setBezelStyle:NSShadowlessSquareBezelStyle];
+ DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
+ NSZeroSize, NSMakeSize(14, 0), NULL, mCellDrawView,
+ IsFrameRTL(aFrame));
+ } else {
+ [cell setBezelStyle:NSRoundedBezelStyle];
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
+ mCellDrawView, IsFrameRTL(aFrame), 1.0f);
+ }
+ }
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeFrameDrawInfo fdi;
+ fdi.version = 0;
+ fdi.kind = kHIThemeFrameTextFieldSquare;
+ fdi.state = kThemeStateActive;
+ fdi.isFocused = TRUE;
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ HIThemeDrawFrame(&inBoxRect, &fdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
+
+static void
+RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
+ RenderHIThemeControlFunction aFunc, void* aData,
+ BOOL mirrorHorizontally = NO)
+{
+ CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
+ CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
+
+ bool drawDirect;
+ HIRect drawRect = aRect;
+ drawRect.origin = CGPointZero;
+
+ if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
+ savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
+ drawDirect = TRUE;
+ } else {
+ drawDirect = FALSE;
+ }
+
+ // Fall back to no bitmap buffer if the area of our control (in pixels^2)
+ // is too large.
+ if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
+ aFunc(aCGContext, drawRect, aData);
+ } else {
+ // Inflate the buffer to capture focus rings.
+ int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
+ int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
+
+ int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
+ w * backingScaleFactor,
+ h * backingScaleFactor,
+ 8,
+ w * backingScaleFactor * 4,
+ colorSpace,
+ kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(colorSpace);
+
+ CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
+ CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
+
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
+
+ // HITheme always wants to draw into a flipped context, or things
+ // get confused.
+ CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
+ CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
+
+ aFunc(bitmapctx, drawRect, aData);
+
+ CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
+
+ CGAffineTransform ctm = CGContextGetCTM(aCGContext);
+
+ // We need to unflip, so that we can do a DrawImage without getting a flipped image.
+ CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
+ CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
+
+ if (mirrorHorizontally) {
+ CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
+ CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
+ }
+
+ HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
+ CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
+
+ CGContextSetCTM(aCGContext, ctm);
+
+ CGImageRelease(bitmap);
+ CGContextRelease(bitmapctx);
+ }
+
+ CGContextSetCTM(aCGContext, savedCTM);
+}
+
+static void
+RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
+{
+ HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
+ HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
+}
+
+void
+nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, bool inIsDefault,
+ ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ BOOL isDisabled = IsDisabled(aFrame, inState);
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = inValue;
+ bdi.adornment = inAdornment;
+
+ if (isDisabled) {
+ bdi.state = kThemeStateUnavailable;
+ }
+ else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
+ bdi.state = kThemeStatePressed;
+ }
+ else {
+ if (inKind == kThemeArrowButton)
+ bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
+ else if (!isActive && inKind == kThemeListHeaderButton)
+ bdi.state = kThemeStateInactive;
+ else
+ bdi.state = kThemeStateActive;
+ }
+
+ if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
+ bdi.adornment |= kThemeAdornmentFocus;
+
+ if (inIsDefault && !isDisabled &&
+ !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
+ bdi.adornment |= kThemeAdornmentDefault;
+ bdi.animation.time.start = 0;
+ bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
+ }
+
+ HIRect drawFrame = inBoxRect;
+
+ if (inKind == kThemePushButton) {
+ drawFrame.size.height -= 2;
+ if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
+ bdi.kind = kThemePushButtonMini;
+ }
+ else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
+ bdi.kind = kThemePushButtonSmall;
+ drawFrame.origin.y -= 1;
+ drawFrame.origin.x += 1;
+ drawFrame.size.width -= 2;
+ }
+ }
+ else if (inKind == kThemeListHeaderButton) {
+ CGContextClipToRect(cgContext, inBoxRect);
+ // Always remove the top border.
+ drawFrame.origin.y -= 1;
+ drawFrame.size.height += 1;
+ // Remove the left border in LTR mode and the right border in RTL mode.
+ drawFrame.size.width += 1;
+ bool isLast = IsLastTreeHeaderCell(aFrame);
+ if (isLast)
+ drawFrame.size.width += 1; // Also remove the other border.
+ if (!IsFrameRTL(aFrame) || isLast)
+ drawFrame.origin.x -= 1;
+ }
+
+ RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
+ IsFrameRTL(aFrame));
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings dropdownSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {1, 1, 2, 1}, // mini
+ {3, 0, 3, 1}, // small
+ {3, 0, 3, 0} // regular
+ },
+ { // Yosemite
+ {1, 1, 2, 1}, // mini
+ {3, 0, 3, 1}, // small
+ {3, 0, 3, 0} // regular
+ }
+ }
+};
+
+static const CellRenderSettings editableMenulistSettings = {
+ {
+ NSMakeSize(0, 15), // mini
+ NSMakeSize(0, 18), // small
+ NSMakeSize(0, 21) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 2, 2}, // mini
+ {0, 0, 3, 2}, // small
+ {0, 1, 3, 3} // regular
+ },
+ { // Yosemite
+ {0, 0, 2, 2}, // mini
+ {0, 0, 3, 2}, // small
+ {0, 1, 3, 3} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
+
+ BOOL isEditable = (aWidgetType == NS_THEME_MENULIST_TEXTFIELD);
+ NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
+
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
+ [cell setHighlighted:IsOpenButton(aFrame)];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
+
+ const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
+ 0.5f, mCellDrawView, IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings spinnerSettings = {
+ {
+ NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+ NSMakeSize(15, 22), // small
+ NSMakeSize(19, 27) // regular
+ },
+ {
+ NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+ NSMakeSize(15, 22), // small
+ NSMakeSize(19, 27) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = inAdornment;
+
+ if (IsDisabled(aFrame, inState))
+ bdi.state = kThemeStateUnavailable;
+ else
+ bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
+
+ HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
+ ThemeButtonKind inKind,
+ const HIRect& inBoxRect,
+ ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ EventStates inState,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UPBUTTON ||
+ aWidgetType == NS_THEME_SPINNER_DOWNBUTTON);
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = inAdornment;
+
+ if (IsDisabled(aFrame, inState))
+ bdi.state = kThemeStateUnavailable;
+ else
+ bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
+
+ // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
+ // together as a single unit (presumably because when one button is active,
+ // the appearance of both changes (in different ways)). Here we have to paint
+ // both buttons, using clip to hide the one we don't want to paint.
+ HIRect drawRect = inBoxRect;
+ drawRect.size.height *= 2;
+ if (aWidgetType == NS_THEME_SPINNER_DOWNBUTTON) {
+ drawRect.origin.y -= inBoxRect.size.height;
+ }
+
+ // Shift the drawing a little to the left, since cocoa paints with more
+ // blank space around the visual buttons than we'd like:
+ drawRect.origin.x -= 1;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
+ const HIRect& inBoxRect, bool inDisabled,
+ EventStates inState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeFrameDrawInfo fdi;
+ fdi.version = 0;
+ fdi.kind = inKind;
+
+ // We don't ever set an inactive state for this because it doesn't
+ // look right (see other apps).
+ fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
+
+ // for some reason focus rings on listboxes draw incorrectly
+ if (inKind == kHIThemeFrameListBox)
+ fdi.isFocused = 0;
+ else
+ fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
+
+ // HIThemeDrawFrame takes the rect for the content area of the frame, not
+ // the bounding rect for the frame. Here we reduce the size of the rect we
+ // will pass to make it the size of the content.
+ HIRect drawRect = inBoxRect;
+ if (inKind == kHIThemeFrameTextFieldSquare) {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+ drawRect.origin.x += frameOutset;
+ drawRect.origin.y += frameOutset;
+ drawRect.size.width -= frameOutset * 2;
+ drawRect.size.height -= frameOutset * 2;
+ }
+ else if (inKind == kHIThemeFrameListBox) {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
+ drawRect.origin.x += frameOutset;
+ drawRect.origin.y += frameOutset;
+ drawRect.size.width -= frameOutset * 2;
+ drawRect.size.height -= frameOutset * 2;
+ }
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings progressSettings[2][2] = {
+ // Vertical progress bar.
+ {
+ // Determined settings.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(10, 0), // small
+ NSMakeSize(16, 0) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+ },
+ // There is no horizontal margin in regular undetermined size.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(10, 0), // small
+ NSMakeSize(16, 0) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 0, 1, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 0, 1, 0} // regular
+ }
+ }
+ }
+ },
+ // Horizontal progress bar.
+ {
+ // Determined settings.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(0, 10), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+ },
+ // There is no horizontal margin in regular undetermined size.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(0, 10), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {0, 1, 0, 1} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {0, 1, 0, 1} // regular
+ }
+ }
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
+ bool inIsIndeterminate, bool inIsHorizontal,
+ double inValue, double inMaxValue,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSProgressBarCell* cell = mProgressBarCell;
+
+ [cell setValue:inValue];
+ [cell setMax:inMaxValue];
+ [cell setIndeterminate:inIsIndeterminate];
+ [cell setHorizontal:inIsHorizontal];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint]
+ : NSClearControlTint)];
+
+ DrawCellWithSnapping(cell, cgContext, inBoxRect,
+ progressSettings[inIsHorizontal][inIsIndeterminate],
+ VerticalAlignFactor(aFrame), mCellDrawView,
+ IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings meterSetting = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 16), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {1, 1, 1, 1}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ },
+ { // Yosemite
+ {1, 1, 1, 1}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK
+
+ NS_PRECONDITION(aFrame, "aFrame should not be null here!");
+
+ // When using -moz-meterbar on an non meter element, we will not be able to
+ // get all the needed information so we just draw an empty meter.
+ nsIContent* content = aFrame->GetContent();
+ if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
+ DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect,
+ meterSetting, VerticalAlignFactor(aFrame),
+ mCellDrawView, IsFrameRTL(aFrame));
+ return;
+ }
+
+ HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
+ double value = meterElement->Value();
+ double min = meterElement->Min();
+ double max = meterElement->Max();
+
+ NSLevelIndicatorCell* cell = mMeterBarCell;
+
+ [cell setMinValue:min];
+ [cell setMaxValue:max];
+ [cell setDoubleValue:value];
+
+ /**
+ * The way HTML and Cocoa defines the meter/indicator widget are different.
+ * So, we are going to use a trick to get the Cocoa widget showing what we
+ * are expecting: we set the warningValue or criticalValue to the current
+ * value when we want to have the widget to be in the warning or critical
+ * state.
+ */
+ EventStates states = aFrame->GetContent()->AsElement()->State();
+
+ // Reset previously set warning and critical values.
+ [cell setWarningValue:max+1];
+ [cell setCriticalValue:max+1];
+
+ if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
+ [cell setWarningValue:value];
+ } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
+ [cell setCriticalValue:value];
+ }
+
+ HIRect rect = CGRectStandardize(inBoxRect);
+ BOOL vertical = IsVerticalMeter(aFrame);
+
+ CGContextSaveGState(cgContext);
+
+ if (vertical) {
+ /**
+ * Cocoa doesn't provide a vertical meter bar so to show one, we have to
+ * show a rotated horizontal meter bar.
+ * Given that we want to show a vertical meter bar, we assume that the rect
+ * has vertical dimensions but we can't correctly draw a meter widget inside
+ * such a rectangle so we need to inverse width and height (and re-position)
+ * to get a rectangle with horizontal dimensions.
+ * Finally, we want to show a vertical meter so we want to rotate the result
+ * so it is vertical. We do that by changing the context.
+ */
+ CGFloat tmp = rect.size.width;
+ rect.size.width = rect.size.height;
+ rect.size.height = tmp;
+ rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
+ rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
+
+ CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
+ CGContextRotateCTM(cgContext, -M_PI / 2.f);
+ CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
+ }
+
+ DrawCellWithSnapping(cell, cgContext, rect,
+ meterSetting, VerticalAlignFactor(aFrame),
+ mCellDrawView, !vertical && IsFrameRTL(aFrame));
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void
+nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTabPaneDrawInfo tpdi;
+
+ tpdi.version = 1;
+ tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
+ tpdi.direction = kThemeTabNorth;
+ tpdi.size = kHIThemeTabSizeNormal;
+ tpdi.kind = kHIThemeTabKindNormal;
+
+ HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, bool inIsVertical,
+ bool inIsReverse, int32_t inCurrentValue,
+ int32_t inMinValue, int32_t inMaxValue,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTrackDrawInfo tdi;
+
+ tdi.version = 0;
+ tdi.kind = kThemeMediumSlider;
+ tdi.bounds = inBoxRect;
+ tdi.min = inMinValue;
+ tdi.max = inMaxValue;
+ tdi.value = inCurrentValue;
+ tdi.attributes = kThemeTrackShowThumb;
+ if (!inIsVertical)
+ tdi.attributes |= kThemeTrackHorizontal;
+ if (inIsReverse)
+ tdi.attributes |= kThemeTrackRightToLeft;
+ if (inState.HasState(NS_EVENT_STATE_FOCUS))
+ tdi.attributes |= kThemeTrackHasFocus;
+ if (IsDisabled(aFrame, inState))
+ tdi.enableState = kThemeTrackDisabled;
+ else
+ tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
+ tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
+ tdi.trackInfo.slider.pressState = 0;
+
+ HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsIFrame*
+nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter)
+{
+ // Usually a separator is drawn by the segment to the right of the
+ // separator, but pressed and selected segments have higher priority.
+ if (!aBefore || !aAfter)
+ return nullptr;
+ if (IsSelectedButton(aAfter))
+ return aAfter;
+ if (IsSelectedButton(aBefore) || IsPressedButton(aBefore))
+ return aBefore;
+ return aAfter;
+}
+
+CGRect
+nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
+ nsIFrame* aCurrent, nsIFrame* aRight)
+{
+ // A separator between two segments should always be located in the leftmost
+ // pixel column of the segment to the right of the separator, regardless of
+ // who ends up drawing it.
+ // CoreUI draws the separators inside the drawing rect.
+ if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) {
+ // The left button draws the separator, so we need to make room for it.
+ aRect.origin.x += 1;
+ aRect.size.width -= 1;
+ }
+ if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) {
+ // We draw the right separator, so we need to extend the draw rect into the
+ // segment to our right.
+ aRect.size.width += 1;
+ }
+ return aRect;
+}
+
+static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast)
+{
+ if (aIsFirst) {
+ if (aIsLast)
+ return @"kCUISegmentPositionOnly";
+ return @"kCUISegmentPositionFirst";
+ }
+ if (aIsLast)
+ return @"kCUISegmentPositionLast";
+ return @"kCUISegmentPositionMiddle";
+}
+
+struct SegmentedControlRenderSettings {
+ const CGFloat* heights;
+ const NSString* widgetName;
+ const BOOL ignoresPressedWhenSelected;
+ const BOOL isToolbarControl;
+};
+
+static const CGFloat tabHeights[3] = { 17, 20, 23 };
+
+static const SegmentedControlRenderSettings tabRenderSettings = {
+ tabHeights, @"tab", YES, NO
+};
+
+static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 };
+
+static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
+ toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES
+};
+
+void
+nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, nsIFrame* aFrame,
+ const SegmentedControlRenderSettings& aSettings)
+{
+ BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl);
+ BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
+ BOOL isSelected = IsSelectedButton(aFrame);
+ BOOL isPressed = IsPressedButton(aFrame);
+ if (isSelected && aSettings.ignoresPressedWhenSelected) {
+ isPressed = NO;
+ }
+
+ BOOL isRTL = IsFrameRTL(aFrame);
+ nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
+ nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
+ CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right);
+ BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
+ BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
+ NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f);
+
+ RenderWithCoreUI(drawRect, cgContext, [NSDictionary dictionaryWithObjectsAndKeys:
+ aSettings.widgetName, @"widget",
+ (isActive ? @"kCUIPresentationStateActiveKey" : @"kCUIPresentationStateInactive"), @"kCUIPresentationStateKey",
+ ToolbarButtonPosition(!left, !right), @"kCUIPositionKey",
+ [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey",
+ [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey",
+ [NSNumber numberWithBool:isSelected], @"value",
+ (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state",
+ [NSNumber numberWithBool:isFocused], @"focus",
+ CUIControlSizeForCocoaSize(controlSize), @"size",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ @"up", @"direction",
+ nil]);
+}
+
+void
+nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame* aFrame,
+ EventStates aButtonStates[])
+{
+ static nsIContent::AttrValuesArray attributeValues[] = {
+ &nsGkAtoms::scrollbarUpTop,
+ &nsGkAtoms::scrollbarDownTop,
+ &nsGkAtoms::scrollbarUpBottom,
+ &nsGkAtoms::scrollbarDownBottom,
+ nullptr
+ };
+
+ // Get the state of any scrollbar buttons in our child frames
+ for (nsIFrame *childFrame : aFrame->PrincipalChildList()) {
+ nsIContent *childContent = childFrame->GetContent();
+ if (!childContent) continue;
+ int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr,
+ attributeValues, eCaseMatters);
+ if (attrIndex < 0) continue;
+
+ aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
+ }
+}
+
+nsIFrame*
+nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
+{
+ // Walk our parents to find a scrollbar frame
+ nsIFrame *scrollbarFrame = aFrame;
+ do {
+ if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break;
+ } while ((scrollbarFrame = scrollbarFrame->GetParent()));
+
+ // We return null if we can't find a parent scrollbar frame
+ return scrollbarFrame;
+}
+
+static bool
+ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
+{
+ if (![aWindow isKindOfClass:[ToolbarWindow class]])
+ return false;
+
+ ToolbarWindow* win = (ToolbarWindow*)aWindow;
+ float unifiedToolbarHeight = [win unifiedToolbarHeight];
+ return inBoxRect.origin.x == 0 &&
+ inBoxRect.size.width >= [win frame].size.width &&
+ CGRectGetMaxY(inBoxRect) <= unifiedToolbarHeight;
+}
+
+// By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
+// upper corners. Depending on the context type, it fills the background in
+// the corners with black or leaves it transparent. Unfortunately, this corner
+// rounding interacts poorly with the window corner masking we apply during
+// titlebar drawing and results in small remnants of the corner background
+// appearing at the rounded edge.
+// So we draw square corners.
+static void
+DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
+{
+ // We extend the draw rect horizontally and clip away the rounded corners.
+ const CGFloat extendHorizontal = 10;
+ CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
+ CGContextSaveGState(aContext);
+ CGContextClipToRect(aContext, aRect);
+
+ RenderWithCoreUI(drawRect, aContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIWidgetWindowFrame", @"widget",
+ @"regularwin", @"windowtype",
+ (aIsMain ? @"normal" : @"inactive"), @"state",
+ [NSNumber numberWithDouble:aUnifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
+ [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
+ [NSNumber numberWithBool:aIsFlipped], @"is.flipped",
+ nil]);
+
+ CGContextRestoreGState(aContext);
+}
+
+void
+nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
+ NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ CGFloat unifiedHeight = std::max([(ToolbarWindow*)aWindow unifiedToolbarHeight],
+ inBoxRect.size.height);
+ BOOL isMain = [aWindow isMainWindow];
+ CGFloat titlebarHeight = unifiedHeight - inBoxRect.size.height;
+ CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
+ inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
+ DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, unifiedHeight, isMain, YES);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame *aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (inBoxRect.size.height < 2.0f)
+ return;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ // kCUIWidgetWindowFrame draws a complete window frame with both title bar
+ // and bottom bar. We only want the bottom bar, so we extend the draw rect
+ // upwards to make space for the title bar, and then we clip it away.
+ CGRect drawRect = inBoxRect;
+ const int extendUpwards = 40;
+ drawRect.origin.y -= extendUpwards;
+ drawRect.size.height += extendUpwards;
+ RenderWithCoreUI(drawRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIWidgetWindowFrame", @"widget",
+ @"regularwin", @"windowtype",
+ (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state",
+ [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey",
+ [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil]);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
+{
+ CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
+ DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain, aIsFlipped);
+}
+
+static void
+RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
+{
+ HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
+ HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
+}
+
+void
+nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
+ nsIFrame *aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeGrowBoxDrawInfo drawInfo;
+ drawInfo.version = 0;
+ drawInfo.state = kThemeStateActive;
+ drawInfo.kind = kHIThemeGrowBoxKindNormal;
+ drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
+ drawInfo.size = kHIThemeGrowBoxSizeNormal;
+
+ RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
+ IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static void
+DrawVibrancyBackground(CGContextRef cgContext, CGRect inBoxRect,
+ nsIFrame* aFrame, nsITheme::ThemeGeometryType aThemeGeometryType,
+ int aCornerRadiusIfOpaque = 0)
+{
+ ChildView* childView = ChildViewForFrame(aFrame);
+ if (childView) {
+ NSRect rect = NSRectFromCGRect(inBoxRect);
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+ [NSGraphicsContext saveGraphicsState];
+
+ NSColor* fillColor = [childView vibrancyFillColorForThemeGeometryType:aThemeGeometryType];
+ if ([fillColor alphaComponent] == 1.0 && aCornerRadiusIfOpaque > 0) {
+ // The fillColor being opaque means that the system-wide pref "reduce
+ // transparency" is set. In that scenario, we still go through all the
+ // vibrancy rendering paths (VibrancyManager::SystemSupportsVibrancy()
+ // will still return true), but the result just won't look "vibrant".
+ // However, there's one unfortunate change of behavior that this pref
+ // has: It stops the window server from applying window masks. We use
+ // a window mask to get rounded corners on menus. So since the mask
+ // doesn't work in "reduce vibrancy" mode, we need to do our own rounded
+ // corner clipping here.
+ [[NSBezierPath bezierPathWithRoundedRect:rect
+ xRadius:aCornerRadiusIfOpaque
+ yRadius:aCornerRadiusIfOpaque] addClip];
+ }
+
+ [fillColor set];
+ NSRectFill(rect);
+
+ [NSGraphicsContext restoreGraphicsState];
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+}
+
+bool
+nsNativeThemeCocoa::IsParentScrollbarRolledOver(nsIFrame* aFrame)
+{
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ return nsLookAndFeel::UseOverlayScrollbars()
+ ? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
+ : GetContentState(scrollbarFrame, NS_THEME_NONE).HasState(NS_EVENT_STATE_HOVER);
+}
+
+static bool
+IsHiDPIContext(nsPresContext* aContext)
+{
+ return nsPresContext::AppUnitsPerCSSPixel() >=
+ 2 * aContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+}
+
+static bool
+IsScrollbarWidthThin(nsIFrame* aFrame)
+{
+ return aFrame->StyleUserInterface()->mScrollbarWidth == StyleScrollbarWidth::Thin;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ // setup to draw into the correct port
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ gfx::Rect nativeDirtyRect = NSRectToRect(aDirtyRect, p2a);
+ gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
+ nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
+ float nativeWidgetHeight = round(nativeWidgetRect.Height());
+ nativeWidgetRect.Round();
+ if (nativeWidgetRect.IsEmpty())
+ return NS_OK; // Don't attempt to draw invisible widgets.
+
+ AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
+
+ bool hidpi = IsHiDPIContext(aFrame->PresContext());
+ if (hidpi) {
+ // Use high-resolution drawing.
+ nativeWidgetRect.Scale(0.5f);
+ nativeWidgetHeight *= 0.5f;
+ nativeDirtyRect.Scale(0.5f);
+ aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(2.0f, 2.0f));
+ }
+
+ gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, nativeDirtyRect);
+
+ CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
+ if (cgContext == nullptr) {
+ // The Quartz surface handles 0x0 surfaces by internally
+ // making all operations no-ops; there's no cgcontext created for them.
+ // Unfortunately, this means that callers that want to render
+ // directly to the CGContext need to be aware of this quirk.
+ return NS_OK;
+ }
+
+ if (hidpi) {
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(2, 2));
+ }
+
+#if 0
+ if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
+ fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
+ aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
+ fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
+ mat._11, mat._12, mat._21, mat._22, mat._31, mat._32);
+ fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
+ mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
+ CGAffineTransform mm = CGContextGetCTM(cgContext);
+ fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
+ mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
+ }
+#endif
+
+ CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
+ nativeWidgetRect.Width(), nativeWidgetRect.Height());
+
+#if 0
+ fprintf(stderr, " --> macRect %f %f %f %f\n",
+ macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
+ CGRect bounds = CGContextGetClipBoundingBox(cgContext);
+ fprintf(stderr, " --> clip bounds: %f %f %f %f\n",
+ bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
+
+ //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
+ //CGContextFillRect(cgContext, bounds);
+#endif
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ switch (aWidgetType) {
+ case NS_THEME_DIALOG: {
+ if (IsWindowSheet(aFrame)) {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ HIThemeSetFill(kThemeBrushSheetBackgroundTransparent, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextFillRect(cgContext, macRect);
+ }
+ } else {
+ HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextFillRect(cgContext, macRect);
+ }
+
+ }
+ break;
+
+ case NS_THEME_MENUPOPUP:
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ DrawVibrancyBackground(cgContext, macRect, aFrame, eThemeGeometryTypeMenu, 4);
+ } else {
+ HIThemeMenuDrawInfo mdi;
+ memset(&mdi, 0, sizeof(mdi));
+ mdi.version = 0;
+ mdi.menuType = IsDisabled(aFrame, eventState) ?
+ static_cast<ThemeMenuType>(kThemeMenuTypeInactive) :
+ static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
+
+ bool isLeftOfParent = false;
+ if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
+ mdi.menuType = kThemeMenuTypeHierarchical;
+ }
+
+ // The rounded corners draw outside the frame.
+ CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
+ macRect.size.width, macRect.size.height - 8);
+ HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_MENUARROW: {
+ bool isRTL = IsFrameRTL(aFrame);
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kMenuarrowSize,
+ isRTL ? kMenuarrowLeftImage : kMenuarrowRightImage, true);
+ }
+ break;
+
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM: {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ bool isDisabled = IsDisabled(aFrame, eventState);
+ bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
+ HIThemeMenuItemDrawInfo drawInfo;
+ memset(&drawInfo, 0, sizeof(drawInfo));
+ drawInfo.version = 0;
+ drawInfo.itemType = kThemeMenuItemPlain;
+ drawInfo.state = (isDisabled ?
+ static_cast<ThemeMenuState>(kThemeMenuDisabled) :
+ isSelected ?
+ static_cast<ThemeMenuState>(kThemeMenuSelected) :
+ static_cast<ThemeMenuState>(kThemeMenuActive));
+
+ // XXX pass in the menu rect instead of always using the item rect
+ HIRect ignored;
+ HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
+ }
+
+ if (aWidgetType == NS_THEME_CHECKMENUITEM) {
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kCheckmarkSize, kCheckmarkImage, false);
+ }
+ }
+ break;
+
+ case NS_THEME_MENUSEPARATOR: {
+ // Workaround for visual artifacts issues with
+ // HIThemeDrawMenuSeparator on macOS Big Sur.
+ if (nsCocoaFeatures::OnBigSurOrLater()) {
+ CGRect separatorRect = macRect;
+ separatorRect.size.height = 1;
+ separatorRect.size.width -= 42;
+ separatorRect.origin.x += 21;
+ // Use a gray color similar to the native separator
+ CGContextSetRGBFillColor(cgContext, 0.816, 0.816, 0.816, 1.0);
+ CGContextFillRect(cgContext, separatorRect);
+ }
+ else
+ {
+ ThemeMenuState menuState;
+ if (IsDisabled(aFrame, eventState)) {
+ menuState = kThemeMenuDisabled;
+ }
+ else {
+ menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
+ kThemeMenuSelected : kThemeMenuActive;
+ }
+ HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
+ HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
+ }
+ }
+ break;
+
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kMenuScrollArrowSize,
+ aWidgetType == NS_THEME_BUTTON_ARROW_UP ?
+ kMenuUpScrollArrowImage : kMenuDownScrollArrowImage, true);
+ break;
+
+ case NS_THEME_TOOLTIP:
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ DrawVibrancyBackground(cgContext, macRect, aFrame, ThemeGeometryTypeForWidget(aFrame, aWidgetType));
+ } else {
+ CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
+ CGContextFillRect(cgContext, macRect);
+ }
+ break;
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO: {
+ bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
+ DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
+ eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_BUTTON:
+ if (IsDefaultButton(aFrame)) {
+ // Check whether the default button is in a document that does not
+ // match the :-moz-window-inactive pseudoclass. This activeness check
+ // is different from the other "active window" checks in this file
+ // because we absolutely need the button's default button appearance to
+ // be in sync with its text color, and the text color is changed by
+ // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
+ // default buttons in active windows have blue background and white
+ // text, and default buttons in inactive windows have white background
+ // and black text.)
+ EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ bool isInActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+ if (!IsDisabled(aFrame, eventState) && isInActiveWindow &&
+ !QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
+ NS_WARNING("Unable to animate button!");
+ }
+ DrawButton(cgContext, kThemePushButton, macRect, isInActiveWindow,
+ kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
+ } else if (IsButtonTypeMenu(aFrame)) {
+ DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
+ } else {
+ DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame,
+ nativeWidgetHeight);
+ }
+ break;
+
+ case NS_THEME_FOCUS_OUTLINE:
+ DrawFocusOutline(cgContext, macRect, eventState, aWidgetType, aFrame);
+ break;
+
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame,
+ nativeWidgetHeight);
+ break;
+
+ case NS_THEME_BUTTON_BEVEL:
+ DrawButton(cgContext, kThemeMediumBevelButton, macRect,
+ IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
+ eventState, aFrame);
+ break;
+
+ case NS_THEME_SPINNER: {
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsHTMLElement()) {
+ // In HTML the theming for the spin buttons is drawn individually into
+ // their own backgrounds instead of being drawn into the background of
+ // their spinner parent as it is for XUL.
+ break;
+ }
+ ThemeDrawState state = kThemeStateActive;
+ if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("up"), eCaseMatters)) {
+ state = kThemeStatePressedUp;
+ }
+ else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("down"), eCaseMatters)) {
+ state = kThemeStatePressedDown;
+ }
+
+ DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
+ kThemeAdornmentNone, eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON: {
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame) {
+ ThemeDrawState state = kThemeStateActive;
+ if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
+ state = kThemeStatePressedUp;
+ } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
+ state = kThemeStatePressedDown;
+ }
+ DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state,
+ kThemeAdornmentNone, eventState, aFrame, aWidgetType);
+ }
+ }
+ break;
+
+ case NS_THEME_TOOLBARBUTTON:
+ DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
+ break;
+
+ case NS_THEME_SEPARATOR: {
+ HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
+ HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_TOOLBAR: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ if (ToolbarCanBeUnified(cgContext, macRect, win)) {
+ DrawUnifiedToolbar(cgContext, macRect, win);
+ break;
+ }
+ BOOL isMain = [win isMainWindow];
+ CGRect drawRect = macRect;
+
+ // top border
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
+
+ // background
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = macRect.size.height - 2.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain);
+
+ // bottom border
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain);
+ }
+ break;
+
+ case NS_THEME_WINDOW_TITLEBAR: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ BOOL isMain = [win isMainWindow];
+ float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]] ?
+ [(ToolbarWindow*)win unifiedToolbarHeight] : macRect.size.height;
+ DrawNativeTitlebar(cgContext, macRect, unifiedToolbarHeight, isMain, YES);
+ }
+ break;
+
+ case NS_THEME_STATUSBAR:
+ DrawStatusBar(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
+ break;
+
+ case NS_THEME_MENULIST_BUTTON:
+ DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn,
+ kThemeAdornmentArrowDownArrow, eventState, aFrame);
+ break;
+
+ case NS_THEME_GROUPBOX: {
+ HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
+ HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
+ break;
+ }
+
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_NUMBER_INPUT:
+ // HIThemeSetFill is not available on 10.3
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+
+ // XUL textboxes set the native appearance on the containing box, while
+ // concrete focus is set on the html:input element within it. We can
+ // though, check the focused attribute of xul textboxes in this case.
+ // On Mac, focus rings are always shown for textboxes, so we do not need
+ // to check the window's focus ring state here
+ if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) {
+ eventState |= NS_EVENT_STATE_FOCUS;
+ }
+
+ DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
+ IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
+ break;
+
+ case NS_THEME_SEARCHFIELD:
+ DrawSearchField(cgContext, macRect, aFrame, eventState);
+ break;
+
+ case NS_THEME_PROGRESSBAR:
+ {
+ double value = GetProgressValue(aFrame);
+ double maxValue = GetProgressMaxValue(aFrame);
+ // Don't request repaints for scrollbars at 100% because those don't animate.
+ if (value < maxValue) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("Unable to animate progressbar!");
+ }
+ }
+ DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
+ !IsVerticalProgress(aFrame),
+ value, maxValue, aFrame);
+ break;
+ }
+
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
+ false, GetProgressValue(aFrame),
+ GetProgressMaxValue(aFrame), aFrame);
+ break;
+
+ case NS_THEME_METERBAR:
+ DrawMeter(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_METERCHUNK:
+ // Do nothing: progress and meter bars cases will draw chunks.
+ break;
+
+ case NS_THEME_TREETWISTY:
+ DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
+ kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
+ break;
+
+ case NS_THEME_TREETWISTYOPEN:
+ DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
+ kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
+ break;
+
+ case NS_THEME_TREEHEADERCELL: {
+ TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
+ DrawButton(cgContext, kThemeListHeaderButton, macRect, false,
+ sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
+ sortDirection == eTreeSortDirection_Ascending ?
+ kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_TREEITEM:
+ case NS_THEME_TREEVIEW:
+ // HIThemeSetFill is not available on 10.3
+ // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+ break;
+
+ case NS_THEME_TREEHEADER:
+ // do nothing, taken care of by individual header cells
+ case NS_THEME_TREEHEADERSORTARROW:
+ // do nothing, taken care of by treeview header
+ case NS_THEME_TREELINE:
+ // do nothing, these lines don't exist on macos
+ break;
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL: {
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
+ if (!maxpos)
+ maxpos = 100;
+
+ bool reverse = aFrame->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ NS_LITERAL_STRING("reverse"), eCaseMatters);
+ DrawScale(cgContext, macRect, eventState,
+ (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
+ curpos, minpos, maxpos, aFrame);
+ }
+ break;
+
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ // do nothing, drawn by scale
+ break;
+
+ case NS_THEME_RANGE: {
+ nsRangeFrame *rangeFrame = do_QueryFrame(aFrame);
+ if (!rangeFrame) {
+ break;
+ }
+ // DrawScale requires integer min, max and value. This is purely for
+ // drawing, so we normalize to a range 0-1000 here.
+ int32_t value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
+ int32_t min = 0;
+ int32_t max = 1000;
+ bool isVertical = !IsRangeHorizontal(aFrame);
+ bool reverseDir = isVertical || rangeFrame->IsRightToLeft();
+ DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
+ value, min, max, aFrame);
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: {
+ BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
+ BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL);
+ BOOL isRolledOver = IsParentScrollbarRolledOver(aFrame);
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ bool isSmall = (scrollbarFrame && scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ if (isOverlay && !isRolledOver) {
+ if (isHorizontal) {
+ macRect.origin.y += 4;
+ macRect.size.height -= 4;
+ } else {
+ if (aFrame->StyleVisibility()->mDirection !=
+ NS_STYLE_DIRECTION_RTL) {
+ macRect.origin.x += 4;
+ }
+ macRect.size.width -= 4;
+ }
+ }
+ const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
+ NSMutableDictionary* options = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
+ (isSmall ? @"small" : @"regular"), @"size",
+ (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+ (isOverlay && isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
+ [NSNumber numberWithBool:YES], @"indiconly",
+ [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil];
+ if (isRolledOver) {
+ [options setObject:@"rollover" forKey:@"state"];
+ }
+ RenderWithCoreUI(macRect, cgContext, options, true);
+ }
+ break;
+
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+#if SCROLLBARS_VISUAL_DEBUG
+ CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
+ CGContextFillRect(cgContext, macRect);
+#endif
+ break;
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+#if SCROLLBARS_VISUAL_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
+ CGContextFillRect(cgContext, macRect);
+#endif
+ break;
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL: {
+ BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
+ if (!isOverlay || IsParentScrollbarRolledOver(aFrame)) {
+ BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL);
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ bool isSmall = (scrollbarFrame && scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
+ RenderWithCoreUI(macRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
+ (isSmall ? @"small" : @"regular"), @"size",
+ (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+ (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
+ [NSNumber numberWithBool:YES], @"noindicator",
+ [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil],
+ true);
+ }
+ }
+ break;
+
+ case NS_THEME_TEXTFIELD_MULTILINE: {
+ // we have to draw this by hand because there is no HITheme value for it
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+
+ CGContextFillRect(cgContext, macRect);
+
+ // #737373 for the top border, #999999 for the rest.
+ float x = macRect.origin.x, y = macRect.origin.y;
+ float w = macRect.size.width, h = macRect.size.height;
+ CGContextSetRGBFillColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ CGContextSetRGBFillColor(cgContext, 0.6, 0.6, 0.6, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
+
+ // draw a focus ring
+ if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+ CGContextSaveGState(cgContext);
+ NSSetFocusRingStyle(NSFocusRingOnly);
+ NSRectFill(NSRectFromCGRect(macRect));
+ CGContextRestoreGState(cgContext);
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+ }
+ break;
+
+ case NS_THEME_LISTBOX: {
+ // We have to draw this by hand because kHIThemeFrameListBox drawing
+ // is buggy on 10.5, see bug 579259.
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+
+ // #8E8E8E for the top border, #BEBEBE for the rest.
+ float x = macRect.origin.x, y = macRect.origin.y;
+ float w = macRect.size.width, h = macRect.size.height;
+ CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
+ }
+ break;
+
+ case NS_THEME_MAC_SOURCE_LIST: {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ CGGradientRef backgroundGradient;
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGFloat activeGradientColors[8] = { 0.9137, 0.9294, 0.9490, 1.0,
+ 0.8196, 0.8471, 0.8784, 1.0 };
+ CGFloat inactiveGradientColors[8] = { 0.9686, 0.9686, 0.9686, 1.0,
+ 0.9216, 0.9216, 0.9216, 1.0 };
+ CGPoint start = macRect.origin;
+ CGPoint end = CGPointMake(macRect.origin.x,
+ macRect.origin.y + macRect.size.height);
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ backgroundGradient =
+ CGGradientCreateWithColorComponents(rgb, isActive ? activeGradientColors
+ : inactiveGradientColors, NULL, 2);
+ CGContextDrawLinearGradient(cgContext, backgroundGradient, start, end, 0);
+ CGGradientRelease(backgroundGradient);
+ CGColorSpaceRelease(rgb);
+ }
+ }
+ break;
+
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION: {
+ // If we're in XUL tree, we need to rely on the source list's clear
+ // background display item. If we cleared the background behind the
+ // selections, the source list would not pick up the right font
+ // smoothing background. So, to simplify a bit, we only support vibrancy
+ // if we're in a source list.
+ if (VibrancyManager::SystemSupportsVibrancy() && IsInSourceList(aFrame)) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ BOOL isActiveSelection =
+ aWidgetType == NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION;
+ RenderWithCoreUI(macRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:isActiveSelection], @"focus",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ @"kCUIVariantGradientSideBarSelection", @"kCUIVariantKey",
+ (FrameIsInActiveWindow(aFrame) ? @"normal" : @"inactive"), @"state",
+ @"gradient", @"widget",
+ nil]);
+ }
+ }
+ break;
+
+ case NS_THEME_TAB:
+ DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings);
+ break;
+
+ case NS_THEME_TABPANELS:
+ DrawTabPanel(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_RESIZER:
+ DrawResizer(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK: {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ break;
+ }
+ }
+
+ if (hidpi) {
+ // Reset the base CTM.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsIntMargin
+nsNativeThemeCocoa::DirectionAwareMargin(const nsIntMargin& aMargin,
+ nsIFrame* aFrame)
+{
+ // Assuming aMargin was originally specified for a horizontal LTR context,
+ // reinterpret the values as logical, and then map to physical coords
+ // according to aFrame's actual writing mode.
+ WritingMode wm = aFrame->GetWritingMode();
+ nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom,
+ aMargin.left).GetPhysicalMargin(wm);
+ return nsIntMargin(m.top, m.right, m.bottom, m.left);
+}
+
+static const nsIntMargin kAquaDropdownBorder(1, 22, 2, 5);
+static const nsIntMargin kAquaComboboxBorder(3, 20, 3, 4);
+static const nsIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aResult->SizeTo(0, 0, 0, 0);
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ {
+ if (IsButtonTypeMenu(aFrame)) {
+ *aResult = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ } else {
+ *aResult = DirectionAwareMargin(nsIntMargin(1, 7, 3, 7), aFrame);
+ }
+ break;
+ }
+
+ case NS_THEME_TOOLBARBUTTON:
+ {
+ *aResult = DirectionAwareMargin(nsIntMargin(1, 4, 1, 4), aFrame);
+ break;
+ }
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
+ // assume a border width of 2px.
+ aResult->SizeTo(2, 2, 2, 2);
+ break;
+ }
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ *aResult = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ break;
+
+ case NS_THEME_MENULIST_TEXTFIELD:
+ *aResult = DirectionAwareMargin(kAquaComboboxBorder, aFrame);
+ break;
+
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+
+ SInt32 textPadding = 0;
+ ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
+
+ frameOutset += textPadding;
+
+ aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ aResult->SizeTo(1, 1, 1, 1);
+ break;
+
+ case NS_THEME_SEARCHFIELD:
+ *aResult = DirectionAwareMargin(kAquaSearchfieldBorder, aFrame);
+ break;
+
+ case NS_THEME_LISTBOX:
+ {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
+ aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ {
+ bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL);
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ if (!nsCocoaFeatures::OnYosemiteOrLater()) {
+ // Pre-10.10, we have to center the thumb rect in the middle of the
+ // scrollbar. Starting with 10.10, the expected rect for thumb
+ // rendering is the full width of the scrollbar.
+ if (isHorizontal) {
+ aResult->top = 2;
+ aResult->bottom = 1;
+ } else {
+ aResult->left = 2;
+ aResult->right = 1;
+ }
+ }
+ // Leave a bit of space at the start and the end on all OS X versions.
+ if (isHorizontal) {
+ aResult->left = 1;
+ aResult->right = 1;
+ } else {
+ aResult->top = 1;
+ aResult->bottom = 1;
+ }
+ }
+
+ break;
+ }
+
+ case NS_THEME_STATUSBAR:
+ aResult->SizeTo(1, 0, 0, 0);
+ break;
+ }
+
+ if (IsHiDPIContext(aFrame->PresContext())) {
+ *aResult = *aResult + *aResult; // doubled
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Return false here to indicate that CSS padding values should be used. There is
+// no reason to make a distinction between padding and border values, just specify
+// whatever values you want in GetWidgetBorder and only use this to return true
+// if you want to override CSS padding values.
+bool
+nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ // We don't want CSS padding being used for certain widgets.
+ // See bug 381639 for an example of why.
+ switch (aWidgetType) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ }
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsRect* aOverflowRect)
+{
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_TAB:
+ {
+ // We assume that the above widgets can draw a focus ring that will be less than
+ // or equal to 4 pixels thick.
+ nsIntMargin extraSize = nsIntMargin(kMaxFocusRingWidth,
+ kMaxFocusRingWidth,
+ kMaxFocusRingWidth,
+ kMaxFocusRingWidth);
+ nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
+ NSIntPixelsToAppUnits(extraSize.right, p2a),
+ NSIntPixelsToAppUnits(extraSize.bottom, p2a),
+ NSIntPixelsToAppUnits(extraSize.left, p2a));
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+ case NS_THEME_PROGRESSBAR:
+ {
+ // Progress bars draw a 2 pixel white shadow under their progress indicators
+ nsMargin m(0, 0, NSIntPixelsToAppUnits(2, p2a), 0);
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+ case NS_THEME_FOCUS_OUTLINE:
+ {
+ aOverflowRect->Inflate(NSIntPixelsToAppUnits(2, p2a));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static const int32_t kRegularScrollbarThumbMinSize = 26;
+static const int32_t kSmallScrollbarThumbMinSize = 26;
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aResult->SizeTo(0,0);
+ *aIsOverridable = true;
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ {
+ aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
+ pushButtonSettings.naturalSizes[miniControlSize].height);
+ break;
+ }
+
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ {
+ aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MENUARROW:
+ {
+ aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ {
+ aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_HELP_BUTTON:
+ {
+ aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_TOOLBARBUTTON:
+ {
+ aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
+ break;
+ }
+
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ {
+ SInt32 buttonHeight = 0, buttonWidth = 0;
+ if (aFrame->GetContent()->IsXULElement()) {
+ ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
+ ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
+ } else {
+ NSSize size =
+ spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
+ buttonWidth = size.width;
+ buttonHeight = size.height;
+ if (aWidgetType != NS_THEME_SPINNER) {
+ // the buttons are half the height of the spinner
+ buttonHeight /= 2;
+ }
+ }
+ aResult->SizeTo(buttonWidth, buttonHeight);
+ *aIsOverridable = true;
+ break;
+ }
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ {
+ SInt32 popupHeight = 0;
+ ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
+ aResult->SizeTo(0, popupHeight);
+ break;
+ }
+
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ {
+ // at minimum, we should be tall enough for 9pt text.
+ // I'm using hardcoded values here because the appearance manager
+ // values for the frame size are incorrect.
+ aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
+ break;
+ }
+
+ case NS_THEME_WINDOW_BUTTON_BOX: {
+ NSSize size = WindowButtonsSize(aFrame);
+ aResult->SizeTo(size.width, size.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_FULLSCREEN_BUTTON: {
+ if ([NativeWindowForFrame(aFrame) respondsToSelector:@selector(toggleFullScreen:)] &&
+ !nsCocoaFeatures::OnYosemiteOrLater()) {
+ // This value is hardcoded because it's needed before we can measure the
+ // position and size of the fullscreen button.
+ aResult->SizeTo(16, 17);
+ }
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_PROGRESSBAR:
+ {
+ SInt32 barHeight = 0;
+ ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
+ aResult->SizeTo(0, barHeight);
+ break;
+ }
+
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ {
+ SInt32 twistyHeight = 0, twistyWidth = 0;
+ ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
+ ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
+ aResult->SizeTo(twistyWidth, twistyHeight);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ {
+ SInt32 headerHeight = 0;
+ ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
+ aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
+ break;
+ }
+
+ case NS_THEME_TAB:
+ {
+ aResult->SizeTo(0, tabHeights[miniControlSize]);
+ break;
+ }
+
+ case NS_THEME_RANGE:
+ {
+ // The Mac Appearance Manager API (the old API we're currently using)
+ // doesn't define constants to obtain a minimum size for sliders. We use
+ // the "thickness" of a slider that has default dimensions for both the
+ // minimum width and height to get something sane and so that paint
+ // invalidation works.
+ SInt32 size = 0;
+ if (IsRangeHorizontal(aFrame)) {
+ ::GetThemeMetric(kThemeMetricHSliderHeight, &size);
+ } else {
+ ::GetThemeMetric(kThemeMetricVSliderWidth, &size);
+ }
+ aResult->SizeTo(size, size);
+ *aIsOverridable = true;
+ break;
+ }
+
+ case NS_THEME_RANGE_THUMB:
+ {
+ SInt32 width = 0;
+ SInt32 height = 0;
+ ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
+ ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
+ aResult->SizeTo(width, height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ {
+ SInt32 scaleHeight = 0;
+ ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
+ aResult->SizeTo(scaleHeight, scaleHeight);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCALE_VERTICAL:
+ {
+ SInt32 scaleWidth = 0;
+ ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
+ aResult->SizeTo(scaleWidth, scaleWidth);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ {
+ // Find our parent scrollbar frame in order to find out whether we're in
+ // a small or a large scrollbar.
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL);
+ int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
+ minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ {
+ *aIsOverridable = false;
+
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (scrollbarFrame &&
+ scrollbarFrame->StyleDisplay()->mAppearance ==
+ NS_THEME_SCROLLBAR_SMALL) {
+ aResult->SizeTo(14, 14);
+ }
+ else {
+ aResult->SizeTo(16, 16);
+ }
+ if (IsScrollbarWidthThin(aFrame)) {
+ aResult->SizeTo(8, 8);
+ }
+ break;
+ }
+
+ // yeah, i know i'm cheating a little here, but i figure that it
+ // really doesn't matter if the scrollbar is vertical or horizontal
+ // and the width metric is a really good metric for every piece
+ // of the scrollbar.
+
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame) return NS_ERROR_FAILURE;
+
+ int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
+ kThemeMetricSmallScrollBarWidth :
+ kThemeMetricScrollBarWidth;
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+ if (IsScrollbarWidthThin(aFrame)) {
+ scrollbarWidth /= 2;
+ }
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth);
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ {
+ int32_t themeMetric = kThemeMetricScrollBarWidth;
+
+ if (aFrame) {
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (scrollbarFrame &&
+ scrollbarFrame->StyleDisplay()->mAppearance ==
+ NS_THEME_SCROLLBAR_SMALL) {
+ // XXX We're interested in the width of non-disappearing scrollbars
+ // to leave enough space for a dropmarker in non-native styled
+ // comboboxes (bug 869314). It isn't clear to me if comboboxes can
+ // ever have small scrollbars.
+ themeMetric = kThemeMetricSmallScrollBarWidth;
+ }
+ }
+
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth);
+ break;
+ }
+
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ {
+ if (!IsScrollbarWidthThin(aFrame)) {
+ // Get scrollbar button metrics from the system, except in the case of
+ // thin scrollbars, where we leave them at 0 (collapse)
+
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame) return NS_ERROR_FAILURE;
+
+ // Since there is no NS_THEME_SCROLLBARBUTTON_UP_SMALL we need to ask the parent what appearance style it has.
+ int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
+ kThemeMetricSmallScrollBarWidth :
+ kThemeMetricScrollBarWidth;
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+
+ // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
+ if (aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT)
+ aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
+ else
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
+ }
+ *aIsOverridable = false;
+ break;
+ }
+ case NS_THEME_RESIZER:
+ {
+ HIThemeGrowBoxDrawInfo drawInfo;
+ drawInfo.version = 0;
+ drawInfo.state = kThemeStateActive;
+ drawInfo.kind = kHIThemeGrowBoxKindNormal;
+ drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
+ drawInfo.size = kHIThemeGrowBoxSizeNormal;
+ HIPoint pnt = { 0, 0 };
+ HIRect bounds;
+ HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
+ aResult->SizeTo(bounds.size.width, bounds.size.height);
+ *aIsOverridable = false;
+ }
+ }
+
+ if (IsHiDPIContext(aPresContext)) {
+ *aResult = *aResult * 2;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue)
+{
+ // Some widget types just never change state.
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_DIALOG:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_METERBAR:
+ case NS_THEME_METERCHUNK:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ // XXXdwh Not sure what can really be done here. Can at least guess for
+ // specific widgets that they're highly unlikely to have certain states.
+ // For example, a toolbar doesn't care about any states.
+ if (!aAttribute) {
+ // Hover/focus/active changed. Always repaint.
+ *aShouldRepaint = true;
+ } else {
+ // Check the attribute to see if it's relevant.
+ // disabled, checked, dlgtype, default, etc.
+ *aShouldRepaint = false;
+ if (aAttribute == nsGkAtoms::disabled ||
+ aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::selected ||
+ aAttribute == nsGkAtoms::visuallyselected ||
+ aAttribute == nsGkAtoms::menuactive ||
+ aAttribute == nsGkAtoms::sortDirection ||
+ aAttribute == nsGkAtoms::focused ||
+ aAttribute == nsGkAtoms::_default ||
+ aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::hover)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::ThemeChanged()
+{
+ // This is unimplemented because we don't care if gecko changes its theme
+ // and Mac OS X doesn't have themes.
+ return NS_OK;
+}
+
+bool
+nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
+ // render natively even if native theme support is disabled.
+ if (aWidgetType != NS_THEME_SCROLLBAR &&
+ aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
+ return false;
+
+ // if this is a dropdown button in a combobox the answer is always no
+ if (aWidgetType == NS_THEME_MENULIST_BUTTON) {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame))
+ return false;
+ }
+
+ switch (aWidgetType) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_MENULIST_TEXT:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ MOZ_FALLTHROUGH;
+
+ case NS_THEME_LISTBOX:
+
+ case NS_THEME_DIALOG:
+ case NS_THEME_WINDOW:
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_MAC_FULLSCREEN_BUTTON:
+ case NS_THEME_TOOLTIP:
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_CHECKBOX_CONTAINER:
+ case NS_THEME_RADIO:
+ case NS_THEME_RADIO_CONTAINER:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ case NS_THEME_BUTTON:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_BEVEL:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ case NS_THEME_TOOLBOX:
+ //case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_METERBAR:
+ case NS_THEME_METERCHUNK:
+ case NS_THEME_SEPARATOR:
+
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TAB:
+
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ case NS_THEME_TREEHEADERSORTARROW:
+ case NS_THEME_TREEITEM:
+ case NS_THEME_TREELINE:
+ case NS_THEME_MAC_SOURCE_LIST:
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+
+ case NS_THEME_RANGE:
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_RESIZER:
+ {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
+ return true;
+
+ // Note that IsWidgetStyled is not called for resizers on Mac. This is
+ // because for scrollable containers, the native resizer looks better
+ // when (non-overlay) scrollbars are present even when the style is
+ // overriden, and the custom transparent resizer looks better when
+ // scrollbars are not present.
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
+ return (!nsLookAndFeel::UseOverlayScrollbars() &&
+ scrollFrame && scrollFrame->GetScrollbarVisibility());
+ }
+
+ case NS_THEME_FOCUS_OUTLINE:
+ return true;
+
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ return VibrancyManager::SystemSupportsVibrancy();
+ }
+
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType)
+{
+ // flesh this out at some point
+ switch (aWidgetType) {
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_METERBAR:
+ case NS_THEME_RANGE:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ return false;
+ }
+ return true;
+}
+
+bool
+nsNativeThemeCocoa::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ if (aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_MENULIST_TEXTFIELD ||
+ aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_MAC_HELP_BUTTON ||
+ aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN ||
+ aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED ||
+ aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_CHECKBOX)
+ return true;
+
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
+{
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_DIALOG:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_SEPARATOR:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREELINE:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_RESIZER:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool
+nsNativeThemeCocoa::IsWindowSheet(nsIFrame* aFrame)
+{
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ id winDelegate = [win delegate];
+ nsIWidget* widget = [(WindowDelegate *)winDelegate geckoWidget];
+ if (!widget) {
+ return false;
+ }
+ return (widget->WindowType() == eWindowType_sheet);
+}
+
+bool
+nsNativeThemeCocoa::NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MAC_SOURCE_LIST:
+ // If we're in a XUL tree, we don't want to clear the background behind the
+ // selections below, since that would make our source list to not pick up
+ // the right font smoothing background. But since we don't call this method
+ // in nsTreeBodyFrame::BuildDisplayList, we never get here.
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ return true;
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame);
+ default:
+ return false;
+ }
+}
+
+static nscolor ConvertNSColor(NSColor* aColor)
+{
+ NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ return NS_RGBA((unsigned int)([deviceColor redComponent] * 255.0),
+ (unsigned int)([deviceColor greenComponent] * 255.0),
+ (unsigned int)([deviceColor blueComponent] * 255.0),
+ (unsigned int)([deviceColor alphaComponent] * 255.0));
+}
+
+bool
+nsNativeThemeCocoa::WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nscolor* aColor)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MAC_SOURCE_LIST:
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_DIALOG:
+ {
+ if ((aWidgetType == NS_THEME_DIALOG && !IsWindowSheet(aFrame)) ||
+ ((aWidgetType == NS_THEME_MAC_SOURCE_LIST_SELECTION ||
+ aWidgetType == NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION) &&
+ !IsInSourceList(aFrame))) {
+ return false;
+ }
+ ChildView* childView = ChildViewForFrame(aFrame);
+ if (childView) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ NSColor* color = [childView vibrancyFontSmoothingBackgroundColorForThemeGeometryType:type];
+ *aColor = ConvertNSColor(color);
+ return true;
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+
+nsITheme::ThemeGeometryType
+nsNativeThemeCocoa::ThemeGeometryTypeForWidget(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ return eThemeGeometryTypeTitlebar;
+ case NS_THEME_TOOLBAR:
+ return eThemeGeometryTypeToolbar;
+ case NS_THEME_TOOLBOX:
+ return eThemeGeometryTypeToolbox;
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ return eThemeGeometryTypeWindowButtons;
+ case NS_THEME_MAC_FULLSCREEN_BUTTON:
+ return eThemeGeometryTypeFullscreenButton;
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ return eThemeGeometryTypeVibrancyLight;
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ return eThemeGeometryTypeVibrancyDark;
+ case NS_THEME_TOOLTIP:
+ return eThemeGeometryTypeTooltip;
+ case NS_THEME_MENUPOPUP:
+ return eThemeGeometryTypeMenu;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM: {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ bool isDisabled = IsDisabled(aFrame, eventState);
+ bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
+ }
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame) ? eThemeGeometryTypeSheet : eThemeGeometryTypeUnknown;
+ case NS_THEME_MAC_SOURCE_LIST:
+ return eThemeGeometryTypeSourceList;
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ default:
+ return eThemeGeometryTypeUnknown;
+ }
+}
+
+nsITheme::Transparency
+nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_TOOLTIP:
+ return eTransparent;
+
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame) ? eTransparent : eOpaque;
+
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ return nsLookAndFeel::UseOverlayScrollbars() ? eTransparent : eOpaque;
+
+ case NS_THEME_STATUSBAR:
+ // Knowing that scrollbars and statusbars are opaque improves
+ // performance, because we create layers for them.
+ return eOpaque;
+
+ case NS_THEME_TOOLBAR:
+ return eOpaque;
+
+ default:
+ return eUnknownTransparency;
+ }
+}
diff --git a/widget/cocoa/nsNativeThemeColors.h b/widget/cocoa/nsNativeThemeColors.h
new file mode 100644
index 0000000000..b1691b5163
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeColors.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsNativeThemeColors_h_
+#define nsNativeThemeColors_h_
+
+#include "nsCocoaFeatures.h"
+#import <Cocoa/Cocoa.h>
+
+enum ColorName {
+ toolbarTopBorderGrey,
+ toolbarFillGrey,
+ toolbarBottomBorderGrey,
+};
+
+static const int sLionThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ { 0xD0, 0xF0 }, // top separator line
+ { 0xB2, 0xE1 }, // fill color
+ { 0x59, 0x87 }, // bottom separator line
+};
+
+static const int sYosemiteThemeColors[][2] = {
+ /* { active window, inactive window } */
+ // toolbar:
+ { 0xBD, 0xDF }, // top separator line
+ { 0xD3, 0xF6 }, // fill color
+ { 0xB3, 0xD1 }, // bottom separator line
+};
+
+__attribute__((unused))
+static int NativeGreyColorAsInt(ColorName name, BOOL isMain)
+{
+ if (nsCocoaFeatures::OnYosemiteOrLater())
+ return sYosemiteThemeColors[name][isMain ? 0 : 1];
+ return sLionThemeColors[name][isMain ? 0 : 1];
+}
+
+__attribute__((unused))
+static float NativeGreyColorAsFloat(ColorName name, BOOL isMain)
+{
+ return NativeGreyColorAsInt(name, isMain) / 255.0f;
+}
+
+__attribute__((unused))
+static void DrawNativeGreyColorInRect(CGContextRef context, ColorName name,
+ CGRect rect, BOOL isMain)
+{
+ float grey = NativeGreyColorAsFloat(name, isMain);
+ CGContextSetRGBFillColor(context, grey, grey, grey, 1.0f);
+ CGContextFillRect(context, rect);
+}
+
+#endif // nsNativeThemeColors_h_
diff --git a/widget/cocoa/nsPIWidgetCocoa.idl b/widget/cocoa/nsPIWidgetCocoa.idl
new file mode 100644
index 0000000000..a8fd8149ce
--- /dev/null
+++ b/widget/cocoa/nsPIWidgetCocoa.idl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl"
+
+interface nsIWidget;
+
+[ptr] native NSWindowPtr(NSWindow);
+
+//
+// nsPIWidgetCocoa
+//
+// A private interface (unfrozen, private to the widget implementation) that
+// gives us access to some extra features on a widget/window.
+//
+[uuid(f75ff69e-3a51-419e-bd29-042f804bc2ed)]
+interface nsPIWidgetCocoa : nsISupports
+{
+ void SendSetZLevelEvent();
+
+ // Find the displayed child sheet (if aShown) or a child sheet that
+ // wants to be displayed (if !aShown)
+ nsIWidget GetChildSheet(in boolean aShown);
+
+ // Get the parent widget (if any) StandardCreate() was called with.
+ nsIWidget GetRealParent();
+
+ // If the object implementing this interface is a sheet, this will return the
+ // native NSWindow it is attached to
+ readonly attribute NSWindowPtr sheetWindowParent;
+
+ // True if window is a sheet
+ readonly attribute boolean isSheet;
+
+}; // nsPIWidgetCocoa
diff --git a/widget/cocoa/nsPrintDialogX.h b/widget/cocoa/nsPrintDialogX.h
new file mode 100644
index 0000000000..470f17d99d
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsPrintDialog_h_
+#define nsPrintDialog_h_
+
+#include "nsIPrintDialogService.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+
+#import <Cocoa/Cocoa.h>
+
+class nsIPrintSettings;
+class nsIStringBundle;
+
+class nsPrintDialogServiceX : public nsIPrintDialogService
+{
+public:
+ nsPrintDialogServiceX();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aSettings) override;
+
+protected:
+ virtual ~nsPrintDialogServiceX();
+};
+
+@interface PrintPanelAccessoryView : NSView
+{
+ nsIPrintSettings* mSettings;
+ nsIStringBundle* mPrintBundle;
+ NSButton* mPrintSelectionOnlyCheckbox;
+ NSButton* mShrinkToFitCheckbox;
+ NSButton* mPrintBGColorsCheckbox;
+ NSButton* mPrintBGImagesCheckbox;
+ NSButtonCell* mAsLaidOutRadio;
+ NSButtonCell* mSelectedFrameRadio;
+ NSButtonCell* mSeparateFramesRadio;
+ NSPopUpButton* mHeaderLeftList;
+ NSPopUpButton* mHeaderCenterList;
+ NSPopUpButton* mHeaderRightList;
+ NSPopUpButton* mFooterLeftList;
+ NSPopUpButton* mFooterCenterList;
+ NSPopUpButton* mFooterRightList;
+}
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+@interface PrintPanelAccessoryController : NSViewController <NSPrintPanelAccessorizing>
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings;
+
+- (void)exportSettings;
+
+@end
+
+#endif // nsPrintDialog_h_
diff --git a/widget/cocoa/nsPrintDialogX.mm b/widget/cocoa/nsPrintDialogX.mm
new file mode 100644
index 0000000000..a6d58d5bfb
--- /dev/null
+++ b/widget/cocoa/nsPrintDialogX.mm
@@ -0,0 +1,682 @@
+/* -*- Mode: C++; tab-width: 2; 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 "mozilla/ArrayUtils.h"
+
+#include "nsPrintDialogX.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsPrintSettingsX.h"
+#include "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIWebProgressListener.h"
+#include "nsIStringBundle.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsCRT.h"
+
+#import <Cocoa/Cocoa.h>
+#include "nsObjCExceptions.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceX, nsIPrintDialogService)
+
+nsPrintDialogServiceX::nsPrintDialogServiceX()
+{
+}
+
+nsPrintDialogServiceX::~nsPrintDialogServiceX()
+{
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::Show(nsPIDOMWindowOuter *aParent, nsIPrintSettings *aSettings,
+ nsIWebBrowserPrint *aWebBrowserPrint)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NS_PRECONDITION(aSettings, "aSettings must not be null");
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (!settingsX)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc
+ = do_GetService("@mozilla.org/gfx/printsettings-service;1");
+
+ // Set the print job title
+ char16_t** docTitles;
+ uint32_t titleCount;
+ nsresult rv = aWebBrowserPrint->EnumerateDocumentNames(&titleCount, &docTitles);
+ if (NS_SUCCEEDED(rv) && titleCount > 0) {
+ CFStringRef cfTitleString = CFStringCreateWithCharacters(NULL, reinterpret_cast<const UniChar*>(docTitles[0]),
+ NS_strlen(docTitles[0]));
+ if (cfTitleString) {
+ ::PMPrintSettingsSetJobName(settingsX->GetPMPrintSettings(), cfTitleString);
+ CFRelease(cfTitleString);
+ }
+ for (int32_t i = titleCount - 1; i >= 0; i--) {
+ free(docTitles[i]);
+ }
+ free(docTitles);
+ docTitles = NULL;
+ titleCount = 0;
+ }
+
+ // Read default print settings from prefs
+ printSettingsSvc->InitPrintSettingsFromPrefs(settingsX, true,
+ nsIPrintSettings::kInitSaveNativeData);
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+
+ // Put the print info into the current print operation, since that's where
+ // [panel runModal] will look for it. We create the view because otherwise
+ // we'll get unrelated warnings printed to the console.
+ NSView* tmpView = [[NSView alloc] init];
+ NSPrintOperation* printOperation = [NSPrintOperation printOperationWithView:tmpView printInfo:printInfo];
+ [NSPrintOperation setCurrentOperation:printOperation];
+
+ NSPrintPanel* panel = [NSPrintPanel printPanel];
+ [panel setOptions:NSPrintPanelShowsCopies
+ | NSPrintPanelShowsPageRange
+ | NSPrintPanelShowsPaperSize
+ | NSPrintPanelShowsOrientation
+ | NSPrintPanelShowsScaling ];
+ PrintPanelAccessoryController* viewController =
+ [[PrintPanelAccessoryController alloc] initWithSettings:aSettings];
+ [panel addAccessoryController:viewController];
+ [viewController release];
+
+ // Show the dialog.
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int button = [panel runModal];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ NSPrintInfo* copy = [[[NSPrintOperation currentOperation] printInfo] copy];
+ if (!copy) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ [NSPrintOperation setCurrentOperation:nil];
+ [tmpView release];
+
+ if (button != NSFileHandlingPanelOKButton)
+ return NS_ERROR_ABORT;
+
+ settingsX->SetCocoaPrintInfo(copy);
+ settingsX->InitUnwriteableMargin();
+
+ // Save settings unless saving is pref'd off
+ if (Preferences::GetBool("print.save_print_settings", false)) {
+ printSettingsSvc->SavePrintSettingsToPrefs(settingsX, true,
+ nsIPrintSettings::kInitSaveNativeData);
+ }
+
+ // Get coordinate space resolution for converting paper size units to inches
+ NSWindow *win = [[NSApplication sharedApplication] mainWindow];
+ if (win) {
+ NSDictionary *devDesc = [win deviceDescription];
+ if (devDesc) {
+ NSSize res = [[devDesc objectForKey: NSDeviceResolution] sizeValue];
+ float scale = [win backingScaleFactor];
+ if (scale > 0) {
+ settingsX->SetInchesScale(res.width / scale, res.height / scale);
+ }
+ }
+ }
+
+ // Export settings.
+ [viewController exportSettings];
+
+ // If "ignore scaling" is checked, overwrite scaling factor with 1.
+ bool isShrinkToFitChecked;
+ settingsX->GetShrinkToFit(&isShrinkToFitChecked);
+ if (isShrinkToFitChecked) {
+ NSMutableDictionary* dict = [copy dictionary];
+ if (dict) {
+ [dict setObject: [NSNumber numberWithFloat: 1]
+ forKey: NSPrintScalingFactor];
+ }
+ // Set the scaling factor to 100% in the NSPrintInfo
+ // object so that it will not affect the paper size
+ // retrieved from the PMPageFormat routines.
+ [copy setScalingFactor:1.0];
+ } else {
+ aSettings->SetScaling([copy scalingFactor]);
+ }
+
+ // Set the adjusted paper size now that we've updated
+ // the scaling factor.
+ settingsX->InitAdjustedPaperSize();
+
+ [copy release];
+
+ int16_t pageRange;
+ aSettings->GetPrintRange(&pageRange);
+ if (pageRange != nsIPrintSettings::kRangeSelection) {
+ PMPrintSettings nativePrintSettings = settingsX->GetPMPrintSettings();
+ UInt32 firstPage, lastPage;
+ OSStatus status = ::PMGetFirstPage(nativePrintSettings, &firstPage);
+ if (status == noErr) {
+ status = ::PMGetLastPage(nativePrintSettings, &lastPage);
+ if (status == noErr && lastPage != UINT32_MAX) {
+ aSettings->SetPrintRange(nsIPrintSettings::kRangeSpecifiedPageRange);
+ aSettings->SetStartPageRange(firstPage);
+ aSettings->SetEndPageRange(lastPage);
+ }
+ }
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceX::ShowPageSetup(nsPIDOMWindowOuter *aParent,
+ nsIPrintSettings *aNSSettings)
+{
+ NS_PRECONDITION(aParent, "aParent must not be null");
+ NS_PRECONDITION(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aNSSettings));
+ if (!settingsX)
+ return NS_ERROR_FAILURE;
+
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+ NSPageLayout *pageLayout = [NSPageLayout pageLayout];
+ nsCocoaUtils::PrepareForNativeAppModalDialog();
+ int button = [pageLayout runModalWithPrintInfo:printInfo];
+ nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
+
+ return button == NSFileHandlingPanelOKButton ? NS_OK : NS_ERROR_ABORT;
+}
+
+// Accessory view
+
+@interface PrintPanelAccessoryView (Private)
+
+- (NSString*)localizedString:(const char*)aKey;
+
+- (int16_t)chosenFrameSetting;
+
+- (const char*)headerFooterStringForList:(NSPopUpButton*)aList;
+
+- (void)exportHeaderFooterSettings;
+
+- (void)initBundle;
+
+- (NSTextField*)label:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment;
+
+- (void)addLabel:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment;
+
+- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect;
+
+- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect;
+
+- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect;
+
+- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
+ selectedItem:(const char16_t*)aCurrentString;
+
+- (void)addOptionsSection;
+
+- (void)addAppearanceSection;
+
+- (void)addFramesSection;
+
+- (void)addHeaderFooterSection;
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox;
+
+- (NSString*)framesSummaryValue;
+
+- (NSString*)headerSummaryValue;
+
+- (NSString*)footerSummaryValue;
+
+@end
+
+static const char sHeaderFooterTags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
+
+@implementation PrintPanelAccessoryView
+
+// Public methods
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings
+{
+ [super initWithFrame:NSMakeRect(0, 0, 540, 270)];
+
+ mSettings = aSettings;
+ [self initBundle];
+ [self addOptionsSection];
+ [self addAppearanceSection];
+ [self addFramesSection];
+ [self addHeaderFooterSection];
+
+ return self;
+}
+
+- (void)exportSettings
+{
+ mSettings->SetPrintRange([mPrintSelectionOnlyCheckbox state] == NSOnState ?
+ (int16_t)nsIPrintSettings::kRangeSelection :
+ (int16_t)nsIPrintSettings::kRangeAllPages);
+ mSettings->SetShrinkToFit([mShrinkToFitCheckbox state] == NSOnState);
+ mSettings->SetPrintBGColors([mPrintBGColorsCheckbox state] == NSOnState);
+ mSettings->SetPrintBGImages([mPrintBGImagesCheckbox state] == NSOnState);
+ mSettings->SetPrintFrameType([self chosenFrameSetting]);
+
+ [self exportHeaderFooterSettings];
+}
+
+- (void)dealloc
+{
+ NS_IF_RELEASE(mPrintBundle);
+ [super dealloc];
+}
+
+// Localization
+
+- (void)initBundle
+{
+ nsCOMPtr<nsIStringBundleService> bundleSvc = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties", &mPrintBundle);
+}
+
+- (NSString*)localizedString:(const char*)aKey
+{
+ if (!mPrintBundle)
+ return @"";
+
+ nsXPIDLString intlString;
+ mPrintBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aKey).get(), getter_Copies(intlString));
+ NSMutableString* s = [NSMutableString stringWithUTF8String:NS_ConvertUTF16toUTF8(intlString).get()];
+
+ // Remove all underscores (they're used in the GTK dialog for accesskeys).
+ [s replaceOccurrencesOfString:@"_" withString:@"" options:0 range:NSMakeRange(0, [s length])];
+ return s;
+}
+
+// Widget helpers
+
+- (NSTextField*)label:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment
+{
+ NSTextField* label = [[[NSTextField alloc] initWithFrame:aRect] autorelease];
+ [label setStringValue:[self localizedString:aLabel]];
+ [label setEditable:NO];
+ [label setSelectable:NO];
+ [label setBezeled:NO];
+ [label setBordered:NO];
+ [label setDrawsBackground:NO];
+ [label setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ [label setAlignment:aAlignment];
+ return label;
+}
+
+- (void)addLabel:(const char*)aLabel
+ withFrame:(NSRect)aRect
+ alignment:(NSTextAlignment)aAlignment
+{
+ NSTextField* label = [self label:aLabel withFrame:aRect alignment:aAlignment];
+ [self addSubview:label];
+}
+
+- (void)addLabel:(const char*)aLabel withFrame:(NSRect)aRect
+{
+ [self addLabel:aLabel withFrame:aRect alignment:NSRightTextAlignment];
+}
+
+- (void)addCenteredLabel:(const char*)aLabel withFrame:(NSRect)aRect
+{
+ [self addLabel:aLabel withFrame:aRect alignment:NSCenterTextAlignment];
+}
+
+- (NSButton*)checkboxWithLabel:(const char*)aLabel andFrame:(NSRect)aRect
+{
+ aRect.origin.y += 4.0f;
+ NSButton* checkbox = [[[NSButton alloc] initWithFrame:aRect] autorelease];
+ [checkbox setButtonType:NSSwitchButton];
+ [checkbox setTitle:[self localizedString:aLabel]];
+ [checkbox setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ [checkbox sizeToFit];
+ return checkbox;
+}
+
+- (NSPopUpButton*)headerFooterItemListWithFrame:(NSRect)aRect
+ selectedItem:(const char16_t*)aCurrentString
+{
+ NSPopUpButton* list = [[[NSPopUpButton alloc] initWithFrame:aRect pullsDown:NO] autorelease];
+ [list setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ [[list cell] setControlSize:NSSmallControlSize];
+ NSArray* items =
+ [NSArray arrayWithObjects:[self localizedString:"headerFooterBlank"],
+ [self localizedString:"headerFooterTitle"],
+ [self localizedString:"headerFooterURL"],
+ [self localizedString:"headerFooterDate"],
+ [self localizedString:"headerFooterPage"],
+ [self localizedString:"headerFooterPageTotal"],
+ nil];
+ [list addItemsWithTitles:items];
+
+ NS_ConvertUTF16toUTF8 currentStringUTF8(aCurrentString);
+ for (unsigned int i = 0; i < ArrayLength(sHeaderFooterTags); i++) {
+ if (!strcmp(currentStringUTF8.get(), sHeaderFooterTags[i])) {
+ [list selectItemAtIndex:i];
+ break;
+ }
+ }
+
+ return list;
+}
+
+// Build sections
+
+- (void)addOptionsSection
+{
+ // Title
+ [self addLabel:"optionsTitleMac" withFrame:NSMakeRect(0, 240, 151, 22)];
+
+ // "Print Selection Only"
+ mPrintSelectionOnlyCheckbox = [self checkboxWithLabel:"selectionOnly"
+ andFrame:NSMakeRect(156, 240, 0, 0)];
+
+ bool canPrintSelection;
+ mSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
+ &canPrintSelection);
+ [mPrintSelectionOnlyCheckbox setEnabled:canPrintSelection];
+
+ int16_t printRange;
+ mSettings->GetPrintRange(&printRange);
+ if (printRange == nsIPrintSettings::kRangeSelection) {
+ [mPrintSelectionOnlyCheckbox setState:NSOnState];
+ }
+
+ [self addSubview:mPrintSelectionOnlyCheckbox];
+
+ // "Shrink To Fit"
+ mShrinkToFitCheckbox = [self checkboxWithLabel:"shrinkToFit"
+ andFrame:NSMakeRect(156, 218, 0, 0)];
+
+ bool shrinkToFit;
+ mSettings->GetShrinkToFit(&shrinkToFit);
+ [mShrinkToFitCheckbox setState:(shrinkToFit ? NSOnState : NSOffState)];
+
+ [self addSubview:mShrinkToFitCheckbox];
+}
+
+- (void)addAppearanceSection
+{
+ // Title
+ [self addLabel:"appearanceTitleMac" withFrame:NSMakeRect(0, 188, 151, 22)];
+
+ // "Print Background Colors"
+ mPrintBGColorsCheckbox = [self checkboxWithLabel:"printBGColors"
+ andFrame:NSMakeRect(156, 188, 0, 0)];
+
+ bool geckoBool;
+ mSettings->GetPrintBGColors(&geckoBool);
+ [mPrintBGColorsCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGColorsCheckbox];
+
+ // "Print Background Images"
+ mPrintBGImagesCheckbox = [self checkboxWithLabel:"printBGImages"
+ andFrame:NSMakeRect(156, 166, 0, 0)];
+
+ mSettings->GetPrintBGImages(&geckoBool);
+ [mPrintBGImagesCheckbox setState:(geckoBool ? NSOnState : NSOffState)];
+
+ [self addSubview:mPrintBGImagesCheckbox];
+}
+
+- (void)addFramesSection
+{
+ // Title
+ [self addLabel:"framesTitleMac" withFrame:NSMakeRect(0, 124, 151, 22)];
+
+ // Radio matrix
+ NSButtonCell *radio = [[NSButtonCell alloc] init];
+ [radio setButtonType:NSRadioButton];
+ [radio setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
+ NSMatrix *matrix = [[NSMatrix alloc] initWithFrame:NSMakeRect(156, 81, 400, 66)
+ mode:NSRadioModeMatrix
+ prototype:(NSCell*)radio
+ numberOfRows:3
+ numberOfColumns:1];
+ [radio release];
+ [matrix setCellSize:NSMakeSize(400, 21)];
+ [self addSubview:matrix];
+ [matrix release];
+ NSArray *cellArray = [matrix cells];
+ mAsLaidOutRadio = [cellArray objectAtIndex:0];
+ mSelectedFrameRadio = [cellArray objectAtIndex:1];
+ mSeparateFramesRadio = [cellArray objectAtIndex:2];
+ [mAsLaidOutRadio setTitle:[self localizedString:"asLaidOut"]];
+ [mSelectedFrameRadio setTitle:[self localizedString:"selectedFrame"]];
+ [mSeparateFramesRadio setTitle:[self localizedString:"separateFrames"]];
+
+ // Radio enabled state
+ int16_t frameUIFlag;
+ mSettings->GetHowToEnableFrameUI(&frameUIFlag);
+ if (frameUIFlag == nsIPrintSettings::kFrameEnableNone) {
+ [mAsLaidOutRadio setEnabled:NO];
+ [mSelectedFrameRadio setEnabled:NO];
+ [mSeparateFramesRadio setEnabled:NO];
+ } else if (frameUIFlag == nsIPrintSettings::kFrameEnableAsIsAndEach) {
+ [mSelectedFrameRadio setEnabled:NO];
+ }
+
+ // Radio values
+ int16_t printFrameType;
+ mSettings->GetPrintFrameType(&printFrameType);
+ switch (printFrameType) {
+ case nsIPrintSettings::kFramesAsIs:
+ [mAsLaidOutRadio setState:NSOnState];
+ break;
+ case nsIPrintSettings::kSelectedFrame:
+ [mSelectedFrameRadio setState:NSOnState];
+ break;
+ case nsIPrintSettings::kEachFrameSep:
+ [mSeparateFramesRadio setState:NSOnState];
+ break;
+ }
+}
+
+- (void)addHeaderFooterSection
+{
+ // Labels
+ [self addLabel:"pageHeadersTitleMac" withFrame:NSMakeRect(0, 44, 151, 22)];
+ [self addLabel:"pageFootersTitleMac" withFrame:NSMakeRect(0, 0, 151, 22)];
+ [self addCenteredLabel:"left" withFrame:NSMakeRect(156, 22, 100, 22)];
+ [self addCenteredLabel:"center" withFrame:NSMakeRect(256, 22, 100, 22)];
+ [self addCenteredLabel:"right" withFrame:NSMakeRect(356, 22, 100, 22)];
+
+ // Lists
+ nsXPIDLString sel;
+
+ mSettings->GetHeaderStrLeft(getter_Copies(sel));
+ mHeaderLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderLeftList];
+
+ mSettings->GetHeaderStrCenter(getter_Copies(sel));
+ mHeaderCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderCenterList];
+
+ mSettings->GetHeaderStrRight(getter_Copies(sel));
+ mHeaderRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 44, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mHeaderRightList];
+
+ mSettings->GetFooterStrLeft(getter_Copies(sel));
+ mFooterLeftList = [self headerFooterItemListWithFrame:NSMakeRect(156, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterLeftList];
+
+ mSettings->GetFooterStrCenter(getter_Copies(sel));
+ mFooterCenterList = [self headerFooterItemListWithFrame:NSMakeRect(256, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterCenterList];
+
+ mSettings->GetFooterStrRight(getter_Copies(sel));
+ mFooterRightList = [self headerFooterItemListWithFrame:NSMakeRect(356, 0, 100, 22)
+ selectedItem:sel];
+ [self addSubview:mFooterRightList];
+}
+
+// Export settings
+
+- (int16_t)chosenFrameSetting
+{
+ if ([mAsLaidOutRadio state] == NSOnState)
+ return nsIPrintSettings::kFramesAsIs;
+ if ([mSelectedFrameRadio state] == NSOnState)
+ return nsIPrintSettings::kSelectedFrame;
+ if ([mSeparateFramesRadio state] == NSOnState)
+ return nsIPrintSettings::kEachFrameSep;
+ return nsIPrintSettings::kNoFrames;
+}
+
+- (const char*)headerFooterStringForList:(NSPopUpButton*)aList
+{
+ NSInteger index = [aList indexOfSelectedItem];
+ NS_ASSERTION(index < NSInteger(ArrayLength(sHeaderFooterTags)), "Index of dropdown is higher than expected!");
+ return sHeaderFooterTags[index];
+}
+
+- (void)exportHeaderFooterSettings
+{
+ const char* headerFooterStr;
+ headerFooterStr = [self headerFooterStringForList:mHeaderLeftList];
+ mSettings->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderCenterList];
+ mSettings->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mHeaderRightList];
+ mSettings->SetHeaderStrRight(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterLeftList];
+ mSettings->SetFooterStrLeft(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterCenterList];
+ mSettings->SetFooterStrCenter(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+
+ headerFooterStr = [self headerFooterStringForList:mFooterRightList];
+ mSettings->SetFooterStrRight(NS_ConvertUTF8toUTF16(headerFooterStr).get());
+}
+
+// Summary
+
+- (NSString*)summaryValueForCheckbox:(NSButton*)aCheckbox
+{
+ if (![aCheckbox isEnabled])
+ return [self localizedString:"summaryNAValue"];
+
+ return [aCheckbox state] == NSOnState ?
+ [self localizedString:"summaryOnValue"] :
+ [self localizedString:"summaryOffValue"];
+}
+
+- (NSString*)framesSummaryValue
+{
+ switch([self chosenFrameSetting]) {
+ case nsIPrintSettings::kFramesAsIs:
+ return [self localizedString:"asLaidOut"];
+ case nsIPrintSettings::kSelectedFrame:
+ return [self localizedString:"selectedFrame"];
+ case nsIPrintSettings::kEachFrameSep:
+ return [self localizedString:"separateFrames"];
+ }
+ return [self localizedString:"summaryNAValue"];
+}
+
+- (NSString*)headerSummaryValue
+{
+ return [[mHeaderLeftList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [[mHeaderCenterList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [mHeaderRightList titleOfSelectedItem]]]]];
+}
+
+- (NSString*)footerSummaryValue
+{
+ return [[mFooterLeftList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [[mFooterCenterList titleOfSelectedItem] stringByAppendingString:
+ [@", " stringByAppendingString:
+ [mFooterRightList titleOfSelectedItem]]]]];
+}
+
+- (NSArray*)localizedSummaryItems
+{
+ return [NSArray arrayWithObjects:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryFramesTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self framesSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summarySelectionOnlyTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintSelectionOnlyCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryShrinkToFitTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mShrinkToFitCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryPrintBGColorsTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintBGColorsCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryPrintBGImagesTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self summaryValueForCheckbox:mPrintBGImagesCheckbox], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryHeaderTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self headerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [self localizedString:"summaryFooterTitle"], NSPrintPanelAccessorySummaryItemNameKey,
+ [self footerSummaryValue], NSPrintPanelAccessorySummaryItemDescriptionKey, nil],
+ nil];
+}
+
+@end
+
+// Accessory controller
+
+@implementation PrintPanelAccessoryController
+
+- (id)initWithSettings:(nsIPrintSettings*)aSettings
+{
+ [super initWithNibName:nil bundle:nil];
+
+ NSView* accView = [[PrintPanelAccessoryView alloc] initWithSettings:aSettings];
+ [self setView:accView];
+ [accView release];
+ return self;
+}
+
+- (void)exportSettings
+{
+ return [(PrintPanelAccessoryView*)[self view] exportSettings];
+}
+
+- (NSArray *)localizedSummaryItems
+{
+ return [(PrintPanelAccessoryView*)[self view] localizedSummaryItems];
+}
+
+@end
diff --git a/widget/cocoa/nsPrintOptionsX.h b/widget/cocoa/nsPrintOptionsX.h
new file mode 100644
index 0000000000..e34e75059e
--- /dev/null
+++ b/widget/cocoa/nsPrintOptionsX.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsPrintOptionsX_h_
+#define nsPrintOptionsX_h_
+
+#include "nsPrintOptionsImpl.h"
+
+namespace mozilla
+{
+namespace embedding
+{
+ class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+class nsPrintOptionsX : public nsPrintOptions
+{
+public:
+ nsPrintOptionsX();
+ virtual ~nsPrintOptionsX();
+
+ /*
+ * These serialize and deserialize methods are not symmetrical in that
+ * printSettingsX != deserialize(serialize(printSettingsX)). This is because
+ * the native print settings stored in the nsPrintSettingsX's NSPrintInfo
+ * object are not fully serialized. Only the values needed for successful
+ * printing are.
+ */
+ NS_IMETHODIMP SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ mozilla::embedding::PrintData* data);
+ NS_IMETHODIMP DeserializeToPrintSettings(const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings);
+
+protected:
+ nsresult _CreatePrintSettings(nsIPrintSettings **_retval);
+ nsresult ReadPrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags);
+ nsresult WritePrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags);
+};
+
+#endif // nsPrintOptionsX_h_
diff --git a/widget/cocoa/nsPrintOptionsX.mm b/widget/cocoa/nsPrintOptionsX.mm
new file mode 100644
index 0000000000..d9aa17b42e
--- /dev/null
+++ b/widget/cocoa/nsPrintOptionsX.mm
@@ -0,0 +1,349 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsCOMPtr.h"
+#include "nsQueryObject.h"
+#include "nsIServiceManager.h"
+#include "nsPrintOptionsX.h"
+#include "nsPrintSettingsX.h"
+
+// The constants for paper orientation were renamed in 10.9. __MAC_10_9 is
+// defined on OS X 10.9 and later. Although 10.8 and earlier are not supported
+// at this time, this allows for building on those older OS versions. The
+// values are consistent across OS versions so the rename does not affect
+// runtime, just compilation.
+#ifdef __MAC_10_9
+#define NS_PAPER_ORIENTATION_PORTRAIT (NSPaperOrientationPortrait)
+#define NS_PAPER_ORIENTATION_LANDSCAPE (NSPaperOrientationLandscape)
+#else
+#define NS_PAPER_ORIENTATION_PORTRAIT (NSPortraitOrientation)
+#define NS_PAPER_ORIENTATION_LANDSCAPE (NSLandscapeOrientation)
+#endif
+
+using namespace mozilla::embedding;
+
+nsPrintOptionsX::nsPrintOptionsX()
+{
+}
+
+nsPrintOptionsX::~nsPrintOptionsX()
+{
+}
+
+NS_IMETHODIMP
+nsPrintOptionsX::SerializeToPrintData(nsIPrintSettings* aSettings,
+ nsIWebBrowserPrint* aWBP,
+ PrintData* data)
+{
+ nsresult rv = nsPrintOptions::SerializeToPrintData(aSettings, aWBP, data);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aWBP) {
+ // When serializing an nsIWebBrowserPrint, we need to pass up the first
+ // document name. We could pass up the entire collection of document
+ // names, but the OS X printing prompt code only really cares about
+ // the first one, so we just send the first to save IPC traffic.
+ char16_t** docTitles;
+ uint32_t titleCount;
+ rv = aWBP->EnumerateDocumentNames(&titleCount, &docTitles);
+ if (NS_SUCCEEDED(rv) && titleCount > 0) {
+ data->printJobName().Assign(docTitles[0]);
+ }
+
+ for (int32_t i = titleCount - 1; i >= 0; i--) {
+ free(docTitles[i]);
+ }
+ free(docTitles);
+ docTitles = nullptr;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(aSettings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSPrintInfo* printInfo = settingsX->GetCocoaPrintInfo();
+ if (NS_WARN_IF(!printInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ double adjustedWidth, adjustedHeight;
+ settingsX->GetAdjustedPaperSize(&adjustedWidth, &adjustedHeight);
+ data->adjustedPaperWidth() = adjustedWidth;
+ data->adjustedPaperHeight() = adjustedHeight;
+
+ NSDictionary* dict = [printInfo dictionary];
+ if (NS_WARN_IF(!dict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSString* printerName = [dict objectForKey: NSPrintPrinterName];
+ if (printerName) {
+ nsCocoaUtils::GetStringForNSString(printerName, data->printerName());
+ }
+
+ NSString* faxNumber = [dict objectForKey: NSPrintFaxNumber];
+ if (faxNumber) {
+ nsCocoaUtils::GetStringForNSString(faxNumber, data->faxNumber());
+ }
+
+ NSURL* printToFileURL = [dict objectForKey: NSPrintJobSavingURL];
+ if (printToFileURL) {
+ nsCocoaUtils::GetStringForNSString([printToFileURL absoluteString],
+ data->toFileName());
+ }
+
+ NSDate* printTime = [dict objectForKey: NSPrintTime];
+ if (printTime) {
+ NSTimeInterval timestamp = [printTime timeIntervalSinceReferenceDate];
+ data->printTime() = timestamp;
+ }
+
+ NSString* disposition = [dict objectForKey: NSPrintJobDisposition];
+ if (disposition) {
+ nsCocoaUtils::GetStringForNSString(disposition, data->disposition());
+ }
+
+ NSString* paperName = [dict objectForKey: NSPrintPaperName];
+ if (paperName) {
+ nsCocoaUtils::GetStringForNSString(paperName, data->paperName());
+ }
+
+ float scalingFactor = [[dict objectForKey: NSPrintScalingFactor] floatValue];
+ data->scalingFactor() = scalingFactor;
+
+ int32_t orientation;
+ if ([printInfo orientation] == NS_PAPER_ORIENTATION_PORTRAIT) {
+ orientation = nsIPrintSettings::kPortraitOrientation;
+ } else {
+ orientation = nsIPrintSettings::kLandscapeOrientation;
+ }
+ data->orientation() = orientation;
+
+ NSSize paperSize = [printInfo paperSize];
+ float widthScale, heightScale;
+ settingsX->GetInchesScale(&widthScale, &heightScale);
+ if (orientation == nsIPrintSettings::kLandscapeOrientation) {
+ // switch widths and heights
+ data->widthScale() = heightScale;
+ data->heightScale() = widthScale;
+ data->paperWidth() = paperSize.height / heightScale;
+ data->paperHeight() = paperSize.width / widthScale;
+ } else {
+ data->widthScale() = widthScale;
+ data->heightScale() = heightScale;
+ data->paperWidth() = paperSize.width / widthScale;
+ data->paperHeight() = paperSize.height / heightScale;
+ }
+
+ data->numCopies() = [[dict objectForKey: NSPrintCopies] intValue];
+ data->printAllPages() = [[dict objectForKey: NSPrintAllPages] boolValue];
+ data->startPageRange() = [[dict objectForKey: NSPrintFirstPage] intValue];
+ data->endPageRange() = [[dict objectForKey: NSPrintLastPage] intValue];
+ data->mustCollate() = [[dict objectForKey: NSPrintMustCollate] boolValue];
+ data->printReversed() = [[dict objectForKey: NSPrintReversePageOrder] boolValue];
+ data->pagesAcross() = [[dict objectForKey: NSPrintPagesAcross] unsignedShortValue];
+ data->pagesDown() = [[dict objectForKey: NSPrintPagesDown] unsignedShortValue];
+ data->detailedErrorReporting() = [[dict objectForKey: NSPrintDetailedErrorReporting] boolValue];
+ data->addHeaderAndFooter() = [[dict objectForKey: NSPrintHeaderAndFooter] boolValue];
+ data->fileNameExtensionHidden() =
+ [[dict objectForKey: NSPrintJobSavingFileNameExtensionHidden] boolValue];
+
+ bool printSelectionOnly = [[dict objectForKey: NSPrintSelectionOnly] boolValue];
+ aSettings->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB,
+ printSelectionOnly);
+ aSettings->GetPrintOptionsBits(&data->optionFlags());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintOptionsX::DeserializeToPrintSettings(const PrintData& data,
+ nsIPrintSettings* settings)
+{
+ nsresult rv = nsPrintOptions::DeserializeToPrintSettings(data, settings);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsPrintSettingsX> settingsX(do_QueryObject(settings));
+ if (NS_WARN_IF(!settingsX)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSPrintInfo* sharedPrintInfo = [NSPrintInfo sharedPrintInfo];
+ if (NS_WARN_IF(!sharedPrintInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSDictionary* sharedDict = [sharedPrintInfo dictionary];
+ if (NS_WARN_IF(!sharedDict)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // We need to create a new NSMutableDictionary to pass to NSPrintInfo with
+ // the values that we got from the other process.
+ NSMutableDictionary* newPrintInfoDict =
+ [NSMutableDictionary dictionaryWithDictionary:sharedDict];
+ if (NS_WARN_IF(!newPrintInfoDict)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NSString* printerName = nsCocoaUtils::ToNSString(data.printerName());
+ if (printerName) {
+ NSPrinter* printer = [NSPrinter printerWithName: printerName];
+ if (printer) {
+ [newPrintInfoDict setObject: printer forKey: NSPrintPrinter];
+ [newPrintInfoDict setObject: printerName forKey: NSPrintPrinterName];
+ }
+ }
+
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.numCopies()]
+ forKey: NSPrintCopies];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.printAllPages()]
+ forKey: NSPrintAllPages];
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.startPageRange()]
+ forKey: NSPrintFirstPage];
+ [newPrintInfoDict setObject: [NSNumber numberWithInt: data.endPageRange()]
+ forKey: NSPrintLastPage];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.mustCollate()]
+ forKey: NSPrintMustCollate];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.printReversed()]
+ forKey: NSPrintReversePageOrder];
+
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.disposition())
+ forKey: NSPrintJobDisposition];
+
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.paperName())
+ forKey: NSPrintPaperName];
+
+ [newPrintInfoDict setObject: [NSNumber numberWithFloat: data.scalingFactor()]
+ forKey: NSPrintScalingFactor];
+
+ CGFloat width = data.paperWidth() * data.widthScale();
+ CGFloat height = data.paperHeight() * data.heightScale();
+ [newPrintInfoDict setObject: [NSValue valueWithSize:NSMakeSize(width,height)]
+ forKey: NSPrintPaperSize];
+
+ int paperOrientation;
+ if (data.orientation() == nsIPrintSettings::kPortraitOrientation) {
+ paperOrientation = NS_PAPER_ORIENTATION_PORTRAIT;
+ settings->SetOrientation(nsIPrintSettings::kPortraitOrientation);
+ } else {
+ paperOrientation = NS_PAPER_ORIENTATION_LANDSCAPE;
+ settings->SetOrientation(nsIPrintSettings::kLandscapeOrientation);
+ }
+ [newPrintInfoDict setObject: [NSNumber numberWithInt:paperOrientation]
+ forKey: NSPrintOrientation];
+
+ [newPrintInfoDict setObject: [NSNumber numberWithShort: data.pagesAcross()]
+ forKey: NSPrintPagesAcross];
+ [newPrintInfoDict setObject: [NSNumber numberWithShort: data.pagesDown()]
+ forKey: NSPrintPagesDown];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.detailedErrorReporting()]
+ forKey: NSPrintDetailedErrorReporting];
+ [newPrintInfoDict setObject: nsCocoaUtils::ToNSString(data.faxNumber())
+ forKey: NSPrintFaxNumber];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.addHeaderAndFooter()]
+ forKey: NSPrintHeaderAndFooter];
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: data.fileNameExtensionHidden()]
+ forKey: NSPrintJobSavingFileNameExtensionHidden];
+
+ // At this point, the base class should have properly deserialized the print
+ // options bitfield for nsIPrintSettings, so that it holds the correct value
+ // for kEnableSelectionRB, which we use to set NSPrintSelectionOnly.
+
+ bool printSelectionOnly = false;
+ rv = settings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &printSelectionOnly);
+ if (NS_SUCCEEDED(rv)) {
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: printSelectionOnly]
+ forKey: NSPrintSelectionOnly];
+ } else {
+ [newPrintInfoDict setObject: [NSNumber numberWithBool: NO]
+ forKey: NSPrintSelectionOnly];
+ }
+
+ NSURL* jobSavingURL =
+ [NSURL URLWithString: nsCocoaUtils::ToNSString(data.toFileName())];
+ if (jobSavingURL) {
+ [newPrintInfoDict setObject: jobSavingURL forKey: NSPrintJobSavingURL];
+ }
+
+ NSTimeInterval timestamp = data.printTime();
+ NSDate* printTime = [NSDate dateWithTimeIntervalSinceReferenceDate: timestamp];
+ if (printTime) {
+ [newPrintInfoDict setObject: printTime forKey: NSPrintTime];
+ }
+
+ // Next, we create a new NSPrintInfo with the values in our dictionary.
+ NSPrintInfo* newPrintInfo =
+ [[NSPrintInfo alloc] initWithDictionary: newPrintInfoDict];
+ if (NS_WARN_IF(!newPrintInfo)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // And now swap in the new NSPrintInfo we've just populated.
+ settingsX->SetCocoaPrintInfo(newPrintInfo);
+ [newPrintInfo release];
+
+ settingsX->SetAdjustedPaperSize(data.adjustedPaperWidth(),
+ data.adjustedPaperHeight());
+
+ return NS_OK;
+}
+
+nsresult
+nsPrintOptionsX::ReadPrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags)
+{
+ nsresult rv;
+
+ rv = nsPrintOptions::ReadPrefs(aPS, aPrinterName, aFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintOptions::ReadPrefs() failed");
+
+ RefPtr<nsPrintSettingsX> printSettingsX(do_QueryObject(aPS));
+ if (!printSettingsX)
+ return NS_ERROR_NO_INTERFACE;
+ rv = printSettingsX->ReadPageFormatFromPrefs();
+
+ return NS_OK;
+}
+
+nsresult nsPrintOptionsX::_CreatePrintSettings(nsIPrintSettings **_retval)
+{
+ nsresult rv;
+ *_retval = nullptr;
+
+ nsPrintSettingsX* printSettings = new nsPrintSettingsX; // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*_retval = printSettings);
+
+ rv = printSettings->Init();
+ if (NS_FAILED(rv)) {
+ NS_RELEASE(*_retval);
+ return rv;
+ }
+
+ InitPrintSettingsFromPrefs(*_retval, false, nsIPrintSettings::kInitSaveAll);
+ return rv;
+}
+
+nsresult
+nsPrintOptionsX::WritePrefs(nsIPrintSettings* aPS, const nsAString& aPrinterName, uint32_t aFlags)
+{
+ nsresult rv;
+
+ rv = nsPrintOptions::WritePrefs(aPS, aPrinterName, aFlags);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintOptions::WritePrefs() failed");
+
+ RefPtr<nsPrintSettingsX> printSettingsX(do_QueryObject(aPS));
+ if (!printSettingsX)
+ return NS_ERROR_NO_INTERFACE;
+ rv = printSettingsX->WritePageFormatToPrefs();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "nsPrintSettingsX::WritePageFormatToPrefs() failed");
+
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsPrintSettingsX.h b/widget/cocoa/nsPrintSettingsX.h
new file mode 100644
index 0000000000..1d755b2505
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsPrintSettingsX_h_
+#define nsPrintSettingsX_h_
+
+#include "nsPrintSettingsImpl.h"
+#import <Cocoa/Cocoa.h>
+
+#define NS_PRINTSETTINGSX_IID \
+{ 0x0DF2FDBD, 0x906D, 0x4726, \
+ { 0x9E, 0x4D, 0xCF, 0xE0, 0x87, 0x8D, 0x70, 0x7C } }
+
+class nsPrintSettingsX : public nsPrintSettings
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSX_IID)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsPrintSettingsX();
+ nsresult Init();
+ NSPrintInfo* GetCocoaPrintInfo() { return mPrintInfo; }
+ void SetCocoaPrintInfo(NSPrintInfo* aPrintInfo);
+ virtual nsresult ReadPageFormatFromPrefs();
+ virtual nsresult WritePageFormatToPrefs();
+ virtual nsresult GetEffectivePageSize(double *aWidth,
+ double *aHeight) override;
+
+ // In addition to setting the paper width and height, these
+ // overrides set the adjusted width and height returned from
+ // GetEffectivePageSize. This is needed when a paper size is
+ // set manually without using a print dialog a la reftest-print.
+ virtual nsresult SetPaperWidth(double aPaperWidth) override;
+ virtual nsresult SetPaperHeight(double aPaperWidth) override;
+
+ PMPrintSettings GetPMPrintSettings();
+ PMPrintSession GetPMPrintSession();
+ PMPageFormat GetPMPageFormat();
+ void SetPMPageFormat(PMPageFormat aPageFormat);
+
+ // Re-initialize mUnwriteableMargin with values from mPageFormat.
+ // Should be called whenever mPageFormat is initialized or overwritten.
+ nsresult InitUnwriteableMargin();
+
+ // Re-initialize mAdjustedPaper{Width,Height} with values from mPageFormat.
+ // Should be called whenever mPageFormat is initialized or overwritten.
+ nsresult InitAdjustedPaperSize();
+
+ void SetInchesScale(float aWidthScale, float aHeightScale);
+ void GetInchesScale(float *aWidthScale, float *aHeightScale);
+
+ void SetAdjustedPaperSize(double aWidth, double aHeight);
+ void GetAdjustedPaperSize(double *aWidth, double *aHeight);
+
+protected:
+ virtual ~nsPrintSettingsX();
+
+ nsPrintSettingsX(const nsPrintSettingsX& src);
+ nsPrintSettingsX& operator=(const nsPrintSettingsX& rhs);
+
+ nsresult _Clone(nsIPrintSettings **_retval) override;
+ nsresult _Assign(nsIPrintSettings *aPS) override;
+
+ // The out param has a ref count of 1 on return so caller needs to PMRelase() when done.
+ OSStatus CreateDefaultPageFormat(PMPrintSession aSession, PMPageFormat& outFormat);
+ OSStatus CreateDefaultPrintSettings(PMPrintSession aSession, PMPrintSettings& outSettings);
+
+ NSPrintInfo* mPrintInfo;
+
+ // Scaling factors used to convert the NSPrintInfo
+ // paper size units to inches
+ float mWidthScale;
+ float mHeightScale;
+ double mAdjustedPaperWidth;
+ double mAdjustedPaperHeight;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsX, NS_PRINTSETTINGSX_IID)
+
+#endif // nsPrintSettingsX_h_
diff --git a/widget/cocoa/nsPrintSettingsX.mm b/widget/cocoa/nsPrintSettingsX.mm
new file mode 100644
index 0000000000..73a8e78d2b
--- /dev/null
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsPrintSettingsX.h"
+#include "nsObjCExceptions.h"
+
+#include "plbase64.h"
+#include "plstr.h"
+
+#include "nsCocoaUtils.h"
+
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+#define MAC_OS_X_PAGE_SETUP_PREFNAME "print.macosx.pagesetup-2"
+#define COCOA_PAPER_UNITS_PER_INCH 72.0
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsX, nsPrintSettings, nsPrintSettingsX)
+
+nsPrintSettingsX::nsPrintSettingsX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mPrintInfo = [[NSPrintInfo sharedPrintInfo] copy];
+ mWidthScale = COCOA_PAPER_UNITS_PER_INCH;
+ mHeightScale = COCOA_PAPER_UNITS_PER_INCH;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsPrintSettingsX::nsPrintSettingsX(const nsPrintSettingsX& src)
+{
+ *this = src;
+}
+
+nsPrintSettingsX::~nsPrintSettingsX()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mPrintInfo release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsPrintSettingsX& nsPrintSettingsX::operator=(const nsPrintSettingsX& rhs)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (this == &rhs) {
+ return *this;
+ }
+
+ nsPrintSettings::operator=(rhs);
+
+ [mPrintInfo release];
+ mPrintInfo = [rhs.mPrintInfo copy];
+
+ return *this;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(*this);
+}
+
+nsresult nsPrintSettingsX::Init()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ InitUnwriteableMargin();
+ InitAdjustedPaperSize();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Should be called whenever the page format changes.
+NS_IMETHODIMP nsPrintSettingsX::InitUnwriteableMargin()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPaper paper;
+ PMPaperMargins paperMargin;
+ PMPageFormat pageFormat = GetPMPageFormat();
+ ::PMGetPageFormatPaper(pageFormat, &paper);
+ ::PMPaperGetMargins(paper, &paperMargin);
+ mUnwriteableMargin.top = NS_POINTS_TO_INT_TWIPS(paperMargin.top);
+ mUnwriteableMargin.left = NS_POINTS_TO_INT_TWIPS(paperMargin.left);
+ mUnwriteableMargin.bottom = NS_POINTS_TO_INT_TWIPS(paperMargin.bottom);
+ mUnwriteableMargin.right = NS_POINTS_TO_INT_TWIPS(paperMargin.right);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::InitAdjustedPaperSize()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPageFormat pageFormat = GetPMPageFormat();
+
+ PMRect paperRect;
+ ::PMGetAdjustedPaperRect(pageFormat, &paperRect);
+
+ mAdjustedPaperWidth = paperRect.right - paperRect.left;
+ mAdjustedPaperHeight = paperRect.bottom - paperRect.top;
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsPrintSettingsX::SetCocoaPrintInfo(NSPrintInfo* aPrintInfo)
+{
+ if (mPrintInfo != aPrintInfo) {
+ [mPrintInfo release];
+ mPrintInfo = [aPrintInfo retain];
+ }
+}
+
+NS_IMETHODIMP nsPrintSettingsX::ReadPageFormatFromPrefs()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsAutoCString encodedData;
+ nsresult rv =
+ Preferences::GetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, &encodedData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // decode the base64
+ char* decodedData = PL_Base64Decode(encodedData.get(), encodedData.Length(), nullptr);
+ NSData* data = [NSData dataWithBytes:decodedData length:strlen(decodedData)];
+ if (!data)
+ return NS_ERROR_FAILURE;
+
+ PMPageFormat newPageFormat;
+ OSStatus status = ::PMPageFormatCreateWithDataRepresentation((CFDataRef)data, &newPageFormat);
+ if (status == noErr) {
+ SetPMPageFormat(newPageFormat);
+ }
+ InitUnwriteableMargin();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::WritePageFormatToPrefs()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ PMPageFormat pageFormat = GetPMPageFormat();
+ if (pageFormat == kPMNoPageFormat)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSData* data = nil;
+ OSStatus err = ::PMPageFormatCreateDataRepresentation(pageFormat, (CFDataRef*)&data, kPMDataFormatXMLDefault);
+ if (err != noErr)
+ return NS_ERROR_FAILURE;
+
+ nsAutoCString encodedData;
+ encodedData.Adopt(PL_Base64Encode((char*)[data bytes], [data length], nullptr));
+ if (!encodedData.get())
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return Preferences::SetCString(MAC_OS_X_PAGE_SETUP_PREFNAME, encodedData);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult nsPrintSettingsX::_Clone(nsIPrintSettings **_retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsPrintSettingsX *newSettings = new nsPrintSettingsX(*this);
+ if (!newSettings)
+ return NS_ERROR_FAILURE;
+ *_retval = newSettings;
+ NS_ADDREF(*_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::_Assign(nsIPrintSettings *aPS)
+{
+ nsPrintSettingsX *printSettingsX = static_cast<nsPrintSettingsX*>(aPS);
+ if (!printSettingsX)
+ return NS_ERROR_UNEXPECTED;
+ *this = *printSettingsX;
+ return NS_OK;
+}
+
+PMPrintSettings
+nsPrintSettingsX::GetPMPrintSettings()
+{
+ return static_cast<PMPrintSettings>([mPrintInfo PMPrintSettings]);
+}
+
+PMPrintSession
+nsPrintSettingsX::GetPMPrintSession()
+{
+ return static_cast<PMPrintSession>([mPrintInfo PMPrintSession]);
+}
+
+PMPageFormat
+nsPrintSettingsX::GetPMPageFormat()
+{
+ return static_cast<PMPageFormat>([mPrintInfo PMPageFormat]);
+}
+
+void
+nsPrintSettingsX::SetPMPageFormat(PMPageFormat aPageFormat)
+{
+ PMPageFormat oldPageFormat = GetPMPageFormat();
+ ::PMCopyPageFormat(aPageFormat, oldPageFormat);
+ [mPrintInfo updateFromPMPageFormat];
+}
+
+void
+nsPrintSettingsX::SetInchesScale(float aWidthScale, float aHeightScale)
+{
+ if (aWidthScale > 0 && aHeightScale > 0) {
+ mWidthScale = aWidthScale;
+ mHeightScale = aHeightScale;
+ }
+}
+
+void
+nsPrintSettingsX::GetInchesScale(float *aWidthScale, float *aHeightScale)
+{
+ *aWidthScale = mWidthScale;
+ *aHeightScale = mHeightScale;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::SetPaperWidth(double aPaperWidth)
+{
+ mPaperWidth = aPaperWidth;
+ mAdjustedPaperWidth = aPaperWidth * mWidthScale;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsPrintSettingsX::SetPaperHeight(double aPaperHeight)
+{
+ mPaperHeight = aPaperHeight;
+ mAdjustedPaperHeight = aPaperHeight * mHeightScale;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsX::GetEffectivePageSize(double *aWidth, double *aHeight)
+{
+ *aWidth = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
+ *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+ return NS_OK;
+}
+
+void nsPrintSettingsX::SetAdjustedPaperSize(double aWidth, double aHeight)
+{
+ mAdjustedPaperWidth = aWidth;
+ mAdjustedPaperHeight = aHeight;
+}
+
+void nsPrintSettingsX::GetAdjustedPaperSize(double *aWidth, double *aHeight)
+{
+ *aWidth = mAdjustedPaperWidth;
+ *aHeight = mAdjustedPaperHeight;
+}
diff --git a/widget/cocoa/nsSandboxViolationSink.h b/widget/cocoa/nsSandboxViolationSink.h
new file mode 100644
index 0000000000..35b5d89af5
--- /dev/null
+++ b/widget/cocoa/nsSandboxViolationSink.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#ifndef nsSandboxViolationSink_h_
+#define nsSandboxViolationSink_h_
+
+#include <stdint.h>
+
+// Class for tracking sandbox violations. Currently it just logs them to
+// stdout and the system console. In the future it may do more.
+
+// What makes this possible is the fact that Apple' sandboxd calls
+// notify_post("com.apple.sandbox.violation.*") whenever it's notified by the
+// Sandbox kernel extension of a sandbox violation. We register to receive
+// these notifications. But the notifications are empty, and are sent for
+// every violation in every process. So we need to do more to get only "our"
+// violations, and to find out what kind of violation they were. See the
+// implementation of nsSandboxViolationSink::ViolationHandler().
+
+#define SANDBOX_VIOLATION_QUEUE_NAME "org.mozilla.sandbox.violation.queue"
+#define SANDBOX_VIOLATION_NOTIFICATION_NAME "com.apple.sandbox.violation.*"
+
+class nsSandboxViolationSink
+{
+public:
+ static void Start();
+ static void Stop();
+private:
+ static void ViolationHandler();
+ static int mNotifyToken;
+ static uint64_t mLastMsgReceived;
+};
+
+#endif // nsSandboxViolationSink_h_
diff --git a/widget/cocoa/nsSandboxViolationSink.mm b/widget/cocoa/nsSandboxViolationSink.mm
new file mode 100644
index 0000000000..0572173344
--- /dev/null
+++ b/widget/cocoa/nsSandboxViolationSink.mm
@@ -0,0 +1,115 @@
+/* -*- 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 "nsSandboxViolationSink.h"
+
+#include <unistd.h>
+#include <time.h>
+#include <asl.h>
+#include <dispatch/dispatch.h>
+#include <notify.h>
+#include "nsCocoaDebugUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Sprintf.h"
+
+int nsSandboxViolationSink::mNotifyToken = 0;
+uint64_t nsSandboxViolationSink::mLastMsgReceived = 0;
+
+void
+nsSandboxViolationSink::Start()
+{
+ if (mNotifyToken) {
+ return;
+ }
+ notify_register_dispatch(SANDBOX_VIOLATION_NOTIFICATION_NAME,
+ &mNotifyToken,
+ dispatch_queue_create(SANDBOX_VIOLATION_QUEUE_NAME,
+ DISPATCH_QUEUE_SERIAL),
+ ^(int token) { ViolationHandler(); });
+}
+
+void
+nsSandboxViolationSink::Stop()
+{
+ if (!mNotifyToken) {
+ return;
+ }
+ notify_cancel(mNotifyToken);
+ mNotifyToken = 0;
+}
+
+// We need to query syslogd to find out what violations occurred, and whether
+// they were "ours". We can use the Apple System Log facility to do this.
+// Besides calling notify_post("com.apple.sandbox.violation.*"), Apple's
+// sandboxd also reports all sandbox violations (sent to it by the Sandbox
+// kernel extension) to syslogd, which stores them and makes them viewable
+// in the system console. This is the database we query.
+
+// ViolationHandler() is always called on its own secondary thread. This
+// makes it unlikely it will interfere with other browser activity.
+
+void
+nsSandboxViolationSink::ViolationHandler()
+{
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+
+ asl_set_query(query, ASL_KEY_FACILITY, "com.apple.sandbox",
+ ASL_QUERY_OP_EQUAL);
+
+ // Only get reports that were generated very recently.
+ char query_time[30] = {0};
+ SprintfLiteral(query_time, "%li", time(NULL) - 2);
+ asl_set_query(query, ASL_KEY_TIME, query_time,
+ ASL_QUERY_OP_NUMERIC | ASL_QUERY_OP_GREATER_EQUAL);
+
+ // This code is easier to test if we don't just track "our" violations,
+ // which are (normally) few and far between. For example (for the time
+ // being at least) four appleeventsd sandbox violations happen every time
+ // we start the browser in e10s mode. But it makes sense to default to
+ // only tracking "our" violations.
+ if (mozilla::Preferences::GetBool(
+ "security.sandbox.mac.track.violations.oursonly", true)) {
+ // This makes each of our processes log its own violations. It might
+ // be better to make the chrome process log all the other processes'
+ // violations.
+ char query_pid[20] = {0};
+ SprintfLiteral(query_pid, "%u", getpid());
+ asl_set_query(query, ASL_KEY_REF_PID, query_pid, ASL_QUERY_OP_EQUAL);
+ }
+
+ aslresponse response = asl_search(nullptr, query);
+
+ // Each time ViolationHandler() is called we grab as many messages as are
+ // available. Otherwise we might not get them all.
+ if (response) {
+ while (true) {
+ aslmsg hit = nullptr;
+ aslmsg found = nullptr;
+ const char* id_str;
+
+ while ((hit = aslresponse_next(response))) {
+ // Record the message id to avoid logging the same violation more
+ // than once.
+ id_str = asl_get(hit, ASL_KEY_MSG_ID);
+ uint64_t id_val = atoll(id_str);
+ if (id_val <= mLastMsgReceived) {
+ continue;
+ }
+ mLastMsgReceived = id_val;
+ found = hit;
+ break;
+ }
+ if (!found) {
+ break;
+ }
+
+ const char* pid_str = asl_get(found, ASL_KEY_REF_PID);
+ const char* message_str = asl_get(found, ASL_KEY_MSG);
+ nsCocoaDebugUtils::DebugLog("nsSandboxViolationSink::ViolationHandler(): id %s, pid %s, message %s",
+ id_str, pid_str, message_str);
+ }
+ aslresponse_free(response);
+ }
+}
diff --git a/widget/cocoa/nsScreenCocoa.h b/widget/cocoa/nsScreenCocoa.h
new file mode 100644
index 0000000000..268d5beb0a
--- /dev/null
+++ b/widget/cocoa/nsScreenCocoa.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsScreenCocoa_h_
+#define nsScreenCocoa_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsBaseScreen.h"
+
+class nsScreenCocoa : public nsBaseScreen
+{
+public:
+ explicit nsScreenCocoa (NSScreen *screen);
+ ~nsScreenCocoa ();
+
+ NS_IMETHOD GetId(uint32_t* outId);
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor);
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor)
+ {
+ return GetContentsScaleFactor(aScaleFactor);
+ }
+
+ NSScreen *CocoaScreen() { return mScreen; }
+
+private:
+ CGFloat BackingScaleFactor();
+
+ NSScreen *mScreen;
+ uint32_t mId;
+};
+
+#endif // nsScreenCocoa_h_
diff --git a/widget/cocoa/nsScreenCocoa.mm b/widget/cocoa/nsScreenCocoa.mm
new file mode 100644
index 0000000000..08905bf0ad
--- /dev/null
+++ b/widget/cocoa/nsScreenCocoa.mm
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsScreenCocoa.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+
+static uint32_t sScreenId = 0;
+
+nsScreenCocoa::nsScreenCocoa (NSScreen *screen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ mScreen = [screen retain];
+ mId = ++sScreenId;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsScreenCocoa::~nsScreenCocoa ()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mScreen release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetId(uint32_t *outId)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *outId = mId;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen frame];
+
+ mozilla::LayoutDeviceIntRect r =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetAvailRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen visibleFrame];
+
+ mozilla::LayoutDeviceIntRect r =
+ nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen frame];
+
+ mozilla::DesktopIntRect r = nsCocoaUtils::CocoaRectToGeckoRect(frame);
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetAvailRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ NSRect frame = [mScreen visibleFrame];
+
+ mozilla::DesktopIntRect r = nsCocoaUtils::CocoaRectToGeckoRect(frame);
+
+ *outX = r.x;
+ *outY = r.y;
+ *outWidth = r.width;
+ *outHeight = r.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetPixelDepth(int32_t *aPixelDepth)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindowDepth depth = [mScreen depth];
+ int bpp = NSBitsPerPixelFromDepth(depth);
+
+ *aPixelDepth = bpp;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetColorDepth(int32_t *aColorDepth)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindowDepth depth = [mScreen depth];
+ int bpp = NSBitsPerPixelFromDepth (depth);
+
+ *aColorDepth = bpp;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenCocoa::GetContentsScaleFactor(double *aContentsScaleFactor)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ *aContentsScaleFactor = (double) BackingScaleFactor();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+CGFloat
+nsScreenCocoa::BackingScaleFactor()
+{
+ return nsCocoaUtils::GetBackingScaleFactor(mScreen);
+}
diff --git a/widget/cocoa/nsScreenManagerCocoa.h b/widget/cocoa/nsScreenManagerCocoa.h
new file mode 100644
index 0000000000..61a059d977
--- /dev/null
+++ b/widget/cocoa/nsScreenManagerCocoa.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsScreenManagerCocoa_h_
+#define nsScreenManagerCocoa_h_
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/RefPtr.h"
+#include "nsTArray.h"
+#include "nsIScreenManager.h"
+#include "nsScreenCocoa.h"
+
+class nsScreenManagerCocoa : public nsIScreenManager
+{
+public:
+ nsScreenManagerCocoa();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+protected:
+ virtual ~nsScreenManagerCocoa();
+
+private:
+
+ nsScreenCocoa *ScreenForCocoaScreen(NSScreen *screen);
+ nsTArray< RefPtr<nsScreenCocoa> > mScreenList;
+};
+
+#endif // nsScreenManagerCocoa_h_
diff --git a/widget/cocoa/nsScreenManagerCocoa.mm b/widget/cocoa/nsScreenManagerCocoa.mm
new file mode 100644
index 0000000000..9a0cbb9cc5
--- /dev/null
+++ b/widget/cocoa/nsScreenManagerCocoa.mm
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsScreenManagerCocoa.h"
+#include "nsObjCExceptions.h"
+#include "nsCOMPtr.h"
+#include "nsCocoaUtils.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsScreenManagerCocoa, nsIScreenManager)
+
+nsScreenManagerCocoa::nsScreenManagerCocoa()
+{
+}
+
+nsScreenManagerCocoa::~nsScreenManagerCocoa()
+{
+}
+
+nsScreenCocoa*
+nsScreenManagerCocoa::ScreenForCocoaScreen(NSScreen *screen)
+{
+ for (uint32_t i = 0; i < mScreenList.Length(); ++i) {
+ nsScreenCocoa* sc = mScreenList[i];
+ if (sc->CocoaScreen() == screen) {
+ // doesn't addref
+ return sc;
+ }
+ }
+
+ // didn't find it; create and insert
+ RefPtr<nsScreenCocoa> sc = new nsScreenCocoa(screen);
+ mScreenList.AppendElement(sc);
+ return sc.get();
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForId (uint32_t aId, nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT
+
+ *outScreen = nullptr;
+
+ for (uint32_t i = 0; i < mScreenList.Length(); ++i) {
+ nsScreenCocoa* sc = mScreenList[i];
+ uint32_t id;
+ nsresult rv = sc->GetId(&id);
+
+ if (NS_SUCCEEDED(rv) && id == aId) {
+ *outScreen = sc;
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForRect (int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight,
+ nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+ NSRect inRect =
+ nsCocoaUtils::GeckoRectToCocoaRect(DesktopIntRect(aX, aY,
+ aWidth, aHeight));
+ NSScreen *screenWindowIsOn = [NSScreen mainScreen];
+ float greatestArea = 0;
+
+ while (NSScreen *screen = [screenEnum nextObject]) {
+ NSDictionary *desc = [screen deviceDescription];
+ if ([desc objectForKey:NSDeviceIsScreen] == nil)
+ continue;
+
+ NSRect r = NSIntersectionRect([screen frame], inRect);
+ float area = r.size.width * r.size.height;
+ if (area > greatestArea) {
+ greatestArea = area;
+ screenWindowIsOn = screen;
+ }
+ }
+
+ *outScreen = ScreenForCocoaScreen(screenWindowIsOn);
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetPrimaryScreen (nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // the mainScreen is the screen with the "key window" (focus, I assume?)
+ NSScreen *sc = [[NSScreen screens] objectAtIndex:0];
+
+ *outScreen = ScreenForCocoaScreen(sc);
+ NS_ADDREF(*outScreen);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetNumberOfScreens (uint32_t *aNumberOfScreens)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSArray *ss = [NSScreen screens];
+
+ *aNumberOfScreens = [ss count];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = 1.0f;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerCocoa::ScreenForNativeWidget (void *nativeWidget, nsIScreen **outScreen)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSWindow *window = static_cast<NSWindow*>(nativeWidget);
+ if (window) {
+ nsIScreen *screen = ScreenForCocoaScreen([window screen]);
+ *outScreen = screen;
+ NS_ADDREF(*outScreen);
+ return NS_OK;
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsSound.h b/widget/cocoa/nsSound.h
new file mode 100644
index 0000000000..0e0293ae28
--- /dev/null
+++ b/widget/cocoa/nsSound.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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/. */
+
+#ifndef nsSound_h_
+#define nsSound_h_
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+
+class nsSound : public nsISound,
+ public nsIStreamLoaderObserver
+{
+public:
+ nsSound();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+protected:
+ virtual ~nsSound();
+};
+
+#endif // nsSound_h_
diff --git a/widget/cocoa/nsSound.mm b/widget/cocoa/nsSound.mm
new file mode 100644
index 0000000000..04c6b4d764
--- /dev/null
+++ b/widget/cocoa/nsSound.mm
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "nsSound.h"
+#include "nsContentUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsIURL.h"
+#include "nsString.h"
+
+#import <Cocoa/Cocoa.h>
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
+
+nsSound::nsSound()
+{
+}
+
+nsSound::~nsSound()
+{
+}
+
+NS_IMETHODIMP
+nsSound::Beep()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSBeep();
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
+ nsISupports *context,
+ nsresult aStatus,
+ uint32_t dataLen,
+ const uint8_t *data)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ NSData *value = [NSData dataWithBytes:data length:dataLen];
+
+ NSSound *sound = [[NSSound alloc] initWithData:value];
+
+ [sound play];
+
+ [sound autorelease];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::Play(nsIURL *aURL)
+{
+ nsCOMPtr<nsIURI> uri(do_QueryInterface(aURL));
+ nsCOMPtr<nsIStreamLoader> loader;
+ return NS_NewStreamLoader(getter_AddRefs(loader),
+ uri,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+}
+
+NS_IMETHODIMP
+nsSound::Init()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSound::PlaySystemSound(const nsAString &aSoundAlias)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_IsMozAliasSound(aSoundAlias)) {
+ NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead");
+ // Mac doesn't have system sound settings for each user actions.
+ return NS_OK;
+ }
+
+ NSString *name = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(aSoundAlias.BeginReading())
+ length:aSoundAlias.Length()];
+ NSSound *sound = [NSSound soundNamed:name];
+ if (sound) {
+ [sound stop];
+ [sound play];
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSound::PlayEventSound(uint32_t aEventId)
+{
+ // Mac doesn't have system sound settings for each user actions.
+ return NS_OK;
+}
diff --git a/widget/cocoa/nsStandaloneNativeMenu.h b/widget/cocoa/nsStandaloneNativeMenu.h
new file mode 100644
index 0000000000..e03742b1e0
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+#ifndef nsStandaloneNativeMenu_h_
+#define nsStandaloneNativeMenu_h_
+
+#include "nsMenuGroupOwnerX.h"
+#include "nsMenuX.h"
+#include "nsIStandaloneNativeMenu.h"
+
+class nsStandaloneNativeMenu : public nsMenuGroupOwnerX, public nsIStandaloneNativeMenu
+{
+public:
+ nsStandaloneNativeMenu();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISTANDALONENATIVEMENU
+
+ // nsMenuObjectX
+ nsMenuObjectTypeX MenuObjectType() override { return eStandaloneNativeMenuObjectType; }
+ void * NativeData() override { return mMenu != nullptr ? mMenu->NativeData() : nullptr; }
+ virtual void IconUpdated() override;
+
+ nsMenuX * GetMenuXObject() { return mMenu; }
+
+ // If this menu is the menu of a system status bar item (NSStatusItem),
+ // let the menu know about the status item so that it can propagate
+ // any icon changes to the status item.
+ void SetContainerStatusBarItem(NSStatusItem* aItem);
+
+protected:
+ virtual ~nsStandaloneNativeMenu();
+
+ nsMenuX * mMenu;
+ NSStatusItem* mContainerStatusBarItem;
+};
+
+#endif
diff --git a/widget/cocoa/nsStandaloneNativeMenu.mm b/widget/cocoa/nsStandaloneNativeMenu.mm
new file mode 100644
index 0000000000..98a5fd8f6f
--- /dev/null
+++ b/widget/cocoa/nsStandaloneNativeMenu.mm
@@ -0,0 +1,213 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsStandaloneNativeMenu.h"
+#include "nsMenuUtilsX.h"
+#include "nsIDOMElement.h"
+#include "nsIMutationObserver.h"
+#include "nsGkAtoms.h"
+#include "nsObjCExceptions.h"
+
+
+NS_IMPL_ISUPPORTS_INHERITED(nsStandaloneNativeMenu, nsMenuGroupOwnerX,
+ nsIMutationObserver, nsIStandaloneNativeMenu)
+
+nsStandaloneNativeMenu::nsStandaloneNativeMenu()
+: mMenu(nullptr)
+, mContainerStatusBarItem(nil)
+{
+}
+
+nsStandaloneNativeMenu::~nsStandaloneNativeMenu()
+{
+ if (mMenu) delete mMenu;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::Init(nsIDOMElement * aDOMElement)
+{
+ NS_ASSERTION(mMenu == nullptr, "nsNativeMenu::Init - mMenu not null!");
+
+ nsresult rv;
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!content->IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menupopup))
+ return NS_ERROR_FAILURE;
+
+ rv = nsMenuGroupOwnerX::Create(content);
+ if (NS_FAILED(rv))
+ return rv;
+
+ mMenu = new nsMenuX();
+ rv = mMenu->Create(this, this, content);
+ if (NS_FAILED(rv)) {
+ delete mMenu;
+ mMenu = nullptr;
+ return rv;
+ }
+
+ mMenu->SetupIcon();
+
+ return NS_OK;
+}
+
+static void
+UpdateMenu(nsMenuX * aMenu)
+{
+ aMenu->MenuOpened();
+ aMenu->MenuClosed();
+
+ uint32_t itemCount = aMenu->GetItemCount();
+ for (uint32_t i = 0; i < itemCount; i++) {
+ nsMenuObjectX * menuObject = aMenu->GetItemAt(i);
+ if (menuObject->MenuObjectType() == eSubmenuObjectType) {
+ UpdateMenu(static_cast<nsMenuX*>(menuObject));
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::MenuWillOpen(bool * aResult)
+{
+ NS_ASSERTION(mMenu != nullptr, "nsStandaloneNativeMenu::OnOpen - mMenu is null!");
+
+ // Force an update on the mMenu by faking an open/close on all of
+ // its submenus.
+ UpdateMenu(mMenu);
+
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::GetNativeMenu(void ** aVoidPointer)
+{
+ if (mMenu) {
+ *aVoidPointer = mMenu->NativeData();
+ [[(NSObject *)(*aVoidPointer) retain] autorelease];
+ return NS_OK;
+ } else {
+ *aVoidPointer = nullptr;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+}
+
+static NSMenuItem *
+NativeMenuItemWithLocation(NSMenu * currentSubmenu, NSString * locationString)
+{
+ NSArray * indexes = [locationString componentsSeparatedByString:@"|"];
+ NSUInteger indexCount = [indexes count];
+ if (indexCount == 0)
+ return nil;
+
+ for (NSUInteger i = 0; i < indexCount; i++) {
+ NSInteger targetIndex = [[indexes objectAtIndex:i] integerValue];
+ NSInteger itemCount = [currentSubmenu numberOfItems];
+ if (targetIndex < itemCount) {
+ NSMenuItem* menuItem = [currentSubmenu itemAtIndex:targetIndex];
+
+ // If this is the last index, just return the menu item.
+ if (i == (indexCount - 1))
+ return menuItem;
+
+ // If this is not the last index, find the submenu and keep going.
+ if ([menuItem hasSubmenu])
+ currentSubmenu = [menuItem submenu];
+ else
+ return nil;
+ }
+ }
+
+ return nil;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ActivateNativeMenuItemAt(const nsAString& indexString)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (!mMenu)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSString * locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSMenu * menu = static_cast<NSMenu *> (mMenu->NativeData());
+ NSMenuItem * item = NativeMenuItemWithLocation(menu, locationString);
+
+ // We can't perform an action on an item with a submenu, that will raise
+ // an obj-c exception.
+ if (item && ![item hasSubmenu]) {
+ NSMenu * parent = [item menu];
+ if (parent) {
+ // NSLog(@"Performing action for native menu item titled: %@\n",
+ // [[currentSubmenu itemAtIndex:targetIndex] title]);
+ [parent performActionForItemAtIndex:[parent indexOfItem:item]];
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsStandaloneNativeMenu::ForceUpdateNativeMenuAt(const nsAString& indexString)
+{
+ if (!mMenu)
+ return NS_ERROR_NOT_INITIALIZED;
+
+ NSString* locationString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(indexString.BeginReading())
+ length:indexString.Length()];
+ NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
+ unsigned int indexCount = [indexes count];
+ if (indexCount == 0)
+ return NS_OK;
+
+ nsMenuX* currentMenu = mMenu;
+
+ // now find the correct submenu
+ for (unsigned int i = 1; currentMenu && i < indexCount; i++) {
+ int targetIndex = [[indexes objectAtIndex:i] intValue];
+ int visible = 0;
+ uint32_t length = currentMenu->GetItemCount();
+ for (unsigned int j = 0; j < length; j++) {
+ nsMenuObjectX* targetMenu = currentMenu->GetItemAt(j);
+ if (!targetMenu)
+ return NS_OK;
+ if (!nsMenuUtilsX::NodeIsHiddenOrCollapsed(targetMenu->Content())) {
+ visible++;
+ if (targetMenu->MenuObjectType() == eSubmenuObjectType && visible == (targetIndex + 1)) {
+ currentMenu = static_cast<nsMenuX*>(targetMenu);
+ // fake open/close to cause lazy update to happen
+ currentMenu->MenuOpened();
+ currentMenu->MenuClosed();
+ break;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+nsStandaloneNativeMenu::IconUpdated()
+{
+ if (mContainerStatusBarItem) {
+ [mContainerStatusBarItem setImage:[mMenu->NativeMenuItem() image]];
+ }
+}
+
+void
+nsStandaloneNativeMenu::SetContainerStatusBarItem(NSStatusItem* aItem)
+{
+ mContainerStatusBarItem = aItem;
+ IconUpdated();
+}
diff --git a/widget/cocoa/nsSystemStatusBarCocoa.h b/widget/cocoa/nsSystemStatusBarCocoa.h
new file mode 100644
index 0000000000..51aa4df00d
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.h
@@ -0,0 +1,40 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+#ifndef nsSystemStatusBarCocoa_h_
+#define nsSystemStatusBarCocoa_h_
+
+#include "mozilla/RefPtr.h"
+#include "nsISystemStatusBar.h"
+#include "nsClassHashtable.h"
+
+class nsStandaloneNativeMenu;
+@class NSStatusItem;
+
+class nsSystemStatusBarCocoa : public nsISystemStatusBar
+{
+public:
+ nsSystemStatusBarCocoa() {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISYSTEMSTATUSBAR
+
+protected:
+ virtual ~nsSystemStatusBarCocoa() {}
+
+ struct StatusItem
+ {
+ explicit StatusItem(nsStandaloneNativeMenu* aMenu);
+ ~StatusItem();
+
+ private:
+ RefPtr<nsStandaloneNativeMenu> mMenu;
+ NSStatusItem* mStatusItem;
+ };
+
+ nsClassHashtable<nsISupportsHashKey, StatusItem> mItems;
+};
+
+#endif // nsSystemStatusBarCocoa_h_
diff --git a/widget/cocoa/nsSystemStatusBarCocoa.mm b/widget/cocoa/nsSystemStatusBarCocoa.mm
new file mode 100644
index 0000000000..522da71451
--- /dev/null
+++ b/widget/cocoa/nsSystemStatusBarCocoa.mm
@@ -0,0 +1,74 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "nsComponentManagerUtils.h"
+#include "nsSystemStatusBarCocoa.h"
+#include "nsStandaloneNativeMenu.h"
+#include "nsObjCExceptions.h"
+#include "nsIDOMElement.h"
+
+NS_IMPL_ISUPPORTS(nsSystemStatusBarCocoa, nsISystemStatusBar)
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::AddItem(nsIDOMElement* aDOMElement)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ RefPtr<nsStandaloneNativeMenu> menu = new nsStandaloneNativeMenu();
+ nsresult rv = menu->Init(aDOMElement);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISupports> keyPtr = aDOMElement;
+ mItems.Put(keyPtr, new StatusItem(menu));
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsSystemStatusBarCocoa::RemoveItem(nsIDOMElement* aDOMElement)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ mItems.Remove(aDOMElement);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsSystemStatusBarCocoa::StatusItem::StatusItem(nsStandaloneNativeMenu* aMenu)
+ : mMenu(aMenu)
+{
+ MOZ_COUNT_CTOR(nsSystemStatusBarCocoa::StatusItem);
+
+ NSMenu* nativeMenu = nil;
+ mMenu->GetNativeMenu(reinterpret_cast<void**>(&nativeMenu));
+
+ mStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
+ [mStatusItem setMenu:nativeMenu];
+ [mStatusItem setHighlightMode:YES];
+
+ // We want the status item to get its image from menu item that mMenu was
+ // initialized with. Icon loads are asynchronous, so we need to let the menu
+ // know about the item so that it can update its icon as soon as it has
+ // loaded.
+ mMenu->SetContainerStatusBarItem(mStatusItem);
+}
+
+nsSystemStatusBarCocoa::StatusItem::~StatusItem()
+{
+ mMenu->SetContainerStatusBarItem(nil);
+ [[NSStatusBar systemStatusBar] removeStatusItem:mStatusItem];
+ [mStatusItem release];
+ mStatusItem = nil;
+
+ MOZ_COUNT_DTOR(nsSystemStatusBarCocoa::StatusItem);
+}
diff --git a/widget/cocoa/nsToolkit.h b/widget/cocoa/nsToolkit.h
new file mode 100644
index 0000000000..1631a8ac24
--- /dev/null
+++ b/widget/cocoa/nsToolkit.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsToolkit_h_
+#define nsToolkit_h_
+
+#include "nscore.h"
+
+#import <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+#import <objc/Object.h>
+#import <IOKit/IOKitLib.h>
+
+class nsToolkit
+{
+public:
+ nsToolkit();
+ virtual ~nsToolkit();
+
+ static nsToolkit* GetToolkit();
+
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ static void PostSleepWakeNotification(const char* aNotification);
+
+ static nsresult SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
+ bool classMethods = false);
+
+ void RegisterForAllProcessMouseEvents();
+ void UnregisterAllProcessMouseEventHandlers();
+
+protected:
+
+ nsresult RegisterForSleepWakeNotifications();
+ void RemoveSleepWakeNotifications();
+
+protected:
+
+ static nsToolkit* gToolkit;
+
+ CFRunLoopSourceRef mSleepWakeNotificationRLS;
+ io_object_t mPowerNotifier;
+
+ CFMachPortRef mEventTapPort;
+ CFRunLoopSourceRef mEventTapRLS;
+};
+
+#endif // nsToolkit_h_
diff --git a/widget/cocoa/nsToolkit.mm b/widget/cocoa/nsToolkit.mm
new file mode 100644
index 0000000000..4d0222d5d3
--- /dev/null
+++ b/widget/cocoa/nsToolkit.mm
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsToolkit.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <mach/mach_port.h>
+#include <mach/mach_interface.h>
+#include <mach/mach_init.h>
+
+extern "C" {
+#include <mach-o/getsect.h>
+}
+#include <unistd.h>
+#include <dlfcn.h>
+
+#import <Cocoa/Cocoa.h>
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#import <IOKit/IOMessage.h>
+
+#include "nsCocoaUtils.h"
+#include "nsObjCExceptions.h"
+
+#include "nsGkAtoms.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsBaseWidget.h"
+
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+static io_connect_t gRootPort = MACH_PORT_NULL;
+
+nsToolkit* nsToolkit::gToolkit = nullptr;
+
+nsToolkit::nsToolkit()
+: mSleepWakeNotificationRLS(nullptr)
+, mEventTapPort(nullptr)
+, mEventTapRLS(nullptr)
+{
+ MOZ_COUNT_CTOR(nsToolkit);
+ RegisterForSleepWakeNotifications();
+}
+
+nsToolkit::~nsToolkit()
+{
+ MOZ_COUNT_DTOR(nsToolkit);
+ RemoveSleepWakeNotifications();
+ UnregisterAllProcessMouseEventHandlers();
+}
+
+void
+nsToolkit::PostSleepWakeNotification(const char* aNotification)
+{
+ nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(nullptr, aNotification, nullptr);
+}
+
+// http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/PowerMgmt/chapter_10_section_3.html
+static void ToolkitSleepWakeCallback(void *refCon, io_service_t service, natural_t messageType, void * messageArgument)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ switch (messageType)
+ {
+ case kIOMessageSystemWillSleep:
+ // System is going to sleep now.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_SLEEP_OBSERVER_TOPIC);
+ ::IOAllowPowerChange(gRootPort, (long)messageArgument);
+ break;
+
+ case kIOMessageCanSystemSleep:
+ // In this case, the computer has been idle for several minutes
+ // and will sleep soon so you must either allow or cancel
+ // this notification. Important: if you don’t respond, there will
+ // be a 30-second timeout before the computer sleeps.
+ // In Mozilla's case, we always allow sleep.
+ ::IOAllowPowerChange(gRootPort,(long)messageArgument);
+ break;
+
+ case kIOMessageSystemHasPoweredOn:
+ // Handle wakeup.
+ nsToolkit::PostSleepWakeNotification(NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ break;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsresult
+nsToolkit::RegisterForSleepWakeNotifications()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ IONotificationPortRef notifyPortRef;
+
+ NS_ASSERTION(!mSleepWakeNotificationRLS, "Already registered for sleep/wake");
+
+ gRootPort = ::IORegisterForSystemPower(0, &notifyPortRef, ToolkitSleepWakeCallback, &mPowerNotifier);
+ if (gRootPort == MACH_PORT_NULL) {
+ NS_ERROR("IORegisterForSystemPower failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ mSleepWakeNotificationRLS = ::IONotificationPortGetRunLoopSource(notifyPortRef);
+ ::CFRunLoopAddSource(::CFRunLoopGetCurrent(),
+ mSleepWakeNotificationRLS,
+ kCFRunLoopDefaultMode);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void
+nsToolkit::RemoveSleepWakeNotifications()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mSleepWakeNotificationRLS) {
+ ::IODeregisterForSystemPower(&mPowerNotifier);
+ ::CFRunLoopRemoveSource(::CFRunLoopGetCurrent(),
+ mSleepWakeNotificationRLS,
+ kCFRunLoopDefaultMode);
+
+ mSleepWakeNotificationRLS = nullptr;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Converts aPoint from the CoreGraphics "global display coordinate" system
+// (which includes all displays/screens and has a top-left origin) to its
+// (presumed) Cocoa counterpart (assumed to be the same as the "screen
+// coordinates" system), which has a bottom-left origin.
+static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint)
+{
+ NSPoint cocoaPoint;
+ cocoaPoint.x = aPoint.x;
+ cocoaPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
+ return cocoaPoint;
+}
+
+// Since our event tap is "listen only", events arrive here a little after
+// they've already been processed.
+static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ((type == kCGEventTapDisabledByUserInput) ||
+ (type == kCGEventTapDisabledByTimeout))
+ return event;
+ if ([NSApp isActive])
+ return event;
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ NS_ENSURE_TRUE(rollupListener, event);
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (!rollupWidget)
+ return event;
+
+ // Don't bother with rightMouseDown events here -- because of the delay,
+ // we'll end up closing browser context menus that we just opened. Since
+ // these events usually raise a context menu, we'll handle them by hooking
+ // the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed
+ // notification (in nsAppShell.mm's AppShellDelegate).
+ if (type == kCGEventRightMouseDown)
+ return event;
+ NSWindow *ctxMenuWindow = (NSWindow*) rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!ctxMenuWindow)
+ return event;
+ NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event));
+ // Don't roll up the rollup widget if our mouseDown happens over it (doing
+ // so would break the corresponding context menu).
+ if (NSPointInRect(screenLocation, [ctxMenuWindow frame]))
+ return event;
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ return event;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NULL);
+}
+
+// Cocoa Firefox's use of custom context menus requires that we explicitly
+// handle mouse events from other processes that the OS handles
+// "automatically" for native context menus -- mouseMoved events so that
+// right-click context menus work properly when our browser doesn't have the
+// focus (bmo bug 368077), and mouseDown events so that our browser can
+// dismiss a context menu when a mouseDown happens in another process (bmo
+// bug 339945).
+void
+nsToolkit::RegisterForAllProcessMouseEvents()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (getenv("MOZ_DEBUG"))
+ return;
+
+ // Don't do this for apps that use native context menus.
+#ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
+ return;
+#endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
+
+ if (!mEventTapRLS) {
+ // Using an event tap for mouseDown events (instead of installing a
+ // handler for them on the EventMonitor target) works around an Apple
+ // bug that causes OS menus (like the Clock menu) not to work properly
+ // on OS X 10.4.X and below (bmo bug 381448).
+ // We install our event tap "listen only" to get around yet another Apple
+ // bug -- when we install it as an event filter on any kind of mouseDown
+ // event, that kind of event stops working in the main menu, and usually
+ // mouse event processing stops working in all apps in the current login
+ // session (so the entire OS appears to be hung)! The downside of
+ // installing listen-only is that events arrive at our handler slightly
+ // after they've already been processed.
+ mEventTapPort = CGEventTapCreate(kCGSessionEventTap,
+ kCGHeadInsertEventTap,
+ kCGEventTapOptionListenOnly,
+ CGEventMaskBit(kCGEventLeftMouseDown)
+ | CGEventMaskBit(kCGEventRightMouseDown)
+ | CGEventMaskBit(kCGEventOtherMouseDown),
+ EventTapCallback,
+ nullptr);
+ if (!mEventTapPort)
+ return;
+ mEventTapRLS = CFMachPortCreateRunLoopSource(nullptr, mEventTapPort, 0);
+ if (!mEventTapRLS) {
+ CFRelease(mEventTapPort);
+ mEventTapPort = nullptr;
+ return;
+ }
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode);
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsToolkit::UnregisterAllProcessMouseEventHandlers()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (mEventTapRLS) {
+ CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS,
+ kCFRunLoopDefaultMode);
+ CFRelease(mEventTapRLS);
+ mEventTapRLS = nullptr;
+ }
+ if (mEventTapPort) {
+ // mEventTapPort must be invalidated as well as released. Otherwise the
+ // event tap doesn't get destroyed until the browser process ends (it
+ // keeps showing up in the list returned by CGGetEventTapList()).
+ CFMachPortInvalidate(mEventTapPort);
+ CFRelease(mEventTapPort);
+ mEventTapPort = nullptr;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Return the nsToolkit instance. If a toolkit does not yet exist, then one
+// will be created.
+// static
+nsToolkit* nsToolkit::GetToolkit()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if (!gToolkit) {
+ gToolkit = new nsToolkit();
+ }
+
+ return gToolkit;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nullptr);
+}
+
+// An alternative to [NSObject poseAsClass:] that isn't deprecated on OS X
+// Leopard and is available to 64-bit binaries on Leopard and above. Based on
+// ideas and code from http://www.cocoadev.com/index.pl?MethodSwizzling.
+// Since the Method type becomes an opaque type as of Objective-C 2.0, we'll
+// have to switch to using accessor methods like method_exchangeImplementations()
+// when we build 64-bit binaries that use Objective-C 2.0 (on and for Leopard
+// and above).
+//
+// Be aware that, if aClass doesn't have an orgMethod selector but one of its
+// superclasses does, the method substitution will (in effect) take place in
+// that superclass (rather than in aClass itself). The substitution has
+// effect on the class where it takes place and all of that class's
+// subclasses. In order for method swizzling to work properly, posedMethod
+// needs to be unique in the class where the substitution takes place and all
+// of its subclasses.
+nsresult nsToolkit::SwizzleMethods(Class aClass, SEL orgMethod, SEL posedMethod,
+ bool classMethods)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ Method original = nil;
+ Method posed = nil;
+
+ if (classMethods) {
+ original = class_getClassMethod(aClass, orgMethod);
+ posed = class_getClassMethod(aClass, posedMethod);
+ } else {
+ original = class_getInstanceMethod(aClass, orgMethod);
+ posed = class_getInstanceMethod(aClass, posedMethod);
+ }
+
+ if (!original || !posed)
+ return NS_ERROR_FAILURE;
+
+ method_exchangeImplementations(original, posed);
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
diff --git a/widget/cocoa/nsWidgetFactory.mm b/widget/cocoa/nsWidgetFactory.mm
new file mode 100644
index 0000000000..3bddaf95ce
--- /dev/null
+++ b/widget/cocoa/nsWidgetFactory.mm
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 4; 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 "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsIComponentManager.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "nsWidgetsCID.h"
+
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsFilePicker.h"
+#include "nsColorPicker.h"
+
+#include "nsClipboard.h"
+#include "nsClipboardHelper.h"
+#include "nsTransferable.h"
+#include "nsHTMLFormatConverter.h"
+#include "nsDragService.h"
+#include "nsToolkit.h"
+
+#include "nsLookAndFeel.h"
+
+#include "nsSound.h"
+#include "nsIdleServiceX.h"
+#include "NativeKeyBindings.h"
+#include "OSXNotificationCenter.h"
+
+#include "nsScreenManagerCocoa.h"
+#include "nsDeviceContextSpecX.h"
+#include "nsPrintOptionsX.h"
+#include "nsPrintDialogX.h"
+#include "nsPrintSession.h"
+#include "nsToolkitCompsCID.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCocoaWindow)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildView)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFilePicker)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsColorPicker)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSound)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDragService)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerCocoa)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDeviceContextSpecX)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceX, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceX, nsIdleServiceX::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(OSXNotificationCenter, Init)
+
+#include "nsMenuBarX.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeMenuServiceX)
+
+#include "nsBidiKeyboard.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsBidiKeyboard)
+
+#include "nsNativeThemeCocoa.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeThemeCocoa)
+
+#include "nsMacDockSupport.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacDockSupport)
+
+#include "nsMacWebAppUtils.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacWebAppUtils)
+
+#include "nsStandaloneNativeMenu.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsStandaloneNativeMenu)
+
+#include "nsSystemStatusBarCocoa.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSystemStatusBarCocoa)
+
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+} // namespace widget
+} // namespace mozilla
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_POPUP_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SOUND_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID);
+NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID);
+NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID);
+NS_DEFINE_NAMED_CID(NS_MACSYSTEMSTATUSBAR_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, NULL, nsCocoaWindowConstructor },
+ { &kNS_POPUP_CID, false, NULL, nsCocoaWindowConstructor },
+ { &kNS_CHILD_CID, false, NULL, nsChildViewConstructor },
+ { &kNS_FILEPICKER_CID, false, NULL, nsFilePickerConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_COLORPICKER_CID, false, NULL, nsColorPickerConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_APPSHELL_CID, false, NULL, nsAppShellConstructor, mozilla::Module::ALLOW_IN_GPU_PROCESS },
+ { &kNS_SOUND_CID, false, NULL, nsSoundConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_TRANSFERABLE_CID, false, NULL, nsTransferableConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, NULL, nsHTMLFormatConverterConstructor },
+ { &kNS_CLIPBOARD_CID, false, NULL, nsClipboardConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_CLIPBOARDHELPER_CID, false, NULL, nsClipboardHelperConstructor },
+ { &kNS_DRAGSERVICE_CID, false, NULL, nsDragServiceConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_BIDIKEYBOARD_CID, false, NULL, nsBidiKeyboardConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_THEMERENDERER_CID, false, NULL, nsNativeThemeCocoaConstructor },
+ { &kNS_SCREENMANAGER_CID, false, NULL, nsScreenManagerCocoaConstructor,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { &kNS_DEVICE_CONTEXT_SPEC_CID, false, NULL, nsDeviceContextSpecXConstructor },
+ { &kNS_PRINTSESSION_CID, false, NULL, nsPrintSessionConstructor },
+ { &kNS_PRINTSETTINGSSERVICE_CID, false, NULL, nsPrintOptionsXConstructor },
+ { &kNS_PRINTDIALOGSERVICE_CID, false, NULL, nsPrintDialogServiceXConstructor },
+ { &kNS_IDLE_SERVICE_CID, false, NULL, nsIdleServiceXConstructor },
+ { &kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor },
+ { &kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor },
+ { &kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor },
+ { &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor },
+ { &kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor },
+ { &kNS_MACSYSTEMSTATUSBAR_CID, false, NULL, nsSystemStatusBarCocoaConstructor },
+ { &kNS_GFXINFO_CID, false, NULL, mozilla::widget::GfxInfoConstructor },
+ { NULL }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/mac;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/popup/mac;1", &kNS_POPUP_CID },
+ { "@mozilla.org/widgets/childwindow/mac;1", &kNS_CHILD_CID },
+ { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/appshell/mac;1", &kNS_APPSHELL_CID, mozilla::Module::ALLOW_IN_GPU_PROCESS },
+ { "@mozilla.org/sound;1", &kNS_SOUND_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID,
+ mozilla::Module::MAIN_PROCESS_ONLY },
+ { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
+ { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
+ { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
+ { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/system-alerts-service;1", &kNS_SYSTEMALERTSSERVICE_CID },
+ { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID },
+ { "@mozilla.org/widget/macdocksupport;1", &kNS_MACDOCKSUPPORT_CID },
+ { "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID },
+ { "@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID },
+ { "@mozilla.org/widget/macsystemstatusbar;1", &kNS_MACSYSTEMSTATUSBAR_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { NULL }
+};
+
+static void
+nsWidgetCocoaModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ NativeKeyBindings::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsToolkit::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ NULL,
+ NULL,
+ nsAppShellInit,
+ nsWidgetCocoaModuleDtor,
+ mozilla::Module::ALLOW_IN_GPU_PROCESS
+};
+
+NSMODULE_DEFN(nsWidgetMacModule) = &kWidgetModule;
diff --git a/widget/cocoa/nsWindowMap.h b/widget/cocoa/nsWindowMap.h
new file mode 100644
index 0000000000..c6ad72c010
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef nsWindowMap_h_
+#define nsWindowMap_h_
+
+#import <Cocoa/Cocoa.h>
+
+// WindowDataMap
+//
+// In both mozilla and embedding apps, we need to have a place to put
+// per-top-level-window logic and data, to handle such things as IME
+// commit when the window gains/loses focus. We can't use a window
+// delegate, because an embeddor probably already has one. Nor can we
+// subclass NSWindow, again because we can't impose that burden on the
+// embeddor.
+//
+// So we have a global map of NSWindow -> TopLevelWindowData, and set
+// up TopLevelWindowData as a notification observer etc.
+
+@interface WindowDataMap : NSObject
+{
+@private
+ NSMutableDictionary* mWindowMap; // dict of TopLevelWindowData keyed by address of NSWindow
+}
+
++ (WindowDataMap*)sharedWindowDataMap;
+
+- (void)ensureDataForWindow:(NSWindow*)inWindow;
+- (id)dataForWindow:(NSWindow*)inWindow;
+
+// set data for a given window. inData is retained (and any previously set data
+// is released).
+- (void)setData:(id)inData forWindow:(NSWindow*)inWindow;
+
+// remove the data for the given window. the data is released.
+- (void)removeDataForWindow:(NSWindow*)inWindow;
+
+@end
+
+@class ChildView;
+
+// TopLevelWindowData
+//
+// Class to hold per-window data, and handle window state changes.
+
+@interface TopLevelWindowData : NSObject
+{
+@private
+}
+
+- (id)initWithWindow:(NSWindow*)inWindow;
++ (void)activateInWindow:(NSWindow*)aWindow;
++ (void)deactivateInWindow:(NSWindow*)aWindow;
++ (void)activateInWindowViews:(NSWindow*)aWindow;
++ (void)deactivateInWindowViews:(NSWindow*)aWindow;
+
+@end
+
+#endif // nsWindowMap_h_
diff --git a/widget/cocoa/nsWindowMap.mm b/widget/cocoa/nsWindowMap.mm
new file mode 100644
index 0000000000..c43b024086
--- /dev/null
+++ b/widget/cocoa/nsWindowMap.mm
@@ -0,0 +1,311 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsWindowMap.h"
+#include "nsObjCExceptions.h"
+#include "nsChildView.h"
+#include "nsCocoaWindow.h"
+
+@interface WindowDataMap(Private)
+
+- (NSString*)keyForWindow:(NSWindow*)inWindow;
+
+@end
+
+@interface TopLevelWindowData(Private)
+
+- (void)windowResignedKey:(NSNotification*)inNotification;
+- (void)windowBecameKey:(NSNotification*)inNotification;
+- (void)windowWillClose:(NSNotification*)inNotification;
+
+@end
+
+#pragma mark -
+
+@implementation WindowDataMap
+
++ (WindowDataMap*)sharedWindowDataMap
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ static WindowDataMap* sWindowMap = nil;
+ if (!sWindowMap)
+ sWindowMap = [[WindowDataMap alloc] init];
+
+ return sWindowMap;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)init
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ mWindowMap = [[NSMutableDictionary alloc] initWithCapacity:10];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap release];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)ensureDataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (!inWindow || [self dataForWindow:inWindow])
+ return;
+
+ TopLevelWindowData* windowData = [[TopLevelWindowData alloc] initWithWindow:inWindow];
+ [self setData:windowData forWindow:inWindow]; // takes ownership
+ [windowData release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (id)dataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mWindowMap objectForKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)setData:(id)inData forWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap setObject:inData forKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)removeDataForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mWindowMap removeObjectForKey:[self keyForWindow:inWindow]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)keyForWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSString stringWithFormat:@"%p", inWindow];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+@end
+
+// TopLevelWindowData
+//
+// This class holds data about top-level windows. We can't use a window
+// delegate, because an embedder may already have one.
+
+@implementation TopLevelWindowData
+
+- (id)initWithWindow:(NSWindow*)inWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ((self = [super init])) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowBecameKey:)
+ name:NSWindowDidBecomeKeyNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowResignedKey:)
+ name:NSWindowDidResignKeyNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowBecameMain:)
+ name:NSWindowDidBecomeMainNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowResignedMain:)
+ name:NSWindowDidResignMainNotification
+ object:inWindow];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(windowWillClose:)
+ name:NSWindowWillCloseNotification
+ object:inWindow];
+ }
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// As best I can tell, if the notification's object has a corresponding
+// top-level widget (an nsCocoaWindow object), it has a delegate (set in
+// nsCocoaWindow::StandardCreate()) of class WindowDelegate, and otherwise
+// not (Camino didn't use top-level widgets (nsCocoaWindow objects) --
+// only child widgets (nsChildView objects)). (The notification is sent
+// to windowBecameKey: or windowBecameMain: below.)
+//
+// For use with clients that (like Firefox) do use top-level widgets (and
+// have NSWindow delegates of class WindowDelegate).
++ (void)activateInWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return;
+
+ if ([delegate toplevelActiveState])
+ return;
+ [delegate sendToplevelActivateEvents];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// See comments above activateInWindow:
+//
+// If we're using top-level widgets (nsCocoaWindow objects), we send them
+// NS_DEACTIVATE events (which propagate to child widgets (nsChildView
+// objects) via nsWebShellWindow::HandleEvent()).
+//
+// For use with clients that (like Firefox) do use top-level widgets (and
+// have NSWindow delegates of class WindowDelegate).
++ (void)deactivateInWindow:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ WindowDelegate* delegate = (WindowDelegate*) [aWindow delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
+ return;
+
+ if (![delegate toplevelActiveState])
+ return;
+ [delegate sendToplevelDeactivateEvents];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// For use with clients that (like Camino) don't use top-level widgets (and
+// don't have NSWindow delegates of class WindowDelegate).
++ (void)activateInWindowViews:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ id firstResponder = [aWindow firstResponder];
+ if ([firstResponder isKindOfClass:[ChildView class]])
+ [firstResponder viewsWindowDidBecomeKey];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// For use with clients that (like Camino) don't use top-level widgets (and
+// don't have NSWindow delegates of class WindowDelegate).
++ (void)deactivateInWindowViews:(NSWindow*)aWindow
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ id firstResponder = [aWindow firstResponder];
+ if ([firstResponder isKindOfClass:[ChildView class]])
+ [firstResponder viewsWindowDidResignKey];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// We make certain exceptions for top-level windows in non-embedders (see
+// comment above windowBecameMain below). And we need (elsewhere) to guard
+// against sending duplicate events. But in general the NS_ACTIVATE event
+// should be sent when a native window becomes key, and the NS_DEACTIVATE
+// event should be sent when it resignes key.
+- (void)windowBecameKey:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
+ [TopLevelWindowData activateInWindowViews:window];
+ } else if ([window isSheet]) {
+ [TopLevelWindowData activateInWindow:window];
+ }
+
+ [[window contentView] setNeedsDisplay:YES];
+}
+
+- (void)windowResignedKey:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]]) {
+ [TopLevelWindowData deactivateInWindowViews:window];
+ } else if ([window isSheet]) {
+ [TopLevelWindowData deactivateInWindow:window];
+ }
+
+ [[window contentView] setNeedsDisplay:YES];
+}
+
+// The appearance of a top-level window depends on its main state (not its key
+// state). So (for non-embedders) we need to ensure that a top-level window
+// is main when an NS_ACTIVATE event is sent to Gecko for it.
+- (void)windowBecameMain:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ // Don't send events to a top-level window that has a sheet open above it --
+ // as far as Gecko is concerned, it's inactive, and stays so until the sheet
+ // closes.
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData activateInWindow:window];
+}
+
+- (void)windowResignedMain:(NSNotification*)inNotification
+{
+ NSWindow* window = (NSWindow*)[inNotification object];
+
+ id delegate = [window delegate];
+ if (delegate && [delegate isKindOfClass:[WindowDelegate class]] && ![window attachedSheet])
+ [TopLevelWindowData deactivateInWindow:window];
+}
+
+- (void)windowWillClose:(NSNotification*)inNotification
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // postpone our destruction
+ [[self retain] autorelease];
+
+ // remove ourselves from the window map (which owns us)
+ [[WindowDataMap sharedWindowDataMap] removeDataForWindow:[inNotification object]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/widget/cocoa/resources/MainMenu.nib/classes.nib b/widget/cocoa/resources/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..b9b4b09f6b
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/classes.nib
@@ -0,0 +1,4 @@
+{
+ IBClasses = ({CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; });
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/widget/cocoa/resources/MainMenu.nib/info.nib b/widget/cocoa/resources/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..bcf3ace841
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/info.nib
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>159 127 356 240 0 0 1920 1178 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>413 971 130 44 0 0 1920 1178 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>443.0</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>8F46</string>
+</dict>
+</plist>
diff --git a/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib b/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000000..16b3f7e523
--- /dev/null
+++ b/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/widget/moz.build b/widget/moz.build
index e40bf6a61a..6a88fab706 100644
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -5,6 +5,9 @@
toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+if toolkit in ('cocoa', 'uikit'):
+ DIRS += [toolkit]
+
if toolkit in ('gtk2', 'gtk3'):
EXPORTS += ['nsIPrintDialogService.h']
@@ -25,8 +28,19 @@ if toolkit == 'windows':
'nsIWindowsUIUtils.idl',
'nsIWinTaskbar.idl',
]
+elif toolkit == 'cocoa':
+ XPIDL_SOURCES += [
+ 'nsIMacDockSupport.idl',
+ 'nsIMacWebAppUtils.idl',
+ 'nsIStandaloneNativeMenu.idl',
+ 'nsISystemStatusBar.idl',
+ 'nsITaskbarProgress.idl',
+ ]
+ EXPORTS += [
+ 'nsIPrintDialogService.h',
+ ]
-if toolkit in ('gtk2', 'gtk3'):
+if toolkit in ('cocoa', 'gtk2', 'gtk3'):
EXPORTS += ['nsINativeMenuService.h']
# Don't build the DSO under the 'build' directory as windows does.
@@ -202,15 +216,15 @@ if CONFIG['MOZ_X11']:
'WindowSurfaceX11SHM.cpp',
]
-if toolkit in ('windows'):
+if toolkit in ('cocoa', 'windows'):
UNIFIED_SOURCES += [
'nsBaseClipboard.cpp',
]
-if toolkit in {'gtk2', 'gtk3', 'windows', 'uikit'}:
+if toolkit in {'gtk2', 'gtk3', 'cocoa', 'windows', 'uikit'}:
UNIFIED_SOURCES += ['nsBaseFilePicker.cpp']
-if toolkit in ('gtk2', 'gtk3', 'windows'):
+if toolkit in ('gtk2', 'gtk3', 'windows', 'cocoa'):
UNIFIED_SOURCES += ['nsNativeTheme.cpp']
if toolkit == 'gtk3':
diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp
index 23859383c2..d3cdf72cfc 100644
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -84,6 +84,10 @@ static void debug_RegisterPrefCallbacks();
static int32_t gNumWidgets;
#endif
+#ifdef XP_MACOSX
+#include "nsCocoaFeatures.h"
+#endif
+
#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
static nsRefPtrHashtable<nsVoidPtrHashKey, nsIWidget>* sPluginWidgetList;
#endif
@@ -171,7 +175,7 @@ nsBaseWidget::nsBaseWidget()
, mUpdateCursor(true)
, mUseAttachedEvents(false)
, mIMEHasFocus(false)
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
, mAccessibilityInUseFlag(false)
#endif
{
@@ -641,8 +645,15 @@ void nsBaseWidget::AddChild(nsIWidget* aChild)
void nsBaseWidget::RemoveChild(nsIWidget* aChild)
{
#ifdef DEBUG
+#ifdef XP_MACOSX
+ // nsCocoaWindow doesn't implement GetParent, so in that case parent will be
+ // null and we'll just have to do without this assertion.
+ nsIWidget* parent = aChild->GetParent();
+ NS_ASSERTION(!parent || parent == this, "Not one of our kids!");
+#else
MOZ_RELEASE_ASSERT(aChild->GetParent() == this, "Not one of our kids!");
#endif
+#endif
if (mLastChild == aChild) {
mLastChild = mLastChild->GetPrevSibling();
@@ -1371,8 +1382,12 @@ void nsBaseWidget::CreateCompositor(int aWidth, int aHeight)
mLayerManager = lm.forget();
// Only track compositors for top-level windows, since other window types
- // may use the basic compositor.
+ // may use the basic compositor. Except on the OS X - see bug 1306383
+#if defined(XP_MACOSX)
+ bool getCompositorFromThisWindow = true;
+#else
bool getCompositorFromThisWindow = (mWindowType == eWindowType_toplevel);
+#endif
if (getCompositorFromThisWindow) {
gfxPlatform::GetPlatform()->NotifyCompositorCreated(mLayerManager->GetCompositorBackendType());
@@ -1870,7 +1885,7 @@ nsBaseWidget::ZoomToRect(const uint32_t& aPresShellId,
#ifdef ACCESSIBILITY
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
// defined in nsAppRunner.cpp
extern const char* kAccessibilityLastRunDatePref;
@@ -1899,7 +1914,7 @@ nsBaseWidget::GetRootAccessible()
// make sure it's not created at unsafe times.
nsAccessibilityService* accService = GetOrCreateAccService();
if (accService) {
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
if (!mAccessibilityInUseFlag) {
mAccessibilityInUseFlag = true;
uint32_t now = PRTimeToSeconds(PR_Now());
diff --git a/widget/nsBaseWidget.h b/widget/nsBaseWidget.h
index 4065a7f1e8..bbc6b72383 100644
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -677,7 +677,7 @@ protected:
bool mUpdateCursor;
bool mUseAttachedEvents;
bool mIMEHasFocus;
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
bool mAccessibilityInUseFlag;
#endif
static nsIRollupListener* gRollupListener;
diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h
index bf03652440..e45189bb10 100644
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -431,6 +431,13 @@ struct ParamTraits<mozilla::WidgetKeyboardEvent>
WriteParam(aMsg,
static_cast<paramType::InputMethodAppStateType>
(aParam.mInputMethodAppState));
+#ifdef XP_MACOSX
+ WriteParam(aMsg, aParam.mNativeKeyCode);
+ WriteParam(aMsg, aParam.mNativeModifierFlags);
+ WriteParam(aMsg, aParam.mNativeCharacters);
+ WriteParam(aMsg, aParam.mNativeCharactersIgnoringModifiers);
+ WriteParam(aMsg, aParam.mPluginTextEventString);
+#endif
// An OS-specific native event might be attached in |mNativeKeyEvent|, but
// that cannot be copied across process boundaries.
}
@@ -458,6 +465,13 @@ struct ParamTraits<mozilla::WidgetKeyboardEvent>
ReadParam(aMsg, aIter, &aResult->mUniqueId) &&
ReadParam(aMsg, aIter, &aResult->mIsSynthesizedByTIP) &&
ReadParam(aMsg, aIter, &inputMethodAppState)
+#ifdef XP_MACOSX
+ && ReadParam(aMsg, aIter, &aResult->mNativeKeyCode)
+ && ReadParam(aMsg, aIter, &aResult->mNativeModifierFlags)
+ && ReadParam(aMsg, aIter, &aResult->mNativeCharacters)
+ && ReadParam(aMsg, aIter, &aResult->mNativeCharactersIgnoringModifiers)
+ && ReadParam(aMsg, aIter, &aResult->mPluginTextEventString)
+#endif
)
{
aResult->mKeyNameIndex = static_cast<mozilla::KeyNameIndex>(keyNameIndex);
diff --git a/widget/nsIMacDockSupport.idl b/widget/nsIMacDockSupport.idl
new file mode 100644
index 0000000000..5783e9c0b9
--- /dev/null
+++ b/widget/nsIMacDockSupport.idl
@@ -0,0 +1,39 @@
+/* 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 "nsISupports.idl"
+
+interface nsIStandaloneNativeMenu;
+
+/**
+ * Allow applications to interface with the Mac OS X Dock.
+ *
+ * Applications may indicate progress on their Dock icon. Only one such
+ * progress indicator is available to the entire application.
+ */
+
+[scriptable, uuid(8BE66B0C-5F71-4B74-98CF-6C2551B999B1)]
+interface nsIMacDockSupport : nsISupports
+{
+ /**
+ * Menu to use for application-specific dock menu items.
+ */
+ attribute nsIStandaloneNativeMenu dockMenu;
+
+ /**
+ * Activate the application. This should be used by an application to
+ * activate itself when a dock menu is selected as selection of a dock menu
+ * item does not automatically activate the application.
+ *
+ * @param aIgnoreOtherApplications If false, the application is activated
+ * only if no other application is currently active. If true, the
+ * application activates regardless.
+ */
+ void activateApplication(in boolean aIgnoreOtherApplications);
+
+ /**
+ * Text used to badge the dock tile.
+ */
+ attribute AString badgeText;
+};
diff --git a/widget/nsIMacWebAppUtils.idl b/widget/nsIMacWebAppUtils.idl
new file mode 100644
index 0000000000..4d570a8bf0
--- /dev/null
+++ b/widget/nsIMacWebAppUtils.idl
@@ -0,0 +1,35 @@
+/* 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 "nsISupports.idl"
+
+interface nsIMacWebAppUtils;
+
+[scriptable, function, uuid(8c899c4f-58c1-4b74-9034-3bb64e484b68)]
+interface nsITrashAppCallback : nsISupports
+{
+ void trashAppFinished(in nsresult rv);
+};
+
+/**
+ * Allow MozApps API to locate and manipulate natively installed apps
+ */
+
+[scriptable, uuid(c69cf343-ea41-428b-b161-4655fd54d8e7)]
+interface nsIMacWebAppUtils : nsISupports {
+ /**
+ * Find the path for an app with the given signature.
+ */
+ AString pathForAppWithIdentifier(in AString bundleIdentifier);
+
+ /**
+ * Launch the app with the given identifier, if it exists.
+ */
+ void launchAppWithIdentifier(in AString bundleIdentifier);
+
+ /**
+ * Move the app from the given directory to the Trash.
+ */
+ void trashApp(in AString path, in nsITrashAppCallback callback);
+};
diff --git a/widget/nsISystemStatusBar.idl b/widget/nsISystemStatusBar.idl
new file mode 100644
index 0000000000..9db8015199
--- /dev/null
+++ b/widget/nsISystemStatusBar.idl
@@ -0,0 +1,36 @@
+/* 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 "nsISupports.idl"
+
+interface nsIDOMElement;
+
+/**
+ * Allow applications to interface with the Mac OS X system status bar.
+ */
+
+[scriptable, uuid(24493180-ee81-4b7c-8b17-9e69480b7b8a)]
+interface nsISystemStatusBar : nsISupports
+{
+ /**
+ * Add an item to the system status bar. Each item can only be present once,
+ * subsequent addItem calls with the same element will be ignored.
+ * The system status bar holds a strong reference to the added XUL menu
+ * element and the item will stay in the status bar until it is removed via
+ * a call to removeItem, or until the process shuts down.
+ * @param aDOMMenuElement A XUL menu element that contains a XUL menupopup
+ * with regular menu content. The menu's icon is put
+ * into the system status bar; clicking it will open
+ * a menu with the contents of the menupopup.
+ * The menu label is not shown.
+ */
+ void addItem(in nsIDOMElement aDOMMenuElement);
+
+ /**
+ * Remove a previously-added item from the menu bar. Calling this with an
+ * element that has not been added before will be silently ignored.
+ * @param aDOMMenuElement The XUL menu element that you called addItem with.
+ */
+ void removeItem(in nsIDOMElement aDOMMenuElement);
+};
diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h
index 716ed85c45..8e28c24da5 100644
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -115,6 +115,10 @@ typedef void* nsNativeWidget;
// IME context. Note that the result is only valid in the process. So,
// XP code should use nsIWidget::GetNativeIMEContext() instead of using this.
#define NS_RAW_NATIVE_IME_CONTEXT 14
+#ifdef XP_MACOSX
+#define NS_NATIVE_PLUGIN_PORT_QD 100
+#define NS_NATIVE_PLUGIN_PORT_CG 101
+#endif
#ifdef XP_WIN
#define NS_NATIVE_TSF_THREAD_MGR 100
#define NS_NATIVE_TSF_CATEGORY_MGR 101
diff --git a/widget/nsNativeTheme.cpp b/widget/nsNativeTheme.cpp
index 35c5a84f75..a5bd85fafa 100644
--- a/widget/nsNativeTheme.cpp
+++ b/widget/nsNativeTheme.cpp
@@ -99,13 +99,25 @@ nsNativeTheme::GetContentState(nsIFrame* aFrame, uint8_t aWidgetType)
flags |= NS_EVENT_STATE_FOCUS;
}
- // On Windows, only draw focus rings if they should be shown. This
+ // On Windows and Mac, only draw focus rings if they should be shown. This
// means that focus rings are only shown once the keyboard has been used to
// focus something in the window.
+#if defined(XP_MACOSX)
+ // Mac always draws focus rings for textboxes and lists.
+ if (aWidgetType == NS_THEME_NUMBER_INPUT ||
+ aWidgetType == NS_THEME_TEXTFIELD ||
+ aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
+ aWidgetType == NS_THEME_SEARCHFIELD ||
+ aWidgetType == NS_THEME_LISTBOX) {
+ return flags;
+ }
+#endif
#if defined(XP_WIN)
// On Windows, focused buttons are always drawn as such by the native theme.
if (aWidgetType == NS_THEME_BUTTON)
return flags;
+#endif
+#if defined(XP_MACOSX) || defined(XP_WIN)
nsIDocument* doc = aFrame->GetContent()->OwnerDoc();
nsPIDOMWindowOuter* window = doc->GetWindow();
if (window && !window->ShouldShowFocusRing())
diff --git a/widget/uikit/GfxInfo.cpp b/widget/uikit/GfxInfo.cpp
new file mode 100644
index 0000000000..cabe993dd0
--- /dev/null
+++ b/widget/uikit/GfxInfo.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 2; 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 "GfxInfo.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+namespace widget {
+
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+GfxInfo::GfxInfo()
+{
+}
+
+GfxInfo::~GfxInfo()
+{
+}
+
+nsresult
+GfxInfo::GetD2DEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+GfxInfo::GetDWriteEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString & aDwriteVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString & aCleartypeParams)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString & aAdapterDriver)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active)
+{
+ return NS_ERROR_FAILURE;
+}
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ if (mDriverInfo->IsEmpty()) {
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Ios,
+ (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorAll), GfxDriverInfo::allDevices,
+ nsIGfxInfo::FEATURE_OPENGL_LAYERS, nsIGfxInfo::FEATURE_STATUS_OK,
+ DRIVER_COMPARISON_IGNORED, GfxDriverInfo::allDriverVersions );
+ }
+
+ return *mDriverInfo;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ OperatingSystem* aOS /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ aSuggestedDriverVersion.SetIsVoid(true);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ if (aOS)
+ *aOS = OperatingSystem::Ios;
+
+ if (mShutdownOccurred) {
+ return NS_OK;
+ }
+
+ // OpenGL layers are never blacklisted on iOS.
+ // This early return is so we avoid potentially slow
+ // GLStrings initialization on startup when we initialize GL layers.
+ if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS ||
+ aFeature == nsIGfxInfo::FEATURE_WEBGL_OPENGL ||
+ aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ return NS_OK;
+ }
+
+ return GfxInfoBase::GetFeatureStatusImpl(aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aOS);
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString & aVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+#endif
+
+}
+}
diff --git a/widget/uikit/GfxInfo.h b/widget/uikit/GfxInfo.h
new file mode 100644
index 0000000000..16a2242515
--- /dev/null
+++ b/widget/uikit/GfxInfo.h
@@ -0,0 +1,78 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+#include "GfxDriverInfo.h"
+
+#include "nsString.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
+namespace widget {
+
+class GfxInfo : public GfxInfoBase
+{
+private:
+ ~GfxInfo();
+
+public:
+ GfxInfo();
+
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool *aD2DEnabled);
+ NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled);
+ NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion);
+ NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams);
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active);
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+protected:
+
+ virtual nsresult GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ OperatingSystem* aOS = nullptr);
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/uikit/moz.build b/widget/uikit/moz.build
new file mode 100644
index 0000000000..2c6c188de6
--- /dev/null
+++ b/widget/uikit/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+SOURCES += [
+ 'GfxInfo.cpp',
+ 'nsAppShell.mm',
+ 'nsLookAndFeel.mm',
+ 'nsScreenManager.mm',
+ 'nsWidgetFactory.mm',
+ 'nsWindow.mm',
+]
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/widget',
+]
diff --git a/widget/uikit/nsAppShell.h b/widget/uikit/nsAppShell.h
new file mode 100644
index 0000000000..a88fa8b4f1
--- /dev/null
+++ b/widget/uikit/nsAppShell.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+ * Runs the main native UIKit run loop, interrupting it as needed to process
+ * Gecko events.
+ */
+
+#ifndef nsAppShell_h_
+#define nsAppShell_h_
+
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+#include <Foundation/NSAutoreleasePool.h>
+#include <CoreFoundation/CFRunLoop.h>
+#include <UIKit/UIWindow.h>
+
+@class AppShellDelegate;
+
+class nsAppShell : public nsBaseAppShell
+{
+public:
+ NS_IMETHOD ResumeNative(void);
+
+ nsAppShell();
+
+ nsresult Init();
+
+ NS_IMETHOD Run(void);
+ NS_IMETHOD Exit(void);
+ // Called by the application delegate
+ void WillTerminate(void);
+
+ static nsAppShell* gAppShell;
+ static UIWindow* gWindow;
+ static NSMutableArray* gTopLevelViews;
+
+protected:
+ virtual ~nsAppShell();
+
+ static void ProcessGeckoEvents(void* aInfo);
+ virtual void ScheduleNativeEventCallback();
+ virtual bool ProcessNextNativeEvent(bool aMayWait);
+
+ NSAutoreleasePool* mAutoreleasePool;
+ AppShellDelegate* mDelegate;
+ CFRunLoopRef mCFRunLoop;
+ CFRunLoopSourceRef mCFRunLoopSource;
+
+ bool mTerminated;
+ bool mNotifiedWillTerminate;
+};
+
+#endif // nsAppShell_h_
diff --git a/widget/uikit/nsAppShell.mm b/widget/uikit/nsAppShell.mm
new file mode 100644
index 0000000000..ac007132fd
--- /dev/null
+++ b/widget/uikit/nsAppShell.mm
@@ -0,0 +1,271 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#import <UIKit/UIApplication.h>
+#import <UIKit/UIScreen.h>
+#import <UIKit/UIWindow.h>
+#import <UIKit/UIViewController.h>
+
+#include "nsAppShell.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIRollupListener.h"
+#include "nsIWidget.h"
+#include "nsThreadUtils.h"
+#include "nsIWindowMediator.h"
+#include "nsMemoryPressure.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIWebBrowserChrome.h"
+
+nsAppShell *nsAppShell::gAppShell = NULL;
+UIWindow *nsAppShell::gWindow = nil;
+NSMutableArray *nsAppShell::gTopLevelViews = [[NSMutableArray alloc] init];
+
+#define ALOG(args...) fprintf(stderr, args); fprintf(stderr, "\n")
+
+// ViewController
+@interface ViewController : UIViewController
+@end
+
+
+@implementation ViewController
+
+- (void)loadView {
+ ALOG("[ViewController loadView]");
+ CGRect r = {{0, 0}, {100, 100}};
+ self.view = [[UIView alloc] initWithFrame:r];
+ [self.view setBackgroundColor:[UIColor lightGrayColor]];
+ // add all of the top level views as children
+ for (UIView* v in nsAppShell::gTopLevelViews) {
+ ALOG("[ViewController.view addSubView:%p]", v);
+ [self.view addSubview:v];
+ }
+ [nsAppShell::gTopLevelViews release];
+ nsAppShell::gTopLevelViews = nil;
+}
+@end
+
+// AppShellDelegate
+//
+// Acts as a delegate for the UIApplication
+
+@interface AppShellDelegate : NSObject <UIApplicationDelegate> {
+}
+@property (strong, nonatomic) UIWindow *window;
+@end
+
+@implementation AppShellDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ ALOG("[AppShellDelegate application:didFinishLaunchingWithOptions:]");
+ // We only create one window, since we can only display one window at
+ // a time anyway. Also, iOS 4 fails to display UIWindows if you
+ // create them before calling UIApplicationMain, so this makes more sense.
+ nsAppShell::gWindow = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] retain];
+ self.window = nsAppShell::gWindow;
+
+ self.window.rootViewController = [[ViewController alloc] init];
+
+ // just to make things more visible for now
+ nsAppShell::gWindow.backgroundColor = [UIColor blueColor];
+ [nsAppShell::gWindow makeKeyAndVisible];
+
+ return YES;
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationWillTerminate:]");
+ nsAppShell::gAppShell->WillTerminate();
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationDidBecomeActive:]");
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationWillResignActive:]");
+}
+
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
+{
+ ALOG("[AppShellDelegate applicationDidReceiveMemoryWarning:]");
+ NS_DispatchMemoryPressure(MemPressure_New);
+}
+@end
+
+// nsAppShell implementation
+
+NS_IMETHODIMP
+nsAppShell::ResumeNative(void)
+{
+ return nsBaseAppShell::ResumeNative();
+}
+
+nsAppShell::nsAppShell()
+ : mAutoreleasePool(NULL),
+ mDelegate(NULL),
+ mCFRunLoop(NULL),
+ mCFRunLoopSource(NULL),
+ mTerminated(false),
+ mNotifiedWillTerminate(false)
+{
+ gAppShell = this;
+}
+
+nsAppShell::~nsAppShell()
+{
+ if (mAutoreleasePool) {
+ [mAutoreleasePool release];
+ mAutoreleasePool = NULL;
+ }
+
+ if (mCFRunLoop) {
+ if (mCFRunLoopSource) {
+ ::CFRunLoopRemoveSource(mCFRunLoop, mCFRunLoopSource,
+ kCFRunLoopCommonModes);
+ ::CFRelease(mCFRunLoopSource);
+ }
+ ::CFRelease(mCFRunLoop);
+ }
+
+ gAppShell = NULL;
+}
+
+// Init
+//
+// public
+nsresult
+nsAppShell::Init()
+{
+ mAutoreleasePool = [[NSAutoreleasePool alloc] init];
+
+ // Add a CFRunLoopSource to the main native run loop. The source is
+ // responsible for interrupting the run loop when Gecko events are ready.
+
+ mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
+ NS_ENSURE_STATE(mCFRunLoop);
+ ::CFRetain(mCFRunLoop);
+
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(context));
+ // context.version = 0;
+ context.info = this;
+ context.perform = ProcessGeckoEvents;
+
+ mCFRunLoopSource = ::CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
+ NS_ENSURE_STATE(mCFRunLoopSource);
+
+ ::CFRunLoopAddSource(mCFRunLoop, mCFRunLoopSource, kCFRunLoopCommonModes);
+
+ return nsBaseAppShell::Init();
+}
+
+// ProcessGeckoEvents
+//
+// The "perform" target of mCFRunLoop, called when mCFRunLoopSource is
+// signalled from ScheduleNativeEventCallback.
+//
+// protected static
+void
+nsAppShell::ProcessGeckoEvents(void* aInfo)
+{
+ nsAppShell* self = static_cast<nsAppShell*> (aInfo);
+ self->NativeEventCallback();
+ self->Release();
+}
+
+// WillTerminate
+//
+// public
+void
+nsAppShell::WillTerminate()
+{
+ mNotifiedWillTerminate = true;
+ if (mTerminated)
+ return;
+ mTerminated = true;
+ // We won't get another chance to process events
+ NS_ProcessPendingEvents(NS_GetCurrentThread());
+
+ // Unless we call nsBaseAppShell::Exit() here, it might not get called
+ // at all.
+ nsBaseAppShell::Exit();
+}
+
+// ScheduleNativeEventCallback
+//
+// protected virtual
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ if (mTerminated)
+ return;
+
+ NS_ADDREF_THIS();
+
+ // This will invoke ProcessGeckoEvents on the main thread.
+ ::CFRunLoopSourceSignal(mCFRunLoopSource);
+ ::CFRunLoopWakeUp(mCFRunLoop);
+}
+
+// ProcessNextNativeEvent
+//
+// protected virtual
+bool
+nsAppShell::ProcessNextNativeEvent(bool aMayWait)
+{
+ if (mTerminated)
+ return false;
+
+ NSString* currentMode = nil;
+ NSDate* waitUntil = nil;
+ if (aMayWait)
+ waitUntil = [NSDate distantFuture];
+ NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
+
+ BOOL eventProcessed = NO;
+ do {
+ currentMode = [currentRunLoop currentMode];
+ if (!currentMode)
+ currentMode = NSDefaultRunLoopMode;
+
+ if (aMayWait)
+ eventProcessed = [currentRunLoop runMode:currentMode beforeDate:waitUntil];
+ else
+ [currentRunLoop acceptInputForMode:currentMode beforeDate:waitUntil];
+ } while(eventProcessed && aMayWait);
+
+ return false;
+}
+
+// Run
+//
+// public
+NS_IMETHODIMP
+nsAppShell::Run(void)
+{
+ ALOG("nsAppShell::Run");
+ char argv[1][4] = {"app"};
+ UIApplicationMain(1, (char**)argv, nil, @"AppShellDelegate");
+ // UIApplicationMain doesn't exit. :-(
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit(void)
+{
+ if (mTerminated)
+ return NS_OK;
+
+ mTerminated = true;
+ return nsBaseAppShell::Exit();
+}
diff --git a/widget/uikit/nsLookAndFeel.h b/widget/uikit/nsLookAndFeel.h
new file mode 100644
index 0000000000..91c0c2d73e
--- /dev/null
+++ b/widget/uikit/nsLookAndFeel.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel: public nsXPLookAndFeel
+{
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual nsresult NativeGetColor(const ColorID aID, nscolor &aResult);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel);
+ virtual char16_t GetPasswordCharacterImpl()
+ {
+ // unicode value for the bullet character, used for password textfields.
+ return 0x2022;
+ }
+
+ static bool UseOverlayScrollbars()
+ {
+ return true;
+ }
+};
+
+#endif
diff --git a/widget/uikit/nsLookAndFeel.mm b/widget/uikit/nsLookAndFeel.mm
new file mode 100644
index 0000000000..bb593eb51e
--- /dev/null
+++ b/widget/uikit/nsLookAndFeel.mm
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#import <UIKit/UIColor.h>
+#import <UIKit/UIInterface.h>
+
+#include "nsLookAndFeel.h"
+#include "nsStyleConsts.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+{
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+static nscolor GetColorFromUIColor(UIColor* aColor)
+{
+ CGColorRef cgColor = [aColor CGColor];
+ CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(cgColor));
+ const CGFloat* components = CGColorGetComponents(cgColor);
+ if (model == kCGColorSpaceModelRGB) {
+ return NS_RGB((unsigned int)(components[0] * 255.0),
+ (unsigned int)(components[1] * 255.0),
+ (unsigned int)(components[2] * 255.0));
+ }
+ else if (model == kCGColorSpaceModelMonochrome) {
+ unsigned int val = (unsigned int)(components[0] * 255.0);
+ return NS_RGBA(val, val, val,
+ (unsigned int)(components[1] * 255.0));
+ }
+ NS_NOTREACHED("Unhandled color space!");
+ return 0;
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(const ColorID aID, nscolor &aResult)
+{
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ case eColorID_WindowBackground:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_WindowForeground:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetBackground:
+ aResult = NS_RGB(0xdd,0xdd,0xdd);
+ break;
+ case eColorID_WidgetForeground:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_WidgetSelectBackground:
+ aResult = NS_RGB(0x80,0x80,0x80);
+ break;
+ case eColorID_WidgetSelectForeground:
+ aResult = NS_RGB(0x00,0x00,0x80);
+ break;
+ case eColorID_Widget3DHighlight:
+ aResult = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aResult = NS_RGB(0x40,0x40,0x40);
+ break;
+ case eColorID_TextBackground:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_TextForeground:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_TextSelectBackground:
+ case eColorID_highlight: // CSS2 color
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_menuhover:
+ aResult = NS_RGB(0xee,0xee,0xee);
+ break;
+ case eColorID_TextSelectForeground:
+ case eColorID_highlighttext: // CSS2 color
+ case eColorID__moz_menuhovertext:
+ GetColor(eColorID_TextSelectBackground, aResult);
+ if (aResult == 0x000000)
+ aResult = NS_RGB(0xff,0xff,0xff);
+ else
+ aResult = NS_DONT_CHANGE_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aResult = NS_TRANSPARENT;
+ break;
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aResult = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aResult = NS_40PERCENT_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aResult = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aResult = NS_RGB(0xff, 0, 0);
+ break;
+
+ //
+ // css2 system colors http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ //
+ case eColorID_buttontext:
+ case eColorID__moz_buttonhovertext:
+ case eColorID_captiontext:
+ case eColorID_menutext:
+ case eColorID_infotext:
+ case eColorID__moz_menubartext:
+ case eColorID_windowtext:
+ aResult = GetColorFromUIColor([UIColor darkTextColor]);
+ break;
+ case eColorID_activecaption:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_activeborder:
+ aResult = NS_RGB(0x00,0x00,0x00);
+ break;
+ case eColorID_appworkspace:
+ aResult = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_background:
+ aResult = NS_RGB(0x63,0x63,0xCE);
+ break;
+ case eColorID_buttonface:
+ case eColorID__moz_buttonhoverface:
+ aResult = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_buttonhighlight:
+ aResult = NS_RGB(0xFF,0xFF,0xFF);
+ break;
+ case eColorID_buttonshadow:
+ aResult = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_graytext:
+ aResult = NS_RGB(0x44,0x44,0x44);
+ break;
+ case eColorID_inactiveborder:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_inactivecaption:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID_inactivecaptiontext:
+ aResult = NS_RGB(0x45,0x45,0x45);
+ break;
+ case eColorID_scrollbar:
+ aResult = NS_RGB(0,0,0); //XXX
+ break;
+ case eColorID_threeddarkshadow:
+ aResult = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID_threedshadow:
+ aResult = NS_RGB(0xE0,0xE0,0xE0);
+ break;
+ case eColorID_threedface:
+ aResult = NS_RGB(0xF0,0xF0,0xF0);
+ break;
+ case eColorID_threedhighlight:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_threedlightshadow:
+ aResult = NS_RGB(0xDA,0xDA,0xDA);
+ break;
+ case eColorID_menu:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID_infobackground:
+ aResult = NS_RGB(0xFF,0xFF,0xC7);
+ break;
+ case eColorID_windowframe:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID_window:
+ case eColorID__moz_field:
+ case eColorID__moz_combobox:
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID__moz_fieldtext:
+ case eColorID__moz_comboboxtext:
+ aResult = GetColorFromUIColor([UIColor darkTextColor]);
+ break;
+ case eColorID__moz_dialog:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_dialogtext:
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aResult = GetColorFromUIColor([UIColor darkTextColor]);
+ break;
+ case eColorID__moz_dragtargetzone:
+ case eColorID__moz_mac_chrome_active:
+ case eColorID__moz_mac_chrome_inactive:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_mac_focusring:
+ aResult = NS_RGB(0x3F,0x98,0xDD);
+ break;
+ case eColorID__moz_mac_menushadow:
+ aResult = NS_RGB(0xA3,0xA3,0xA3);
+ break;
+ case eColorID__moz_mac_menutextdisable:
+ aResult = NS_RGB(0x88,0x88,0x88);
+ break;
+ case eColorID__moz_mac_menutextselect:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_mac_disabledtoolbartext:
+ aResult = NS_RGB(0x3F,0x3F,0x3F);
+ break;
+ case eColorID__moz_mac_menuselect:
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_buttondefault:
+ aResult = NS_RGB(0xDC,0xDC,0xDC);
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ case eColorID__moz_mac_secondaryhighlight:
+ // For inactive list selection
+ aResult = NS_RGB(0xaa,0xaa,0xaa);
+ break;
+ case eColorID__moz_eventreerow:
+ // Background color of even list rows.
+ aResult = NS_RGB(0xff,0xff,0xff);
+ break;
+ case eColorID__moz_oddtreerow:
+ // Background color of odd list rows.
+ aResult = NS_TRANSPARENT;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ // There appears to be no available system defined color. HARDCODING to the appropriate color.
+ aResult = NS_RGB(0x14,0x4F,0xAE);
+ break;
+ default:
+ NS_WARNING("Someone asked nsILookAndFeel for a color I don't know about");
+ aResult = NS_RGB(0xff,0xff,0xff);
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+}
+
+NS_IMETHODIMP
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = 567;
+ break;
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by nsEventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+ case eIntID_SubmenuDelay:
+ aResult = 200;
+ break;
+ case eIntID_MenusCanOverlapOSBar:
+ // xul popups are not allowed to overlap the menubar.
+ aResult = 0;
+ break;
+ case eIntID_SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case eIntID_DragThresholdX:
+ case eIntID_DragThresholdY:
+ aResult = 4;
+ break;
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrow_None;
+ break;
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+ case eIntID_TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case eIntID_TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case eIntID_TreeScrollDelay:
+ aResult = 100;
+ break;
+ case eIntID_TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case eIntID_DWMCompositor:
+ case eIntID_WindowsClassic:
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_TouchEnabled:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case eIntID_MacGraphiteTheme:
+ aResult = 0;
+ break;
+ case eIntID_TabFocusModel:
+ aResult = 1; // default to just textboxes
+ break;
+ case eIntID_ScrollToClick:
+ aResult = 0;
+ break;
+ case eIntID_ChosenMenuItemsShouldBlink:
+ aResult = 1;
+ break;
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
+ break;
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+NS_IMETHODIMP
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 2.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString &aFontName,
+ gfxFontStyle &aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ // hack for now
+ if (aID == eFont_Window || aID == eFont_Document) {
+ aFontStyle.style = NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight = NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+ aFontStyle.size = 14 * aDevPixPerCSSPixel;
+ aFontStyle.systemFont = true;
+
+ aFontName.AssignLiteral("sans-serif");
+ return true;
+ }
+
+ //TODO: implement more here?
+ return false;
+}
diff --git a/widget/uikit/nsScreenManager.h b/widget/uikit/nsScreenManager.h
new file mode 100644
index 0000000000..1ff6a87ec7
--- /dev/null
+++ b/widget/uikit/nsScreenManager.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef nsScreenManager_h_
+#define nsScreenManager_h_
+
+#include "nsBaseScreen.h"
+#include "nsIScreenManager.h"
+#include "nsCOMPtr.h"
+#include "nsRect.h"
+
+@class UIScreen;
+
+class UIKitScreen : public nsBaseScreen
+{
+public:
+ explicit UIKitScreen (UIScreen* screen);
+ ~UIKitScreen () {}
+
+ NS_IMETHOD GetId(uint32_t* outId) {
+ *outId = 0;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRectDisplayPix(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+ NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor);
+ NS_IMETHOD GetDefaultCSSScaleFactor(double* aScaleFactor)
+ {
+ return GetContentsScaleFactor(aScaleFactor);
+ }
+
+private:
+ UIScreen* mScreen;
+};
+
+class UIKitScreenManager : public nsIScreenManager
+{
+public:
+ UIKitScreenManager ();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSISCREENMANAGER
+
+ static LayoutDeviceIntRect GetBounds();
+
+private:
+ virtual ~UIKitScreenManager () {}
+ //TODO: support >1 screen, iPad supports external displays
+ nsCOMPtr<nsIScreen> mScreen;
+};
+
+#endif // nsScreenManager_h_
diff --git a/widget/uikit/nsScreenManager.mm b/widget/uikit/nsScreenManager.mm
new file mode 100644
index 0000000000..601c911cd9
--- /dev/null
+++ b/widget/uikit/nsScreenManager.mm
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#import <UIKit/UIScreen.h>
+
+#include "gfxPoint.h"
+#include "nsScreenManager.h"
+#include "nsAppShell.h"
+
+static LayoutDeviceIntRect gScreenBounds;
+static bool gScreenBoundsSet = false;
+
+UIKitScreen::UIKitScreen(UIScreen* aScreen)
+{
+ mScreen = [aScreen retain];
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ return GetRectDisplayPix(outX, outY, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetAvailRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ return GetAvailRectDisplayPix(outX, outY, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ nsIntRect rect = UIKitScreenManager::GetBounds();
+ *outX = rect.x;
+ *outY = rect.y;
+ *outWidth = rect.width;
+ *outHeight = rect.height;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetAvailRectDisplayPix(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
+{
+ CGRect rect = [mScreen applicationFrame];
+ CGFloat scale = [mScreen scale];
+
+ *outX = rect.origin.x * scale;
+ *outY = rect.origin.y * scale;
+ *outWidth = rect.size.width * scale;
+ *outHeight = rect.size.height * scale;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetPixelDepth(int32_t *aPixelDepth)
+{
+ // Close enough.
+ *aPixelDepth = 24;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetColorDepth(int32_t *aColorDepth)
+{
+ return GetPixelDepth(aColorDepth);
+}
+
+NS_IMETHODIMP
+UIKitScreen::GetContentsScaleFactor(double* aContentsScaleFactor)
+{
+ *aContentsScaleFactor = [mScreen scale];
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UIKitScreenManager, nsIScreenManager)
+
+UIKitScreenManager::UIKitScreenManager()
+: mScreen(new UIKitScreen([UIScreen mainScreen]))
+{
+}
+
+LayoutDeviceIntRect
+UIKitScreenManager::GetBounds()
+{
+ if (!gScreenBoundsSet) {
+ CGRect rect = [[UIScreen mainScreen] bounds];
+ CGFloat scale = [[UIScreen mainScreen] scale];
+ gScreenBounds.x = rect.origin.x * scale;
+ gScreenBounds.y = rect.origin.y * scale;
+ gScreenBounds.width = rect.size.width * scale;
+ gScreenBounds.height = rect.size.height * scale;
+ gScreenBoundsSet = true;
+ }
+ printf("UIKitScreenManager::GetBounds: %d %d %d %d\n",
+ gScreenBounds.x, gScreenBounds.y, gScreenBounds.width, gScreenBounds.height);
+ return gScreenBounds;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::GetPrimaryScreen(nsIScreen** outScreen)
+{
+ NS_IF_ADDREF(*outScreen = mScreen.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::ScreenForRect(int32_t inLeft,
+ int32_t inTop,
+ int32_t inWidth,
+ int32_t inHeight,
+ nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::ScreenForId(uint32_t id,
+ nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::ScreenForNativeWidget(void* aWidget, nsIScreen** outScreen)
+{
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::GetNumberOfScreens(uint32_t* aNumberOfScreens)
+{
+ //TODO: support multiple screens
+ *aNumberOfScreens = 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UIKitScreenManager::GetSystemDefaultScale(float* aScale)
+{
+ *aScale = [UIScreen mainScreen].scale;
+ return NS_OK;
+}
diff --git a/widget/uikit/nsWidgetFactory.mm b/widget/uikit/nsWidgetFactory.mm
new file mode 100644
index 0000000000..9e4f028ff6
--- /dev/null
+++ b/widget/uikit/nsWidgetFactory.mm
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsIFactory.h"
+#include "nsISupports.h"
+#include "nsIComponentManager.h"
+#include "mozilla/ModuleUtils.h"
+
+#include "nsWidgetsCID.h"
+
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsLookAndFeel.h"
+#include "nsScreenManager.h"
+#include "nsWindow.h"
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(UIKitScreenManager)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
+
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, UIKitScreenManagerConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/uikit;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/childwindow/uikit;1", &kNS_CHILD_CID },
+ { "@mozilla.org/widget/appshell/uikit;1", &kNS_APPSHELL_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { nullptr }
+};
+
+static void
+nsWidgetUIKitModuleDtor()
+{
+ nsLookAndFeel::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetUIKitModuleDtor
+};
+
+NSMODULE_DEFN(nsWidgetUIKitModule) = &kWidgetModule;
diff --git a/widget/uikit/nsWindow.h b/widget/uikit/nsWindow.h
new file mode 100644
index 0000000000..cb18c09069
--- /dev/null
+++ b/widget/uikit/nsWindow.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#ifndef NSWINDOW_H_
+#define NSWINDOW_H_
+
+#include "nsBaseWidget.h"
+#include "gfxPoint.h"
+
+#include "nsTArray.h"
+
+@class UIWindow;
+@class UIView;
+@class ChildView;
+
+class nsWindow :
+ public nsBaseWidget
+{
+ typedef nsBaseWidget Inherited;
+
+public:
+ nsWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ //
+ // nsIWidget
+ //
+
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData = nullptr)
+ override;
+ virtual void Destroy() override;
+ NS_IMETHOD Show(bool aState) override;
+ NS_IMETHOD Enable(bool aState) override {
+ return NS_OK;
+ }
+ virtual bool IsEnabled() const override {
+ return true;
+ }
+ virtual bool IsVisible() const override {
+ return mVisible;
+ }
+ NS_IMETHOD SetFocus(bool aState=false) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+
+ virtual void SetBackgroundColor(const nscolor &aColor) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+
+ NS_IMETHOD Move(double aX, double aY) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ void EnteredFullScreen(bool aFullScreen);
+ NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override;
+ NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ void ReportMoveEvent();
+ void ReportSizeEvent();
+ void ReportSizeModeEvent(nsSizeMode aMode);
+
+ CGFloat BackingScaleFactor();
+ void BackingScaleFactorChanged();
+ virtual float GetDPI() override {
+ //XXX: terrible
+ return 326.0f;
+ }
+ virtual double GetDefaultScaleInternal() override {
+ return BackingScaleFactor();
+ }
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ NS_IMETHOD SetTitle(const nsAString& aTitle) override {
+ return NS_OK;
+ }
+
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ void WillPaintWindow();
+ bool PaintWindow(LayoutDeviceIntRegion aRegion);
+
+ bool HasModalDescendents() { return false; }
+
+ //NS_IMETHOD NotifyIME(const IMENotification& aIMENotification) override;
+ NS_IMETHOD_(void) SetInputContext(
+ const InputContext& aContext,
+ const InputContextAction& aAction);
+ NS_IMETHOD_(InputContext) GetInputContext();
+ /*
+ NS_IMETHOD_(bool) ExecuteNativeKeyBinding(
+ NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ DoCommandCallback aCallback,
+ void* aCallbackData) override;
+ */
+
+protected:
+ virtual ~nsWindow();
+ void BringToFront();
+ nsWindow *FindTopLevel();
+ bool IsTopLevel();
+ nsresult GetCurrentOffset(uint32_t &aOffset, uint32_t &aLength);
+ nsresult DeleteRange(int aOffset, int aLen);
+
+ void TearDownView();
+
+ ChildView* mNativeView;
+ bool mVisible;
+ nsTArray<nsWindow*> mChildren;
+ nsWindow* mParent;
+ InputContext mInputContext;
+
+ void OnSizeChanged(const mozilla::gfx::IntSize& aSize);
+
+ static void DumpWindows();
+ static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
+ static void LogWindow(nsWindow *win, int index, int indent);
+};
+
+#endif /* NSWINDOW_H_ */
diff --git a/widget/uikit/nsWindow.mm b/widget/uikit/nsWindow.mm
new file mode 100644
index 0000000000..874626237c
--- /dev/null
+++ b/widget/uikit/nsWindow.mm
@@ -0,0 +1,862 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#import <UIKit/UIEvent.h>
+#import <UIKit/UIGraphics.h>
+#import <UIKit/UIInterface.h>
+#import <UIKit/UIScreen.h>
+#import <UIKit/UITapGestureRecognizer.h>
+#import <UIKit/UITouch.h>
+#import <UIKit/UIView.h>
+#import <UIKit/UIViewController.h>
+#import <UIKit/UIWindow.h>
+#import <QuartzCore/QuartzCore.h>
+
+#include <algorithm>
+
+#include "nsWindow.h"
+#include "nsScreenManager.h"
+#include "nsAppShell.h"
+
+#include "nsWidgetsCID.h"
+#include "nsGfxCIID.h"
+
+#include "gfxQuartzSurface.h"
+#include "gfxUtils.h"
+#include "gfxImageSurface.h"
+#include "gfxContext.h"
+#include "nsRegion.h"
+#include "Layers.h"
+#include "nsTArray.h"
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Unused.h"
+
+#include "GeckoProfiler.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+
+#define ALOG(args...) fprintf(stderr, args); fprintf(stderr, "\n")
+
+static LayoutDeviceIntPoint
+UIKitPointsToDevPixels(CGPoint aPoint, CGFloat aBackingScale)
+{
+ return LayoutDeviceIntPoint(NSToIntRound(aPoint.x * aBackingScale),
+ NSToIntRound(aPoint.y * aBackingScale));
+}
+
+static CGRect
+DevPixelsToUIKitPoints(const LayoutDeviceIntRect& aRect, CGFloat aBackingScale)
+{
+ return CGRectMake((CGFloat)aRect.x / aBackingScale,
+ (CGFloat)aRect.y / aBackingScale,
+ (CGFloat)aRect.width / aBackingScale,
+ (CGFloat)aRect.height / aBackingScale);
+}
+
+// Used to retain a Cocoa object for the remainder of a method's execution.
+class nsAutoRetainUIKitObject {
+public:
+nsAutoRetainUIKitObject(id anObject)
+{
+ mObject = [anObject retain];
+}
+~nsAutoRetainUIKitObject()
+{
+ [mObject release];
+}
+private:
+ id mObject; // [STRONG]
+};
+
+@interface ChildView : UIView
+{
+@public
+ nsWindow* mGeckoChild; // weak ref
+ BOOL mWaitingForPaint;
+ CFMutableDictionaryRef mTouches;
+ int mNextTouchID;
+}
+// sets up our view, attaching it to its owning gecko view
+- (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild;
+// Our Gecko child was Destroy()ed
+- (void)widgetDestroyed;
+// Tear down this ChildView
+- (void)delayedTearDown;
+- (void)sendMouseEvent:(EventMessage) aType point:(LayoutDeviceIntPoint)aPoint widget:(nsWindow*)aWindow;
+- (void)handleTap:(UITapGestureRecognizer *)sender;
+- (BOOL)isUsingMainThreadOpenGL;
+- (void)drawUsingOpenGL;
+- (void)drawUsingOpenGLCallback;
+- (void)sendTouchEvent:(EventMessage) aType touches:(NSSet*)aTouches widget:(nsWindow*)aWindow;
+// Event handling (UIResponder)
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
+@end
+
+@implementation ChildView
++ (Class)layerClass {
+ return [CAEAGLLayer class];
+}
+
+- (id)initWithFrame:(CGRect)inFrame geckoChild:(nsWindow*)inChild
+{
+ self.multipleTouchEnabled = YES;
+ if ((self = [super initWithFrame:inFrame])) {
+ mGeckoChild = inChild;
+ }
+ ALOG("[ChildView[%p] initWithFrame:] (mGeckoChild = %p)", (void*)self, (void*)mGeckoChild);
+ self.opaque = YES;
+ self.alpha = 1.0;
+
+ UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
+ initWithTarget:self action:@selector(handleTap:)];
+ tapRecognizer.numberOfTapsRequired = 1;
+ [self addGestureRecognizer:tapRecognizer];
+
+ mTouches = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
+ mNextTouchID = 0;
+ return self;
+}
+
+- (void)widgetDestroyed
+{
+ mGeckoChild = nullptr;
+ CFRelease(mTouches);
+}
+
+- (void)delayedTearDown
+{
+ [self removeFromSuperview];
+ [self release];
+}
+
+- (void)sendMouseEvent:(EventMessage) aType point:(LayoutDeviceIntPoint)aPoint widget:(nsWindow*)aWindow
+{
+ WidgetMouseEvent event(true, aType, aWindow,
+ WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
+
+ event.mRefPoint = aPoint;
+ event.mClickCount = 1;
+ event.button = WidgetMouseEvent::eLeftButton;
+ event.mTime = PR_IntervalNow();
+ event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
+
+ nsEventStatus status;
+ aWindow->DispatchEvent(&event, status);
+}
+
+- (void)handleTap:(UITapGestureRecognizer *)sender
+{
+ if (sender.state == UIGestureRecognizerStateEnded) {
+ ALOG("[ChildView[%p] handleTap]", self);
+ LayoutDeviceIntPoint lp = UIKitPointsToDevPixels([sender locationInView:self], [self contentScaleFactor]);
+ [self sendMouseEvent:eMouseMove point:lp widget:mGeckoChild];
+ [self sendMouseEvent:eMouseDown point:lp widget:mGeckoChild];
+ [self sendMouseEvent:eMouseUp point:lp widget:mGeckoChild];
+ }
+}
+
+- (void)sendTouchEvent:(EventMessage) aType touches:(NSSet*)aTouches widget:(nsWindow*)aWindow
+{
+ WidgetTouchEvent event(true, aType, aWindow);
+ //XXX: I think nativeEvent.timestamp * 1000 is probably usable here but
+ // I don't care that much right now.
+ event.mTime = PR_IntervalNow();
+ event.mTouches.SetCapacity(aTouches.count);
+ for (UITouch* touch in aTouches) {
+ LayoutDeviceIntPoint loc = UIKitPointsToDevPixels([touch locationInView:self], [self contentScaleFactor]);
+ LayoutDeviceIntPoint radius = UIKitPointsToDevPixels([touch majorRadius], [touch majorRadius]);
+ void* value;
+ if (!CFDictionaryGetValueIfPresent(mTouches, touch, (const void**)&value)) {
+ // This shouldn't happen.
+ NS_ASSERTION(false, "Got a touch that we didn't know about");
+ continue;
+ }
+ int id = reinterpret_cast<int>(value);
+ RefPtr<Touch> t = new Touch(id, loc, radius, 0.0f, 1.0f);
+ event.mRefPoint = loc;
+ event.mTouches.AppendElement(t);
+ }
+ aWindow->DispatchInputEvent(&event);
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesBegan", self);
+ if (!mGeckoChild)
+ return;
+
+ for (UITouch* touch : touches) {
+ CFDictionaryAddValue(mTouches, touch, (void*)mNextTouchID);
+ mNextTouchID++;
+ }
+ [self sendTouchEvent:eTouchStart
+ touches:[event allTouches]
+ widget:mGeckoChild];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesCancelled", self);
+ [self sendTouchEvent:eTouchCancel touches:touches widget:mGeckoChild];
+ for (UITouch* touch : touches) {
+ CFDictionaryRemoveValue(mTouches, touch);
+ }
+ if (CFDictionaryGetCount(mTouches) == 0) {
+ mNextTouchID = 0;
+ }
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesEnded", self);
+ if (!mGeckoChild)
+ return;
+
+ [self sendTouchEvent:eTouchEnd touches:touches widget:mGeckoChild];
+ for (UITouch* touch : touches) {
+ CFDictionaryRemoveValue(mTouches, touch);
+ }
+ if (CFDictionaryGetCount(mTouches) == 0) {
+ mNextTouchID = 0;
+ }
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ ALOG("[ChildView[%p] touchesMoved", self);
+ if (!mGeckoChild)
+ return;
+
+ [self sendTouchEvent:eTouchMove
+ touches:[event allTouches]
+ widget:mGeckoChild];
+}
+
+- (void)setNeedsDisplayInRect:(CGRect)aRect
+{
+ if ([self isUsingMainThreadOpenGL]) {
+ // Draw without calling drawRect. This prevent us from
+ // needing to access the normal window buffer surface unnecessarily, so we
+ // waste less time synchronizing the two surfaces.
+ if (!mWaitingForPaint) {
+ mWaitingForPaint = YES;
+ // Use NSRunLoopCommonModes instead of the default NSDefaultRunLoopMode
+ // so that the timer also fires while a native menu is open.
+ [self performSelector:@selector(drawUsingOpenGLCallback)
+ withObject:nil
+ afterDelay:0
+ inModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
+ }
+ }
+}
+
+- (BOOL)isUsingMainThreadOpenGL
+{
+ if (!mGeckoChild || ![self window])
+ return NO;
+
+ return mGeckoChild->GetLayerManager(nullptr)->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_OPENGL;
+}
+
+- (void)drawUsingOpenGL
+{
+ ALOG("drawUsingOpenGL");
+ PROFILER_LABEL("ChildView", "drawUsingOpenGL",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ if (!mGeckoChild->IsVisible())
+ return;
+
+ mWaitingForPaint = NO;
+
+ LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
+ LayoutDeviceIntRegion region(geckoBounds);
+
+ mGeckoChild->PaintWindow(region);
+}
+
+// Called asynchronously after setNeedsDisplay in order to avoid entering the
+// normal drawing machinery.
+- (void)drawUsingOpenGLCallback
+{
+ if (mWaitingForPaint) {
+ [self drawUsingOpenGL];
+ }
+}
+
+// The display system has told us that a portion of our view is dirty. Tell
+// gecko to paint it
+- (void)drawRect:(CGRect)aRect
+{
+ CGContextRef cgContext = UIGraphicsGetCurrentContext();
+ [self drawRect:aRect inContext:cgContext];
+}
+
+- (void)drawRect:(CGRect)aRect inContext:(CGContextRef)aContext
+{
+#ifdef DEBUG_UPDATE
+ LayoutDeviceIntRect geckoBounds = mGeckoChild->GetBounds();
+
+ fprintf (stderr, "---- Update[%p][%p] [%f %f %f %f] cgc: %p\n gecko bounds: [%d %d %d %d]\n",
+ self, mGeckoChild,
+ aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height, aContext,
+ geckoBounds.x, geckoBounds.y, geckoBounds.width, geckoBounds.height);
+
+ CGAffineTransform xform = CGContextGetCTM(aContext);
+ fprintf (stderr, " xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b, xform.c, xform.d, xform.tx, xform.ty);
+#endif
+
+ if (true) {
+ // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
+ // directly called from a delayed perform callback - without going through
+ // drawRect.
+ // Paints that come through here are triggered by something that Cocoa
+ // controls, for example by window resizing or window focus changes.
+
+ // Do GL composition and return.
+ [self drawUsingOpenGL];
+ return;
+ }
+ PROFILER_LABEL("ChildView", "drawRect",
+ js::ProfileEntry::Category::GRAPHICS);
+
+ // The CGContext that drawRect supplies us with comes with a transform that
+ // scales one user space unit to one Cocoa point, which can consist of
+ // multiple dev pixels. But Gecko expects its supplied context to be scaled
+ // to device pixels, so we need to reverse the scaling.
+ double scale = mGeckoChild->BackingScaleFactor();
+ CGContextSaveGState(aContext);
+ CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
+
+ CGSize viewSize = [self bounds].size;
+ gfx::IntSize backingSize(viewSize.width * scale, viewSize.height * scale);
+
+ CGContextSaveGState(aContext);
+
+ LayoutDeviceIntRegion region =
+ LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * scale),
+ NSToIntRound(aRect.origin.y * scale),
+ NSToIntRound(aRect.size.width * scale),
+ NSToIntRound(aRect.size.height * scale));
+
+ // Create Cairo objects.
+ RefPtr<gfxQuartzSurface> targetSurface;
+
+ RefPtr<gfxContext> targetContext;
+ if (gfxPlatform::GetPlatform()->SupportsAzureContentForType(gfx::BackendType::CAIRO)) {
+ // This is dead code unless you mess with prefs, but keep it around for
+ // debugging.
+ targetSurface = new gfxQuartzSurface(aContext, backingSize);
+ targetSurface->SetAllowUseAsSource(false);
+ RefPtr<gfx::DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(targetSurface,
+ backingSize);
+ if (!dt || !dt->IsValid()) {
+ gfxDevCrash(mozilla::gfx::LogReason::InvalidContext) << "Window context problem 2 " << backingSize;
+ return;
+ }
+ dt->AddUserData(&gfxContext::sDontUseAsSourceKey, dt, nullptr);
+ targetContext = gfxContext::CreateOrNull(dt);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("COREGRAPHICS is the only supported backend");
+ }
+ MOZ_ASSERT(targetContext); // already checked for valid draw targets above
+
+ // Set up the clip region.
+ targetContext->NewPath();
+ for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
+ const LayoutDeviceIntRect& r = iter.Get();
+ targetContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height));
+ }
+ targetContext->Clip();
+
+ //nsAutoRetainCocoaObject kungFuDeathGrip(self);
+ bool painted = false;
+ if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ nsBaseWidget::AutoLayerManagerSetup
+ setupLayerManager(mGeckoChild, targetContext, BufferMode::BUFFER_NONE);
+ painted = mGeckoChild->PaintWindow(region);
+ } else if (mGeckoChild->GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT) {
+ // We only need this so that we actually get DidPaintWindow fired
+ painted = mGeckoChild->PaintWindow(region);
+ }
+
+ targetContext = nullptr;
+ targetSurface = nullptr;
+
+ CGContextRestoreGState(aContext);
+
+ // Undo the scale transform so that from now on the context is in
+ // CocoaPoints again.
+ CGContextRestoreGState(aContext);
+ if (!painted && [self isOpaque]) {
+ // Gecko refused to draw, but we've claimed to be opaque, so we have to
+ // draw something--fill with white.
+ CGContextSetRGBFillColor(aContext, 1, 1, 1, 1);
+ CGContextFillRect(aContext, aRect);
+ }
+
+#ifdef DEBUG_UPDATE
+ fprintf (stderr, "---- update done ----\n");
+
+#if 0
+ CGContextSetRGBStrokeColor (aContext,
+ ((((unsigned long)self) & 0xff)) / 255.0,
+ ((((unsigned long)self) & 0xff00) >> 8) / 255.0,
+ ((((unsigned long)self) & 0xff0000) >> 16) / 255.0,
+ 0.5);
+#endif
+ CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8);
+ CGContextSetLineWidth(aContext, 4.0);
+ CGContextStrokeRect(aContext, aRect);
+#endif
+}
+@end
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, Inherited)
+
+nsWindow::nsWindow()
+: mNativeView(nullptr),
+ mVisible(false),
+ mParent(nullptr)
+{
+}
+
+nsWindow::~nsWindow()
+{
+ [mNativeView widgetDestroyed]; // Safe if mNativeView is nil.
+ TearDownView(); // Safe if called twice.
+}
+
+void nsWindow::TearDownView()
+{
+ if (!mNativeView)
+ return;
+
+ [mNativeView performSelectorOnMainThread:@selector(delayedTearDown) withObject:nil waitUntilDone:false];
+ mNativeView = nil;
+}
+
+bool
+nsWindow::IsTopLevel()
+{
+ return mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog ||
+ mWindowType == eWindowType_invisible;
+}
+
+//
+// nsIWidget
+//
+
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ ALOG("nsWindow[%p]::Create %p/%p [%d %d %d %d]", (void*)this, (void*)aParent, (void*)aNativeParent, aRect.x, aRect.y, aRect.width, aRect.height);
+ nsWindow* parent = (nsWindow*) aParent;
+ ChildView* nativeParent = (ChildView*)aNativeParent;
+
+ if (parent == nullptr && nativeParent)
+ parent = nativeParent->mGeckoChild;
+ if (parent && nativeParent == nullptr)
+ nativeParent = parent->mNativeView;
+
+ // for toplevel windows, bounds are fixed to full screen size
+ if (parent == nullptr) {
+ if (nsAppShell::gWindow == nil) {
+ mBounds = UIKitScreenManager::GetBounds();
+ } else {
+ CGRect cgRect = [nsAppShell::gWindow bounds];
+ mBounds.x = cgRect.origin.x;
+ mBounds.y = cgRect.origin.y;
+ mBounds.width = cgRect.size.width;
+ mBounds.height = cgRect.size.height;
+ }
+ } else {
+ mBounds = aRect;
+ }
+
+ ALOG("nsWindow[%p]::Create bounds: %d %d %d %d", (void*)this,
+ mBounds.x, mBounds.y, mBounds.width, mBounds.height);
+
+ // Set defaults which can be overriden from aInitData in BaseCreate
+ mWindowType = eWindowType_toplevel;
+ mBorderStyle = eBorderStyle_default;
+
+ Inherited::BaseCreate(aParent, aInitData);
+
+ NS_ASSERTION(IsTopLevel() || parent, "non top level window doesn't have a parent!");
+
+ mNativeView = [[ChildView alloc] initWithFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor()) geckoChild:this];
+ mNativeView.hidden = YES;
+
+ if (parent) {
+ parent->mChildren.AppendElement(this);
+ mParent = parent;
+ }
+
+ if (nativeParent) {
+ [nativeParent addSubview:mNativeView];
+ } else if (nsAppShell::gWindow) {
+ [nsAppShell::gWindow.rootViewController.view addSubview:mNativeView];
+ }
+ else {
+ [nsAppShell::gTopLevelViews addObject:mNativeView];
+ }
+
+ return NS_OK;
+}
+
+void
+nsWindow::Destroy()
+{
+ for (uint32_t i = 0; i < mChildren.Length(); ++i) {
+ // why do we still have children?
+ mChildren[i]->SetParent(nullptr);
+ }
+
+ if (mParent)
+ mParent->mChildren.RemoveElement(this);
+
+ [mNativeView widgetDestroyed];
+
+ nsBaseWidget::Destroy();
+
+ //ReportDestroyEvent();
+
+ TearDownView();
+
+ nsBaseWidget::OnDestroy();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>& config)
+{
+ for (uint32_t i = 0; i < config.Length(); ++i) {
+ nsWindow *childWin = (nsWindow*) config[i].mChild.get();
+ childWin->Resize(config[i].mBounds.x,
+ config[i].mBounds.y,
+ config[i].mBounds.width,
+ config[i].mBounds.height,
+ false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ if (aState != mVisible) {
+ mNativeView.hidden = aState ? NO : YES;
+ if (aState) {
+ UIView* parentView = mParent ? mParent->mNativeView : nsAppShell::gWindow.rootViewController.view;
+ [parentView bringSubviewToFront:mNativeView];
+ [mNativeView setNeedsDisplay];
+ }
+ mVisible = aState;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Move(double aX, double aY)
+{
+ if (!mNativeView || (mBounds.x == aX && mBounds.y == aY))
+ return NS_OK;
+
+ //XXX: handle this
+ // The point we have is in Gecko coordinates (origin top-left). Convert
+ // it to Cocoa ones (origin bottom-left).
+ mBounds.x = aX;
+ mBounds.y = aY;
+
+ mNativeView.frame = DevPixelsToUIKitPoints(mBounds, BackingScaleFactor());
+
+ if (mVisible)
+ [mNativeView setNeedsDisplay];
+
+ ReportMoveEvent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aX, double aY,
+ double aWidth, double aHeight,
+ bool aRepaint)
+{
+ BOOL isMoving = (mBounds.x != aX || mBounds.y != aY);
+ BOOL isResizing = (mBounds.width != aWidth || mBounds.height != aHeight);
+ if (!mNativeView || (!isMoving && !isResizing))
+ return NS_OK;
+
+ if (isMoving) {
+ mBounds.x = aX;
+ mBounds.y = aY;
+ }
+ if (isResizing) {
+ mBounds.width = aWidth;
+ mBounds.height = aHeight;
+ }
+
+ [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+
+ if (mVisible && aRepaint)
+ [mNativeView setNeedsDisplay];
+
+ if (isMoving)
+ ReportMoveEvent();
+
+ if (isResizing)
+ ReportSizeEvent();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWindow::Resize(double aWidth, double aHeight, bool aRepaint)
+{
+ if (!mNativeView || (mBounds.width == aWidth && mBounds.height == aHeight))
+ return NS_OK;
+
+ mBounds.width = aWidth;
+ mBounds.height = aHeight;
+
+ [mNativeView setFrame:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+
+ if (mVisible && aRepaint)
+ [mNativeView setNeedsDisplay];
+
+ ReportSizeEvent();
+
+ return NS_OK;
+}
+
+void
+nsWindow::SetSizeMode(nsSizeMode aMode)
+{
+ if (aMode == static_cast<int32_t>(mSizeMode)) {
+ return;
+ }
+
+ mSizeMode = static_cast<nsSizeMode>(aMode);
+ if (aMode == nsSizeMode_Maximized || aMode == nsSizeMode_Fullscreen) {
+ // Resize to fill screen
+ nsBaseWidget::InfallibleMakeFullScreen(true);
+ }
+ ReportSizeModeEvent(aMode);
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ if (!mNativeView || !mVisible)
+ return NS_OK;
+
+ MOZ_RELEASE_ASSERT(GetLayerManager()->GetBackendType() != LayersBackend::LAYERS_CLIENT,
+ "Shouldn't need to invalidate with accelerated OMTC layers!");
+
+
+ [mNativeView setNeedsLayout];
+ [mNativeView setNeedsDisplayInRect:DevPixelsToUIKitPoints(mBounds, BackingScaleFactor())];
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ [[mNativeView window] makeKeyWindow];
+ [mNativeView becomeFirstResponder];
+ return NS_OK;
+}
+
+void nsWindow::WillPaintWindow()
+{
+ if (mWidgetListener) {
+ mWidgetListener->WillPaintWindow(this);
+ }
+}
+
+bool nsWindow::PaintWindow(LayoutDeviceIntRegion aRegion)
+{
+ if (!mWidgetListener)
+ return false;
+
+ bool returnValue = false;
+ returnValue = mWidgetListener->PaintWindow(this, aRegion);
+
+ if (mWidgetListener) {
+ mWidgetListener->DidPaintWindow();
+ }
+
+ return returnValue;
+}
+
+void nsWindow::ReportMoveEvent()
+{
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+}
+
+void nsWindow::ReportSizeModeEvent(nsSizeMode aMode)
+{
+ if (mWidgetListener) {
+ // This is terrible.
+ nsSizeMode theMode;
+ switch (aMode) {
+ case nsSizeMode_Maximized:
+ theMode = nsSizeMode_Maximized;
+ break;
+ case nsSizeMode_Fullscreen:
+ theMode = nsSizeMode_Fullscreen;
+ break;
+ default:
+ return;
+ }
+ mWidgetListener->SizeModeChanged(theMode);
+ }
+}
+
+void nsWindow::ReportSizeEvent()
+{
+ if (mWidgetListener) {
+ LayoutDeviceIntRect innerBounds = GetClientBounds();
+ mWidgetListener->WindowResized(this, innerBounds.width, innerBounds.height);
+ }
+}
+
+LayoutDeviceIntRect
+nsWindow::GetScreenBounds()
+{
+ return LayoutDeviceIntRect(WidgetToScreenOffset(), mBounds.Size());
+}
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset()
+{
+ LayoutDeviceIntPoint offset(0, 0);
+ if (mParent) {
+ offset = mParent->WidgetToScreenOffset();
+ }
+
+ CGPoint temp = [mNativeView convertPoint:temp toView:nil];
+
+ if (!mParent && nsAppShell::gWindow) {
+ // convert to screen coords
+ temp = [nsAppShell::gWindow convertPoint:temp toWindow:nil];
+ }
+
+ offset.x += temp.x;
+ offset.y += temp.y;
+
+ return offset;
+}
+
+NS_IMETHODIMP
+nsWindow::DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus)
+{
+ aStatus = nsEventStatus_eIgnore;
+ nsCOMPtr<nsIWidget> kungFuDeathGrip(aEvent->mWidget);
+
+ if (mWidgetListener)
+ aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ //TODO: actually show VKB
+ mInputContext = aContext;
+}
+
+NS_IMETHODIMP_(mozilla::widget::InputContext)
+nsWindow::GetInputContext()
+{
+ return mInputContext;
+}
+
+void
+nsWindow::SetBackgroundColor(const nscolor &aColor)
+{
+ mNativeView.backgroundColor = [UIColor colorWithRed:NS_GET_R(aColor)
+ green:NS_GET_G(aColor)
+ blue:NS_GET_B(aColor)
+ alpha:NS_GET_A(aColor)];
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType)
+{
+ void* retVal = nullptr;
+
+ switch (aDataType)
+ {
+ case NS_NATIVE_WIDGET:
+ case NS_NATIVE_DISPLAY:
+ retVal = (void*)mNativeView;
+ break;
+
+ case NS_NATIVE_WINDOW:
+ retVal = [mNativeView window];
+ break;
+
+ case NS_NATIVE_GRAPHIC:
+ NS_ERROR("Requesting NS_NATIVE_GRAPHIC on a UIKit child view!");
+ break;
+
+ case NS_NATIVE_OFFSETX:
+ retVal = 0;
+ break;
+
+ case NS_NATIVE_OFFSETY:
+ retVal = 0;
+ break;
+
+ case NS_NATIVE_PLUGIN_PORT:
+ // not implemented
+ break;
+
+ case NS_RAW_NATIVE_IME_CONTEXT:
+ retVal = GetPseudoIMEContext();
+ if (retVal) {
+ break;
+ }
+ retVal = NS_ONLY_ONE_NATIVE_IME_CONTEXT;
+ break;
+ }
+
+ return retVal;
+}
+
+CGFloat
+nsWindow::BackingScaleFactor()
+{
+ if (mNativeView) {
+ return [mNativeView contentScaleFactor];
+ }
+ return [UIScreen mainScreen].scale;
+}
+
+int32_t
+nsWindow::RoundsWidgetCoordinatesTo()
+{
+ if (BackingScaleFactor() == 2.0) {
+ return 2;
+ }
+ return 1;
+}
diff --git a/xpcom/base/MacHelpers.h b/xpcom/base/MacHelpers.h
new file mode 100644
index 0000000000..2bff0367ef
--- /dev/null
+++ b/xpcom/base/MacHelpers.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef mozilla_MacHelpers_h
+#define mozilla_MacHelpers_h
+
+#include "nsString.h"
+
+namespace mozilla {
+
+nsresult GetSelectedCityInfo(nsAString& aCountryCode);
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/base/MacHelpers.mm b/xpcom/base/MacHelpers.mm
new file mode 100644
index 0000000000..e0b5e6c35b
--- /dev/null
+++ b/xpcom/base/MacHelpers.mm
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsString.h"
+#include "MacHelpers.h"
+#include "nsObjCExceptions.h"
+
+#import <Foundation/Foundation.h>
+
+namespace mozilla {
+
+nsresult
+GetSelectedCityInfo(nsAString& aCountryCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ // Can be replaced with [[NSLocale currentLocale] countryCode] once we build
+ // with the 10.12 SDK.
+ id countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
+
+ if (![countryCode isKindOfClass:[NSString class]]) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const char* countryCodeUTF8 = [(NSString*)countryCode UTF8String];
+
+ if (!countryCodeUTF8) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AppendUTF8toUTF16(countryCodeUTF8, aCountryCode);
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+} // namespace Mozilla
+
diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build
index 03a0fa43a7..a59528c576 100644
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -28,6 +28,17 @@ XPIDL_SOURCES += [
'nsrootidl.idl',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ XPIDL_SOURCES += [
+ 'nsIMacUtils.idl',
+ ]
+ EXPORTS.mozilla += [
+ 'MacHelpers.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'MacHelpers.mm',
+ ]
+
XPIDL_MODULE = 'xpcom_base'
EXPORTS += [
@@ -125,7 +136,11 @@ if CONFIG['OS_ARCH'] == 'Linux':
'SystemMemoryReporter.cpp',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'nsMacUtilsImpl.cpp',
+ ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
SOURCES += [
'nsCrashOnException.cpp',
]
diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp
index fb4831eeda..c4d4437a9b 100644
--- a/xpcom/base/nsDebugImpl.cpp
+++ b/xpcom/base/nsDebugImpl.cpp
@@ -34,7 +34,7 @@
#include "nsString.h"
#endif
-#if defined(__DragonFly__) || defined(__FreeBSD__) \
+#if defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) \
|| defined(__NetBSD__) || defined(__OpenBSD__)
#include <stdbool.h>
#include <unistd.h>
@@ -58,7 +58,9 @@
#define KINFO_PROC struct kinfo_proc
#endif
-#if defined(__DragonFly__)
+#if defined(XP_MACOSX)
+#define KP_FLAGS kp_proc.p_flag
+#elif defined(__DragonFly__)
#define KP_FLAGS kp_flags
#elif defined(__FreeBSD__)
#define KP_FLAGS ki_flag
@@ -162,7 +164,7 @@ nsDebugImpl::GetIsDebuggerAttached(bool* aResult)
#if defined(XP_WIN)
*aResult = ::IsDebuggerPresent();
-#elif defined(__DragonFly__) || defined(__FreeBSD__) \
+#elif defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) \
|| defined(__NetBSD__) || defined(__OpenBSD__)
// Specify the info we're looking for
int mib[] = {
@@ -424,6 +426,8 @@ RealBreak()
{
#if defined(_WIN32)
::DebugBreak();
+#elif defined(XP_MACOSX)
+ raise(SIGTRAP);
#elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__))
asm("int $3");
#elif defined(__arm__)
@@ -507,6 +511,12 @@ Break(const char* aMsg)
}
RealBreak();
+#elif defined(XP_MACOSX)
+ /* Note that we put this Mac OS X test above the GNUC/x86 test because the
+ * GNUC/x86 test is also true on Intel Mac OS X and we want the PPC/x86
+ * impls to be the same.
+ */
+ RealBreak();
#elif defined(__GNUC__) && (defined(__i386__) || defined(__i386) || defined(__x86_64__))
RealBreak();
#elif defined(__arm__)
diff --git a/xpcom/base/nsIMacUtils.idl b/xpcom/base/nsIMacUtils.idl
new file mode 100644
index 0000000000..9a60df47cd
--- /dev/null
+++ b/xpcom/base/nsIMacUtils.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl"
+
+/**
+ * nsIMacUtils: Generic globally-available Mac-specific utilities.
+ */
+
+[scriptable, uuid(5E9072D7-FF95-455E-9466-8AF9841A72EC)]
+interface nsIMacUtils : nsISupports
+{
+ /**
+ * True when the main executable is a fat file supporting at least
+ * ppc and x86 (universal binary).
+ */
+ readonly attribute boolean isUniversalBinary;
+
+ /**
+ * Returns a string containing a list of architectures delimited
+ * by "-". Architecture sets are always in the same order:
+ * ppc > i386 > ppc64 > x86_64 > (future additions)
+ */
+ readonly attribute AString architecturesInBinary;
+
+ /**
+ * True when running under binary translation (Rosetta).
+ */
+ readonly attribute boolean isTranslated;
+};
diff --git a/xpcom/base/nsMacUtilsImpl.cpp b/xpcom/base/nsMacUtilsImpl.cpp
new file mode 100644
index 0000000000..eb93cc555e
--- /dev/null
+++ b/xpcom/base/nsMacUtilsImpl.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 8; 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 "nsMacUtilsImpl.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+
+NS_IMPL_ISUPPORTS(nsMacUtilsImpl, nsIMacUtils)
+
+nsresult
+nsMacUtilsImpl::GetArchString(nsAString& aArchString)
+{
+ if (!mBinaryArchs.IsEmpty()) {
+ aArchString.Assign(mBinaryArchs);
+ return NS_OK;
+ }
+
+ aArchString.Truncate();
+
+ bool foundPPC = false,
+ foundX86 = false,
+ foundPPC64 = false,
+ foundX86_64 = false;
+
+ CFBundleRef mainBundle = ::CFBundleGetMainBundle();
+ if (!mainBundle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle);
+ if (!archList) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFIndex archCount = ::CFArrayGetCount(archList);
+ for (CFIndex i = 0; i < archCount; i++) {
+ CFNumberRef arch =
+ static_cast<CFNumberRef>(::CFArrayGetValueAtIndex(archList, i));
+
+ int archInt = 0;
+ if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) {
+ ::CFRelease(archList);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (archInt == kCFBundleExecutableArchitecturePPC) {
+ foundPPC = true;
+ } else if (archInt == kCFBundleExecutableArchitectureI386) {
+ foundX86 = true;
+ } else if (archInt == kCFBundleExecutableArchitecturePPC64) {
+ foundPPC64 = true;
+ } else if (archInt == kCFBundleExecutableArchitectureX86_64) {
+ foundX86_64 = true;
+ }
+ }
+
+ ::CFRelease(archList);
+
+ // The order in the string must always be the same so
+ // don't do this in the loop.
+ if (foundPPC) {
+ mBinaryArchs.AppendLiteral("ppc");
+ }
+
+ if (foundX86) {
+ if (!mBinaryArchs.IsEmpty()) {
+ mBinaryArchs.Append('-');
+ }
+ mBinaryArchs.AppendLiteral("i386");
+ }
+
+ if (foundPPC64) {
+ if (!mBinaryArchs.IsEmpty()) {
+ mBinaryArchs.Append('-');
+ }
+ mBinaryArchs.AppendLiteral("ppc64");
+ }
+
+ if (foundX86_64) {
+ if (!mBinaryArchs.IsEmpty()) {
+ mBinaryArchs.Append('-');
+ }
+ mBinaryArchs.AppendLiteral("x86_64");
+ }
+
+ aArchString.Assign(mBinaryArchs);
+
+ return (aArchString.IsEmpty() ? NS_ERROR_FAILURE : NS_OK);
+}
+
+NS_IMETHODIMP
+nsMacUtilsImpl::GetIsUniversalBinary(bool* aIsUniversalBinary)
+{
+ if (NS_WARN_IF(!aIsUniversalBinary)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aIsUniversalBinary = false;
+
+ nsAutoString archString;
+ nsresult rv = GetArchString(archString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The delimiter char in the arch string is '-', so if that character
+ // is in the string we know we have multiple architectures.
+ *aIsUniversalBinary = (archString.Find("-") > -1);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacUtilsImpl::GetArchitecturesInBinary(nsAString& aArchString)
+{
+ return GetArchString(aArchString);
+}
+
+// True when running under binary translation (Rosetta).
+NS_IMETHODIMP
+nsMacUtilsImpl::GetIsTranslated(bool* aIsTranslated)
+{
+#ifdef __ppc__
+ static bool sInitialized = false;
+
+ // Initialize sIsNative to 1. If the sysctl fails because it doesn't
+ // exist, then translation is not possible, so the process must not be
+ // running translated.
+ static int32_t sIsNative = 1;
+
+ if (!sInitialized) {
+ size_t sz = sizeof(sIsNative);
+ sysctlbyname("sysctl.proc_native", &sIsNative, &sz, nullptr, 0);
+ sInitialized = true;
+ }
+
+ *aIsTranslated = !sIsNative;
+#else
+ // Translation only exists for ppc code. Other architectures aren't
+ // translated.
+ *aIsTranslated = false;
+#endif
+
+ return NS_OK;
+}
diff --git a/xpcom/base/nsMacUtilsImpl.h b/xpcom/base/nsMacUtilsImpl.h
new file mode 100644
index 0000000000..12a1add41a
--- /dev/null
+++ b/xpcom/base/nsMacUtilsImpl.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef nsMacUtilsImpl_h___
+#define nsMacUtilsImpl_h___
+
+#include "nsIMacUtils.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+
+class nsMacUtilsImpl final : public nsIMacUtils
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMACUTILS
+
+ nsMacUtilsImpl()
+ {
+ }
+
+private:
+ ~nsMacUtilsImpl()
+ {
+ }
+
+ nsresult GetArchString(nsAString& aArchString);
+
+ // A string containing a "-" delimited list of architectures
+ // in our binary.
+ nsString mBinaryArchs;
+};
+
+// Global singleton service
+// 697BD3FD-43E5-41CE-AD5E-C339175C0818
+#define NS_MACUTILSIMPL_CID \
+ {0x697BD3FD, 0x43E5, 0x41CE, {0xAD, 0x5E, 0xC3, 0x39, 0x17, 0x5C, 0x08, 0x18}}
+#define NS_MACUTILSIMPL_CONTRACTID "@mozilla.org/xpcom/mac-utils;1"
+
+#endif /* nsMacUtilsImpl_h___ */
diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp
index 4397f470e8..c47d3c8415 100644
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -394,6 +394,168 @@ ResidentFastDistinguishedAmount(int64_t* aN)
return ResidentDistinguishedAmount(aN);
}
+#elif defined(XP_MACOSX)
+
+#include <mach/mach_init.h>
+#include <mach/mach_vm.h>
+#include <mach/shared_region.h>
+#include <mach/task.h>
+#include <sys/sysctl.h>
+
+static MOZ_MUST_USE bool
+GetTaskBasicInfo(struct task_basic_info* aTi)
+{
+ mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
+ kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO,
+ (task_info_t)aTi, &count);
+ return kr == KERN_SUCCESS;
+}
+
+// The VSIZE figure on Mac includes huge amounts of shared memory and is always
+// absurdly high, eg. 2GB+ even at start-up. But both 'top' and 'ps' report
+// it, so we might as well too.
+#define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1
+static MOZ_MUST_USE nsresult
+VsizeDistinguishedAmount(int64_t* aN)
+{
+ task_basic_info ti;
+ if (!GetTaskBasicInfo(&ti)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aN = ti.virtual_size;
+ return NS_OK;
+}
+
+// If we're using jemalloc on Mac, we need to instruct jemalloc to purge the
+// pages it has madvise(MADV_FREE)'d before we read our RSS in order to get
+// an accurate result. The OS will take away MADV_FREE'd pages when there's
+// memory pressure, so ideally, they shouldn't count against our RSS.
+//
+// Purging these pages can take a long time for some users (see bug 789975),
+// so we provide the option to get the RSS without purging first.
+static MOZ_MUST_USE nsresult
+ResidentDistinguishedAmountHelper(int64_t* aN, bool aDoPurge)
+{
+#ifdef HAVE_JEMALLOC_STATS
+ if (aDoPurge) {
+ jemalloc_purge_freed_pages();
+ }
+#endif
+
+ task_basic_info ti;
+ if (!GetTaskBasicInfo(&ti)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aN = ti.resident_size;
+ return NS_OK;
+}
+
+static MOZ_MUST_USE nsresult
+ResidentFastDistinguishedAmount(int64_t* aN)
+{
+ return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false);
+}
+
+static MOZ_MUST_USE nsresult
+ResidentDistinguishedAmount(int64_t* aN)
+{
+ return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true);
+}
+
+#define HAVE_RESIDENT_UNIQUE_REPORTER 1
+
+static bool
+InSharedRegion(mach_vm_address_t aAddr, cpu_type_t aType)
+{
+ mach_vm_address_t base;
+ mach_vm_address_t size;
+
+ switch (aType) {
+ case CPU_TYPE_ARM:
+ base = SHARED_REGION_BASE_ARM;
+ size = SHARED_REGION_SIZE_ARM;
+ break;
+ case CPU_TYPE_I386:
+ base = SHARED_REGION_BASE_I386;
+ size = SHARED_REGION_SIZE_I386;
+ break;
+ case CPU_TYPE_X86_64:
+ base = SHARED_REGION_BASE_X86_64;
+ size = SHARED_REGION_SIZE_X86_64;
+ break;
+ default:
+ return false;
+ }
+
+ return base <= aAddr && aAddr < (base + size);
+}
+
+static MOZ_MUST_USE nsresult
+ResidentUniqueDistinguishedAmount(int64_t* aN)
+{
+ if (!aN) {
+ return NS_ERROR_FAILURE;
+ }
+
+ cpu_type_t cpu_type;
+ size_t len = sizeof(cpu_type);
+ if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Roughly based on libtop_update_vm_regions in
+ // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c
+ size_t privatePages = 0;
+ mach_vm_size_t size = 0;
+ for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; ; addr += size) {
+ vm_region_top_info_data_t info;
+ mach_msg_type_number_t infoCount = VM_REGION_TOP_INFO_COUNT;
+ mach_port_t objectName;
+
+ kern_return_t kr =
+ mach_vm_region(mach_task_self(), &addr, &size, VM_REGION_TOP_INFO,
+ reinterpret_cast<vm_region_info_t>(&info),
+ &infoCount, &objectName);
+ if (kr == KERN_INVALID_ADDRESS) {
+ // Done iterating VM regions.
+ break;
+ } else if (kr != KERN_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (InSharedRegion(addr, cpu_type) && info.share_mode != SM_PRIVATE) {
+ continue;
+ }
+
+ switch (info.share_mode) {
+ case SM_LARGE_PAGE:
+ // NB: Large pages are not shareable and always resident.
+ case SM_PRIVATE:
+ privatePages += info.private_pages_resident;
+ privatePages += info.shared_pages_resident;
+ break;
+ case SM_COW:
+ privatePages += info.private_pages_resident;
+ if (info.ref_count == 1) {
+ // Treat copy-on-write pages as private if they only have one reference.
+ privatePages += info.shared_pages_resident;
+ }
+ break;
+ case SM_SHARED:
+ default:
+ break;
+ }
+ }
+
+ vm_size_t pageSize;
+ if (host_page_size(mach_host_self(), &pageSize) != KERN_SUCCESS) {
+ pageSize = PAGE_SIZE;
+ }
+
+ *aN = privatePages * pageSize;
+ return NS_OK;
+}
+
#elif defined(XP_WIN)
#include <windows.h>
@@ -984,7 +1146,9 @@ ResidentPeakDistinguishedAmount(int64_t* aN)
// - Solaris: pages? But some sources it actually always returns 0, so
// check for that
// - Linux, {Net/Open/Free}BSD, DragonFly: KiB
-#if defined(XP_SOLARIS)
+#ifdef XP_MACOSX
+ *aN = usage.ru_maxrss;
+#elif defined(XP_SOLARIS)
*aN = usage.ru_maxrss * getpagesize();
#else
*aN = usage.ru_maxrss * 1024;
diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp
index df106e4e05..28c4915976 100644
--- a/xpcom/base/nsSystemInfo.cpp
+++ b/xpcom/base/nsSystemInfo.cpp
@@ -25,6 +25,10 @@
#include "nsWindowsHelpers.h"
#endif
+#ifdef XP_MACOSX
+#include "MacHelpers.h"
+#endif
+
#ifdef MOZ_WIDGET_GTK
#include <gtk/gtk.h>
#include <dlfcn.h>
@@ -40,6 +44,10 @@
#include <string>
#endif
+#ifdef XP_MACOSX
+#include <sys/sysctl.h>
+#endif
+
// Slot for NS_InitXPCOM2 to pass information to nsSystemInfo::Init.
// Only set to nonzero (potentially) if XP_UNIX. On such systems, the
// system call to discover the appropriate value is not thread-safe,
@@ -409,6 +417,67 @@ nsSystemInfo::Init()
cpuFamily = si.wProcessorLevel;
cpuModel = si.wProcessorRevision >> 8;
cpuStepping = si.wProcessorRevision & 0xFF;
+#elif defined (XP_MACOSX)
+ // CPU speed
+ uint64_t sysctlValue64 = 0;
+ uint32_t sysctlValue32 = 0;
+ size_t len = 0;
+ len = sizeof(sysctlValue64);
+ if (!sysctlbyname("hw.cpufrequency_max", &sysctlValue64, &len, NULL, 0)) {
+ cpuSpeed = static_cast<int>(sysctlValue64/1000000);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue64) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("hw.physicalcpu_max", &sysctlValue32, &len, NULL, 0)) {
+ physicalCPUs = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("hw.logicalcpu_max", &sysctlValue32, &len, NULL, 0)) {
+ logicalCPUs = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue64);
+ if (!sysctlbyname("hw.l2cachesize", &sysctlValue64, &len, NULL, 0)) {
+ cacheSizeL2 = static_cast<int>(sysctlValue64/1024);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue64) == len);
+
+ len = sizeof(sysctlValue64);
+ if (!sysctlbyname("hw.l3cachesize", &sysctlValue64, &len, NULL, 0)) {
+ cacheSizeL3 = static_cast<int>(sysctlValue64/1024);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue64) == len);
+
+ if (!sysctlbyname("machdep.cpu.vendor", NULL, &len, NULL, 0)) {
+ char* cpuVendorStr = new char[len];
+ if (!sysctlbyname("machdep.cpu.vendor", cpuVendorStr, &len, NULL, 0)) {
+ cpuVendor = cpuVendorStr;
+ }
+ delete [] cpuVendorStr;
+ }
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("machdep.cpu.family", &sysctlValue32, &len, NULL, 0)) {
+ cpuFamily = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("machdep.cpu.model", &sysctlValue32, &len, NULL, 0)) {
+ cpuModel = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
+ len = sizeof(sysctlValue32);
+ if (!sysctlbyname("machdep.cpu.stepping", &sysctlValue32, &len, NULL, 0)) {
+ cpuStepping = static_cast<int>(sysctlValue32);
+ }
+ MOZ_ASSERT(sizeof(sysctlValue32) == len);
+
#elif defined(XP_LINUX)
// Get vendor, family, model, stepping, physical cores, L3 cache size
// from /proc/cpuinfo file
@@ -585,6 +654,14 @@ nsSystemInfo::Init()
}
#endif
+#if defined(XP_MACOSX)
+ nsAutoString countryCode;
+ if (NS_SUCCEEDED(GetSelectedCityInfo(countryCode))) {
+ rv = SetPropertyAsAString(NS_LITERAL_STRING("countryCode"), countryCode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+
#if defined(MOZ_WIDGET_GTK)
// This must be done here because NSPR can only separate OS's when compiled, not libraries.
// 64 bytes is going to be well enough for "GTK " followed by 3 integers
diff --git a/xpcom/base/nsUUIDGenerator.cpp b/xpcom/base/nsUUIDGenerator.cpp
index e3974b5fca..645b34b87e 100644
--- a/xpcom/base/nsUUIDGenerator.cpp
+++ b/xpcom/base/nsUUIDGenerator.cpp
@@ -6,6 +6,8 @@
#if defined(XP_WIN)
#include <windows.h>
#include <objbase.h>
+#elif defined(XP_MACOSX)
+#include <CoreFoundation/CoreFoundation.h>
#else
#include <stdlib.h>
#include "prrng.h"
@@ -32,7 +34,7 @@ nsUUIDGenerator::Init()
// We're a service, so we're guaranteed that Init() is not going
// to be reentered while we're inside Init().
-#if !defined(XP_WIN) && !defined(HAVE_ARC4RANDOM)
+#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM)
/* initialize random number generator using NSPR random noise */
unsigned int seed;
@@ -69,7 +71,7 @@ nsUUIDGenerator::Init()
}
#endif
-#endif /* non XP_WIN and non ARC4RANDOM */
+#endif /* non XP_WIN and non XP_MACOSX and non ARC4RANDOM */
return NS_OK;
}
@@ -104,7 +106,17 @@ nsUUIDGenerator::GenerateUUIDInPlace(nsID* aId)
if (FAILED(hr)) {
return NS_ERROR_FAILURE;
}
-#else /* not windows; generate randomness using random(). */
+#elif defined(XP_MACOSX)
+ CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
+ if (!uuid) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid);
+ memcpy(aId, &bytes, sizeof(nsID));
+
+ CFRelease(uuid);
+#else /* not windows or OS X; generate randomness using random(). */
/* XXX we should be saving the return of setstate here and switching
* back to it; instead, we use the value returned when we called
* initstate, since older glibc's have broken setstate() return values
diff --git a/xpcom/base/nsUUIDGenerator.h b/xpcom/base/nsUUIDGenerator.h
index 1eadd14eea..bfd2805eaf 100644
--- a/xpcom/base/nsUUIDGenerator.h
+++ b/xpcom/base/nsUUIDGenerator.h
@@ -28,7 +28,7 @@ private:
protected:
mozilla::Mutex mLock;
-#if !defined(XP_WIN) && !defined(HAVE_ARC4RANDOM)
+#if !defined(XP_WIN) && !defined(XP_MACOSX) && !defined(HAVE_ARC4RANDOM)
char mState[128];
char* mSavedState;
uint8_t mRBytes;
diff --git a/xpcom/build/BinaryPath.h b/xpcom/build/BinaryPath.h
index 8ad7e8aac2..6c2622a557 100644
--- a/xpcom/build/BinaryPath.h
+++ b/xpcom/build/BinaryPath.h
@@ -9,6 +9,8 @@
#include "nsXPCOMPrivate.h" // for MAXPATHLEN
#ifdef XP_WIN
#include <windows.h>
+#elif defined(XP_MACOSX)
+#include <CoreFoundation/CoreFoundation.h>
#elif defined(XP_UNIX)
#include <sys/stat.h>
#include <string.h>
@@ -41,6 +43,46 @@ private:
return NS_ERROR_FAILURE;
}
+#elif defined(XP_MACOSX)
+ static nsresult Get(const char* argv0, char aResult[MAXPATHLEN])
+ {
+ // Works even if we're not bundled.
+ CFBundleRef appBundle = CFBundleGetMainBundle();
+ if (!appBundle) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle);
+ if (!executableURL) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ if (CFURLGetFileSystemRepresentation(executableURL, false, (UInt8*)aResult,
+ MAXPATHLEN)) {
+ // Sanitize path in case the app was launched from Terminal via
+ // './firefox' for example.
+ size_t readPos = 0;
+ size_t writePos = 0;
+ while (aResult[readPos] != '\0') {
+ if (aResult[readPos] == '.' && aResult[readPos + 1] == '/') {
+ readPos += 2;
+ } else {
+ aResult[writePos] = aResult[readPos];
+ readPos++;
+ writePos++;
+ }
+ }
+ aResult[writePos] = '\0';
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ CFRelease(executableURL);
+ return rv;
+ }
+
#elif defined(XP_UNIX)
static nsresult Get(const char* aArgv0, char aResult[MAXPATHLEN])
{
diff --git a/xpcom/build/PoisonIOInterposer.h b/xpcom/build/PoisonIOInterposer.h
index 6a34e39a0c..20edbd5aa8 100644
--- a/xpcom/build/PoisonIOInterposer.h
+++ b/xpcom/build/PoisonIOInterposer.h
@@ -35,7 +35,7 @@ void MozillaUnRegisterDebugFILE(FILE* aFile);
MOZ_END_EXTERN_C
-#if defined(XP_WIN)
+#if defined(XP_WIN) || defined(XP_MACOSX)
#ifdef __cplusplus
namespace mozilla {
@@ -53,6 +53,16 @@ bool IsDebugFile(intptr_t aFileID);
*/
void InitPoisonIOInterposer();
+#ifdef XP_MACOSX
+/**
+ * Check that writes are dirty before reporting I/O (Mac OS X only)
+ * This is necessary for late-write checks on Mac OS X, but reading the buffer
+ * from file to see if we're writing dirty bits is expensive, so we don't want
+ * to do this for everything else that uses
+ */
+void OnlyReportDirtyWrites();
+#endif /* XP_MACOSX */
+
/**
* Clear IO poisoning, this is only safe to do on the main-thread when no other
* threads are running.
@@ -62,16 +72,19 @@ void ClearPoisonIOInterposer();
} // namespace mozilla
#endif /* __cplusplus */
-#else /* XP_WIN */
+#else /* XP_WIN || XP_MACOSX */
#ifdef __cplusplus
namespace mozilla {
inline bool IsDebugFile(intptr_t aFileID) { return true; }
inline void InitPoisonIOInterposer() {}
inline void ClearPoisonIOInterposer() {}
+#ifdef XP_MACOSX
+inline void OnlyReportDirtyWrites() {}
+#endif /* XP_MACOSX */
} // namespace mozilla
#endif /* __cplusplus */
-#endif /* XP_WIN */
+#endif /* XP_WIN || XP_MACOSX */
#endif // mozilla_PoisonIOInterposer_h
diff --git a/xpcom/build/PoisonIOInterposerMac.cpp b/xpcom/build/PoisonIOInterposerMac.cpp
new file mode 100644
index 0000000000..d76b728ade
--- /dev/null
+++ b/xpcom/build/PoisonIOInterposerMac.cpp
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 8; 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 "PoisonIOInterposer.h"
+#include "mach_override.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsPrintfCString.h"
+#include "mozilla/StackWalk.h"
+#include "nsTraceRefcnt.h"
+#include "plstr.h"
+#include "prio.h"
+
+#include <algorithm>
+#include <vector>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <aio.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef MOZ_REPLACE_MALLOC
+#include "replace_malloc_bridge.h"
+#endif
+
+namespace {
+
+using namespace mozilla;
+
+// Bit tracking if poisoned writes are enabled
+static bool sIsEnabled = false;
+
+// Check if writes are dirty before reporting IO
+static bool sOnlyReportDirtyWrites = false;
+
+// Routines for write validation
+bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount);
+bool IsIPCWrite(int aFd, const struct stat& aBuf);
+
+/******************************** IO AutoTimer ********************************/
+
+/**
+ * RAII class for timing the duration of an I/O call and reporting the result
+ * to the IOInterposeObserver API.
+ */
+class MacIOAutoObservation : public IOInterposeObserver::Observation
+{
+public:
+ MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd)
+ : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled &&
+ !IsDebugFile(aFd))
+ , mFd(aFd)
+ , mHasQueriedFilename(false)
+ , mFilename(nullptr)
+ {
+ }
+
+ MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd,
+ const void* aBuf, size_t aCount)
+ : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled &&
+ !IsDebugFile(aFd) &&
+ IsValidWrite(aFd, aBuf, aCount))
+ , mFd(aFd)
+ , mHasQueriedFilename(false)
+ , mFilename(nullptr)
+ {
+ }
+
+ // Custom implementation of IOInterposeObserver::Observation::Filename
+ const char16_t* Filename() override;
+
+ ~MacIOAutoObservation()
+ {
+ Report();
+ if (mFilename) {
+ free(mFilename);
+ mFilename = nullptr;
+ }
+ }
+
+private:
+ int mFd;
+ bool mHasQueriedFilename;
+ char16_t* mFilename;
+ static const char* sReference;
+};
+
+const char* MacIOAutoObservation::sReference = "PoisonIOInterposer";
+
+// Get filename for this observation
+const char16_t*
+MacIOAutoObservation::Filename()
+{
+ // If mHasQueriedFilename is true, then we already have it
+ if (mHasQueriedFilename) {
+ return mFilename;
+ }
+ char filename[MAXPATHLEN];
+ if (fcntl(mFd, F_GETPATH, filename) != -1) {
+ mFilename = UTF8ToNewUnicode(nsDependentCString(filename));
+ } else {
+ mFilename = nullptr;
+ }
+ mHasQueriedFilename = true;
+
+ // Return filename
+ return mFilename;
+}
+
+/****************************** Write Validation ******************************/
+
+// We want to detect "actual" writes, not IPC. Some IPC mechanisms are
+// implemented with file descriptors, so filter them out.
+bool
+IsIPCWrite(int aFd, const struct stat& aBuf)
+{
+ if ((aBuf.st_mode & S_IFMT) == S_IFIFO) {
+ return true;
+ }
+
+ if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) {
+ return false;
+ }
+
+ sockaddr_storage address;
+ socklen_t len = sizeof(address);
+ if (getsockname(aFd, (sockaddr*)&address, &len) != 0) {
+ return true; // Ignore the aFd if we can't find what it is.
+ }
+
+ return address.ss_family == AF_UNIX;
+}
+
+// We want to report actual disk IO not things that don't move bits on the disk
+bool
+IsValidWrite(int aFd, const void* aWbuf, size_t aCount)
+{
+ // Ignore writes of zero bytes, Firefox does some during shutdown.
+ if (aCount == 0) {
+ return false;
+ }
+
+ {
+ struct stat buf;
+ int rv = fstat(aFd, &buf);
+ if (rv != 0) {
+ return true;
+ }
+
+ if (IsIPCWrite(aFd, buf)) {
+ return false;
+ }
+ }
+
+ // For writev we pass a nullptr aWbuf. We should only get here from
+ // dbm, and it uses write, so assert that we have aWbuf.
+ if (!aWbuf) {
+ return true;
+ }
+
+ // Break, here if we're allowed to report non-dirty writes
+ if (!sOnlyReportDirtyWrites) {
+ return true;
+ }
+
+ // As a really bad hack, accept writes that don't change the on disk
+ // content. This is needed because dbm doesn't keep track of dirty bits
+ // and can end up writing the same data to disk twice. Once when the
+ // user (nss) asks it to sync and once when closing the database.
+ auto wbuf2 = MakeUniqueFallible<char[]>(aCount);
+ if (!wbuf2) {
+ return true;
+ }
+ off_t pos = lseek(aFd, 0, SEEK_CUR);
+ if (pos == -1) {
+ return true;
+ }
+ ssize_t r = read(aFd, wbuf2.get(), aCount);
+ if (r < 0 || (size_t)r != aCount) {
+ return true;
+ }
+ int cmp = memcmp(aWbuf, wbuf2.get(), aCount);
+ if (cmp != 0) {
+ return true;
+ }
+ off_t pos2 = lseek(aFd, pos, SEEK_SET);
+ if (pos2 != pos) {
+ return true;
+ }
+
+ // Otherwise this is not a valid write
+ return false;
+}
+
+/*************************** Function Interception ***************************/
+
+/** Structure for declaration of function override */
+struct FuncData
+{
+ const char* Name; // Name of the function for the ones we use dlsym
+ const void* Wrapper; // The function that we will replace 'Function' with
+ void* Function; // The function that will be replaced with 'Wrapper'
+ void* Buffer; // Will point to the jump buffer that lets us call
+ // 'Function' after it has been replaced.
+};
+
+// Wrap aio_write. We have not seen it before, so just assert/report it.
+typedef ssize_t (*aio_write_t)(struct aiocb* aAioCbp);
+ssize_t wrap_aio_write(struct aiocb* aAioCbp);
+FuncData aio_write_data = { 0, (void*)wrap_aio_write, (void*)aio_write };
+ssize_t
+wrap_aio_write(struct aiocb* aAioCbp)
+{
+ MacIOAutoObservation timer(IOInterposeObserver::OpWrite,
+ aAioCbp->aio_fildes);
+
+ aio_write_t old_write = (aio_write_t)aio_write_data.Buffer;
+ return old_write(aAioCbp);
+}
+
+// Wrap pwrite-like functions.
+// We have not seen them before, so just assert/report it.
+typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes,
+ off_t aOffset);
+template<FuncData& foo>
+ssize_t
+wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes, off_t aOffset)
+{
+ MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aFd);
+ pwrite_t old_write = (pwrite_t)foo.Buffer;
+ return old_write(aFd, aBuf, aNumBytes, aOffset);
+}
+
+// Define a FuncData for a pwrite-like functions.
+#define DEFINE_PWRITE_DATA(X, NAME) \
+FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp<X ## _data> }; \
+
+// This exists everywhere.
+DEFINE_PWRITE_DATA(pwrite, "pwrite")
+// These exist on 32 bit OS X
+DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
+DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
+// This exists on 64 bit OS X
+DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");
+
+
+typedef ssize_t (*writev_t)(int aFd, const struct iovec* aIov, int aIovCount);
+template<FuncData& foo>
+ssize_t
+wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount)
+{
+ MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aFd, nullptr,
+ aIovCount);
+ writev_t old_write = (writev_t)foo.Buffer;
+ return old_write(aFd, aIov, aIovCount);
+}
+
+// Define a FuncData for a writev-like functions.
+#define DEFINE_WRITEV_DATA(X, NAME) \
+FuncData X ## _data = { NAME, (void*) wrap_writev_temp<X ## _data> }; \
+
+// This exists everywhere.
+DEFINE_WRITEV_DATA(writev, "writev");
+// These exist on 32 bit OS X
+DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
+DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
+// This exists on 64 bit OS X
+DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");
+
+typedef ssize_t (*write_t)(int aFd, const void* aBuf, size_t aCount);
+template<FuncData& foo>
+ssize_t
+wrap_write_temp(int aFd, const void* aBuf, size_t aCount)
+{
+ MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aFd, aBuf, aCount);
+ write_t old_write = (write_t)foo.Buffer;
+ return old_write(aFd, aBuf, aCount);
+}
+
+// Define a FuncData for a write-like functions.
+#define DEFINE_WRITE_DATA(X, NAME) \
+FuncData X ## _data = { NAME, (void*) wrap_write_temp<X ## _data> }; \
+
+// This exists everywhere.
+DEFINE_WRITE_DATA(write, "write");
+// These exist on 32 bit OS X
+DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003");
+DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003");
+// This exists on 64 bit OS X
+DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL");
+
+FuncData* Functions[] = {
+ &aio_write_data,
+
+ &pwrite_data,
+ &pwrite_NOCANCEL_UNIX2003_data,
+ &pwrite_UNIX2003_data,
+ &pwrite_NOCANCEL_data,
+
+ &write_data,
+ &write_NOCANCEL_UNIX2003_data,
+ &write_UNIX2003_data,
+ &write_NOCANCEL_data,
+
+ &writev_data,
+ &writev_NOCANCEL_UNIX2003_data,
+ &writev_UNIX2003_data,
+ &writev_NOCANCEL_data
+};
+
+const int NumFunctions = ArrayLength(Functions);
+
+} // namespace
+
+/******************************** IO Poisoning ********************************/
+
+namespace mozilla {
+
+void
+InitPoisonIOInterposer()
+{
+ // Enable reporting from poisoned write methods
+ sIsEnabled = true;
+
+ // Make sure we only poison writes once!
+ static bool WritesArePoisoned = false;
+ if (WritesArePoisoned) {
+ return;
+ }
+ WritesArePoisoned = true;
+
+ // stdout and stderr are OK.
+ MozillaRegisterDebugFD(1);
+ MozillaRegisterDebugFD(2);
+
+#ifdef MOZ_REPLACE_MALLOC
+ // The contract with InitDebugFd is that the given registry can be used
+ // at any moment, so the instance needs to persist longer than the scope
+ // of this functions.
+ static DebugFdRegistry registry;
+ ReplaceMalloc::InitDebugFd(registry);
+#endif
+
+ for (int i = 0; i < NumFunctions; ++i) {
+ FuncData* d = Functions[i];
+ if (!d->Function) {
+ d->Function = dlsym(RTLD_DEFAULT, d->Name);
+ }
+ if (!d->Function) {
+ continue;
+ }
+ DebugOnly<mach_error_t> t = mach_override_ptr(d->Function, d->Wrapper,
+ &d->Buffer);
+ MOZ_ASSERT(t == err_none);
+ }
+}
+
+void
+OnlyReportDirtyWrites()
+{
+ sOnlyReportDirtyWrites = true;
+}
+
+void
+ClearPoisonIOInterposer()
+{
+ // Not sure how or if we can unpoison the functions. Would be nice, but no
+ // worries we won't need to do this anyway.
+ sIsEnabled = false;
+}
+
+} // namespace mozilla
diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp
index 3291c06c07..185a823155 100644
--- a/xpcom/build/XPCOMInit.cpp
+++ b/xpcom/build/XPCOMInit.cpp
@@ -1007,6 +1007,9 @@ ShutdownXPCOM(nsIServiceManager* aServMgr)
PROFILER_MARKER("Shutdown xpcom");
// If we are doing any shutdown checks, poison writes.
if (gShutdownChecks != SCM_NOTHING) {
+#ifdef XP_MACOSX
+ mozilla::OnlyReportDirtyWrites();
+#endif /* XP_MACOSX */
mozilla::BeginLateWriteChecks();
}
diff --git a/xpcom/build/mach_override.c b/xpcom/build/mach_override.c
new file mode 100644
index 0000000000..9e4940d298
--- /dev/null
+++ b/xpcom/build/mach_override.c
@@ -0,0 +1,789 @@
+// Copied from upstream at revision 195c13743fe0ebc658714e2a9567d86529f20443.
+// mach_override.c semver:1.2.0
+// Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com
+// Some rights reserved: http://opensource.org/licenses/mit
+// https://github.com/rentzsch/mach_override
+
+#include "mach_override.h"
+
+#include <mach-o/dyld.h>
+#include <mach/mach_host.h>
+#include <mach/mach_init.h>
+#include <mach/vm_map.h>
+#include <sys/mman.h>
+
+#include <CoreServices/CoreServices.h>
+
+/**************************
+*
+* Constants
+*
+**************************/
+#pragma mark -
+#pragma mark (Constants)
+
+#define kPageSize 4096
+#if defined(__ppc__) || defined(__POWERPC__)
+
+long kIslandTemplate[] = {
+ 0x9001FFFC, // stw r0,-4(SP)
+ 0x3C00DEAD, // lis r0,0xDEAD
+ 0x6000BEEF, // ori r0,r0,0xBEEF
+ 0x7C0903A6, // mtctr r0
+ 0x8001FFFC, // lwz r0,-4(SP)
+ 0x60000000, // nop ; optionally replaced
+ 0x4E800420 // bctr
+};
+
+#define kAddressHi 3
+#define kAddressLo 5
+#define kInstructionHi 10
+#define kInstructionLo 11
+
+#elif defined(__i386__)
+
+#define kOriginalInstructionsSize 16
+// On X86 we migh need to instert an add with a 32 bit immediate after the
+// original instructions.
+#define kMaxFixupSizeIncrease 5
+
+unsigned char kIslandTemplate[] = {
+ // kOriginalInstructionsSize nop instructions so that we
+ // should have enough space to host original instructions
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ // Now the real jump instruction
+ 0xE9, 0xEF, 0xBE, 0xAD, 0xDE
+};
+
+#define kInstructions 0
+#define kJumpAddress kInstructions + kOriginalInstructionsSize + 1
+#elif defined(__x86_64__)
+
+#define kOriginalInstructionsSize 32
+// On X86-64 we never need to instert a new instruction.
+#define kMaxFixupSizeIncrease 0
+
+#define kJumpAddress kOriginalInstructionsSize + 6
+
+unsigned char kIslandTemplate[] = {
+ // kOriginalInstructionsSize nop instructions so that we
+ // should have enough space to host original instructions
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ // Now the real jump instruction
+ 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+};
+
+#endif
+
+/**************************
+*
+* Data Types
+*
+**************************/
+#pragma mark -
+#pragma mark (Data Types)
+
+typedef struct {
+ char instructions[sizeof(kIslandTemplate)];
+} BranchIsland;
+
+/**************************
+*
+* Funky Protos
+*
+**************************/
+#pragma mark -
+#pragma mark (Funky Protos)
+
+static mach_error_t
+allocateBranchIsland(
+ BranchIsland **island,
+ void *originalFunctionAddress);
+
+ mach_error_t
+freeBranchIsland(
+ BranchIsland *island );
+
+#if defined(__ppc__) || defined(__POWERPC__)
+ mach_error_t
+setBranchIslandTarget(
+ BranchIsland *island,
+ const void *branchTo,
+ long instruction );
+#endif
+
+#if defined(__i386__) || defined(__x86_64__)
+mach_error_t
+setBranchIslandTarget_i386(
+ BranchIsland *island,
+ const void *branchTo,
+ char* instructions );
+void
+atomic_mov64(
+ uint64_t *targetAddress,
+ uint64_t value );
+
+ static Boolean
+eatKnownInstructions(
+ unsigned char *code,
+ uint64_t *newInstruction,
+ int *howManyEaten,
+ char *originalInstructions,
+ int *originalInstructionCount,
+ uint8_t *originalInstructionSizes );
+
+ static void
+fixupInstructions(
+ uint32_t offset,
+ void *instructionsToFix,
+ int instructionCount,
+ uint8_t *instructionSizes );
+#endif
+
+/*******************************************************************************
+*
+* Interface
+*
+*******************************************************************************/
+#pragma mark -
+#pragma mark (Interface)
+
+#if defined(__i386__) || defined(__x86_64__)
+mach_error_t makeIslandExecutable(void *address) {
+ mach_error_t err = err_none;
+ uintptr_t page = (uintptr_t)address & ~(uintptr_t)(kPageSize-1);
+ int e = err_none;
+ e |= mprotect((void *)page, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE);
+ e |= msync((void *)page, kPageSize, MS_INVALIDATE );
+ if (e) {
+ err = err_cannot_override;
+ }
+ return err;
+}
+#endif
+
+ mach_error_t
+mach_override_ptr(
+ void *originalFunctionAddress,
+ const void *overrideFunctionAddress,
+ void **originalFunctionReentryIsland )
+{
+ assert( originalFunctionAddress );
+ assert( overrideFunctionAddress );
+
+ // this addresses overriding such functions as AudioOutputUnitStart()
+ // test with modified DefaultOutputUnit project
+#if defined(__x86_64__)
+ for(;;){
+ if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp qword near [rip+0x????????]
+ originalFunctionAddress=*(void**)((char*)originalFunctionAddress+6+*(int32_t *)((uint16_t*)originalFunctionAddress+1));
+ else break;
+ }
+#elif defined(__i386__)
+ for(;;){
+ if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp *0x????????
+ originalFunctionAddress=**(void***)((uint16_t*)originalFunctionAddress+1);
+ else break;
+ }
+#endif
+
+ long *originalFunctionPtr = (long*) originalFunctionAddress;
+ mach_error_t err = err_none;
+
+#if defined(__ppc__) || defined(__POWERPC__)
+ // Ensure first instruction isn't 'mfctr'.
+ #define kMFCTRMask 0xfc1fffff
+ #define kMFCTRInstruction 0x7c0903a6
+
+ long originalInstruction = *originalFunctionPtr;
+ if( !err && ((originalInstruction & kMFCTRMask) == kMFCTRInstruction) )
+ err = err_cannot_override;
+#elif defined(__i386__) || defined(__x86_64__)
+ int eatenCount = 0;
+ int originalInstructionCount = 0;
+ char originalInstructions[kOriginalInstructionsSize];
+ uint8_t originalInstructionSizes[kOriginalInstructionsSize];
+ uint64_t jumpRelativeInstruction = 0; // JMP
+
+ Boolean overridePossible = eatKnownInstructions ((unsigned char *)originalFunctionPtr,
+ &jumpRelativeInstruction, &eatenCount,
+ originalInstructions, &originalInstructionCount,
+ originalInstructionSizes );
+ if (eatenCount + kMaxFixupSizeIncrease > kOriginalInstructionsSize) {
+ //printf ("Too many instructions eaten\n");
+ overridePossible = false;
+ }
+ if (!overridePossible) err = err_cannot_override;
+ if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
+#endif
+
+ // Make the original function implementation writable.
+ if( !err ) {
+ err = vm_protect( mach_task_self(),
+ (vm_address_t) originalFunctionPtr, 8, false,
+ (VM_PROT_ALL | VM_PROT_COPY) );
+ if( err )
+ err = vm_protect( mach_task_self(),
+ (vm_address_t) originalFunctionPtr, 8, false,
+ (VM_PROT_DEFAULT | VM_PROT_COPY) );
+ }
+ if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
+
+ // Allocate and target the escape island to the overriding function.
+ BranchIsland *escapeIsland = NULL;
+ if( !err )
+ err = allocateBranchIsland( &escapeIsland, originalFunctionAddress );
+ if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
+
+
+#if defined(__ppc__) || defined(__POWERPC__)
+ if( !err )
+ err = setBranchIslandTarget( escapeIsland, overrideFunctionAddress, 0 );
+
+ // Build the branch absolute instruction to the escape island.
+ long branchAbsoluteInstruction = 0; // Set to 0 just to silence warning.
+ if( !err ) {
+ long escapeIslandAddress = ((long) escapeIsland) & 0x3FFFFFF;
+ branchAbsoluteInstruction = 0x48000002 | escapeIslandAddress;
+ }
+#elif defined(__i386__) || defined(__x86_64__)
+ if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
+
+ if( !err )
+ err = setBranchIslandTarget_i386( escapeIsland, overrideFunctionAddress, 0 );
+
+ if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__);
+ // Build the jump relative instruction to the escape island
+#endif
+
+
+#if defined(__i386__) || defined(__x86_64__)
+ if (!err) {
+ uint32_t addressOffset = ((char*)escapeIsland - (char*)originalFunctionPtr - 5);
+ addressOffset = OSSwapInt32(addressOffset);
+
+ jumpRelativeInstruction |= 0xE900000000000000LL;
+ jumpRelativeInstruction |= ((uint64_t)addressOffset & 0xffffffff) << 24;
+ jumpRelativeInstruction = OSSwapInt64(jumpRelativeInstruction);
+ }
+#endif
+
+ // Optionally allocate & return the reentry island. This may contain relocated
+ // jmp instructions and so has all the same addressing reachability requirements
+ // the escape island has to the original function, except the escape island is
+ // technically our original function.
+ BranchIsland *reentryIsland = NULL;
+ if( !err && originalFunctionReentryIsland ) {
+ err = allocateBranchIsland( &reentryIsland, escapeIsland);
+ if( !err )
+ *originalFunctionReentryIsland = reentryIsland;
+ }
+
+#if defined(__ppc__) || defined(__POWERPC__)
+ // Atomically:
+ // o If the reentry island was allocated:
+ // o Insert the original instruction into the reentry island.
+ // o Target the reentry island at the 2nd instruction of the
+ // original function.
+ // o Replace the original instruction with the branch absolute.
+ if( !err ) {
+ int escapeIslandEngaged = false;
+ do {
+ if( reentryIsland )
+ err = setBranchIslandTarget( reentryIsland,
+ (void*) (originalFunctionPtr+1), originalInstruction );
+ if( !err ) {
+ escapeIslandEngaged = CompareAndSwap( originalInstruction,
+ branchAbsoluteInstruction,
+ (UInt32*)originalFunctionPtr );
+ if( !escapeIslandEngaged ) {
+ // Someone replaced the instruction out from under us,
+ // re-read the instruction, make sure it's still not
+ // 'mfctr' and try again.
+ originalInstruction = *originalFunctionPtr;
+ if( (originalInstruction & kMFCTRMask) == kMFCTRInstruction)
+ err = err_cannot_override;
+ }
+ }
+ } while( !err && !escapeIslandEngaged );
+ }
+#elif defined(__i386__) || defined(__x86_64__)
+ // Atomically:
+ // o If the reentry island was allocated:
+ // o Insert the original instructions into the reentry island.
+ // o Target the reentry island at the first non-replaced
+ // instruction of the original function.
+ // o Replace the original first instructions with the jump relative.
+ //
+ // Note that on i386, we do not support someone else changing the code under our feet
+ if ( !err ) {
+ uint32_t offset = (uintptr_t)originalFunctionPtr - (uintptr_t)reentryIsland;
+ fixupInstructions(offset, originalInstructions,
+ originalInstructionCount, originalInstructionSizes );
+
+ if( reentryIsland )
+ err = setBranchIslandTarget_i386( reentryIsland,
+ (void*) ((char *)originalFunctionPtr+eatenCount), originalInstructions );
+ // try making islands executable before planting the jmp
+#if defined(__x86_64__) || defined(__i386__)
+ if( !err )
+ err = makeIslandExecutable(escapeIsland);
+ if( !err && reentryIsland )
+ err = makeIslandExecutable(reentryIsland);
+#endif
+ if ( !err )
+ atomic_mov64((uint64_t *)originalFunctionPtr, jumpRelativeInstruction);
+ }
+#endif
+
+ // Clean up on error.
+ if( err ) {
+ if( reentryIsland )
+ freeBranchIsland( reentryIsland );
+ if( escapeIsland )
+ freeBranchIsland( escapeIsland );
+ }
+
+ return err;
+}
+
+/*******************************************************************************
+*
+* Implementation
+*
+*******************************************************************************/
+#pragma mark -
+#pragma mark (Implementation)
+
+static bool jump_in_range(intptr_t from, intptr_t to) {
+ intptr_t field_value = to - from - 5;
+ int32_t field_value_32 = field_value;
+ return field_value == field_value_32;
+}
+
+/*******************************************************************************
+ Implementation: Allocates memory for a branch island.
+
+ @param island <- The allocated island.
+ @result <- mach_error_t
+
+ ***************************************************************************/
+
+static mach_error_t
+allocateBranchIslandAux(
+ BranchIsland **island,
+ void *originalFunctionAddress,
+ bool forward)
+{
+ assert( island );
+ assert( sizeof( BranchIsland ) <= kPageSize );
+
+ vm_map_t task_self = mach_task_self();
+ vm_address_t original_address = (vm_address_t) originalFunctionAddress;
+ vm_address_t address = original_address;
+
+ for (;;) {
+ vm_size_t vmsize = 0;
+ memory_object_name_t object = 0;
+ kern_return_t kr = 0;
+ vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
+ // Find the region the address is in.
+#if __WORDSIZE == 32
+ vm_region_basic_info_data_t info;
+ mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
+ kr = vm_region(task_self, &address, &vmsize, flavor,
+ (vm_region_info_t)&info, &info_count, &object);
+#else
+ vm_region_basic_info_data_64_t info;
+ mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
+ kr = vm_region_64(task_self, &address, &vmsize, flavor,
+ (vm_region_info_t)&info, &info_count, &object);
+#endif
+ if (kr != KERN_SUCCESS)
+ return kr;
+ assert((address & (kPageSize - 1)) == 0);
+
+ // Go to the first page before or after this region
+ vm_address_t new_address = forward ? address + vmsize : address - kPageSize;
+#if __WORDSIZE == 64
+ if(!jump_in_range(original_address, new_address))
+ break;
+#endif
+ address = new_address;
+
+ // Try to allocate this page.
+ kr = vm_allocate(task_self, &address, kPageSize, 0);
+ if (kr == KERN_SUCCESS) {
+ *island = (BranchIsland*) address;
+ return err_none;
+ }
+ if (kr != KERN_NO_SPACE)
+ return kr;
+ }
+
+ return KERN_NO_SPACE;
+}
+
+static mach_error_t
+allocateBranchIsland(
+ BranchIsland **island,
+ void *originalFunctionAddress)
+{
+ mach_error_t err =
+ allocateBranchIslandAux(island, originalFunctionAddress, true);
+ if (!err)
+ return err;
+ return allocateBranchIslandAux(island, originalFunctionAddress, false);
+}
+
+
+/*******************************************************************************
+ Implementation: Deallocates memory for a branch island.
+
+ @param island -> The island to deallocate.
+ @result <- mach_error_t
+
+ ***************************************************************************/
+
+ mach_error_t
+freeBranchIsland(
+ BranchIsland *island )
+{
+ assert( island );
+ assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] );
+ assert( sizeof( BranchIsland ) <= kPageSize );
+ return vm_deallocate( mach_task_self(), (vm_address_t) island,
+ kPageSize );
+}
+
+/*******************************************************************************
+ Implementation: Sets the branch island's target, with an optional
+ instruction.
+
+ @param island -> The branch island to insert target into.
+ @param branchTo -> The address of the target.
+ @param instruction -> Optional instruction to execute prior to branch. Set
+ to zero for nop.
+ @result <- mach_error_t
+
+ ***************************************************************************/
+#if defined(__ppc__) || defined(__POWERPC__)
+ mach_error_t
+setBranchIslandTarget(
+ BranchIsland *island,
+ const void *branchTo,
+ long instruction )
+{
+ // Copy over the template code.
+ bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) );
+
+ // Fill in the address.
+ ((short*)island->instructions)[kAddressLo] = ((long) branchTo) & 0x0000FFFF;
+ ((short*)island->instructions)[kAddressHi]
+ = (((long) branchTo) >> 16) & 0x0000FFFF;
+
+ // Fill in the (optional) instuction.
+ if( instruction != 0 ) {
+ ((short*)island->instructions)[kInstructionLo]
+ = instruction & 0x0000FFFF;
+ ((short*)island->instructions)[kInstructionHi]
+ = (instruction >> 16) & 0x0000FFFF;
+ }
+
+ //MakeDataExecutable( island->instructions, sizeof( kIslandTemplate ) );
+ msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE );
+
+ return err_none;
+}
+#endif
+
+#if defined(__i386__)
+ mach_error_t
+setBranchIslandTarget_i386(
+ BranchIsland *island,
+ const void *branchTo,
+ char* instructions )
+{
+
+ // Copy over the template code.
+ bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) );
+
+ // copy original instructions
+ if (instructions) {
+ bcopy (instructions, island->instructions + kInstructions, kOriginalInstructionsSize);
+ }
+
+ // Fill in the address.
+ int32_t addressOffset = (char *)branchTo - (island->instructions + kJumpAddress + 4);
+ *((int32_t *)(island->instructions + kJumpAddress)) = addressOffset;
+
+ msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE );
+ return err_none;
+}
+
+#elif defined(__x86_64__)
+mach_error_t
+setBranchIslandTarget_i386(
+ BranchIsland *island,
+ const void *branchTo,
+ char* instructions )
+{
+ // Copy over the template code.
+ bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) );
+
+ // Copy original instructions.
+ if (instructions) {
+ bcopy (instructions, island->instructions, kOriginalInstructionsSize);
+ }
+
+ // Fill in the address.
+ *((uint64_t *)(island->instructions + kJumpAddress)) = (uint64_t)branchTo;
+ msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE );
+
+ return err_none;
+}
+#endif
+
+
+#if defined(__i386__) || defined(__x86_64__)
+// simplistic instruction matching
+typedef struct {
+ unsigned int length; // max 15
+ unsigned char mask[15]; // sequence of bytes in memory order
+ unsigned char constraint[15]; // sequence of bytes in memory order
+} AsmInstructionMatch;
+
+#if defined(__i386__)
+static AsmInstructionMatch possibleInstructions[] = {
+ { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x????????
+ { 0x5, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x55, 0x89, 0xe5, 0xc9, 0xc3} }, // push %ebp; mov %esp,%ebp; leave; ret
+ { 0x1, {0xFF}, {0x90} }, // nop
+ { 0x1, {0xFF}, {0x55} }, // push %esp
+ { 0x2, {0xFF, 0xFF}, {0x89, 0xE5} }, // mov %esp,%ebp
+ { 0x1, {0xFF}, {0x53} }, // push %ebx
+ { 0x3, {0xFF, 0xFF, 0x00}, {0x83, 0xEC, 0x00} }, // sub 0x??, %esp
+ { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x81, 0xEC, 0x00, 0x00, 0x00, 0x00} }, // sub 0x??, %esp with 32bit immediate
+ { 0x1, {0xFF}, {0x57} }, // push %edi
+ { 0x1, {0xFF}, {0x56} }, // push %esi
+ { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax
+ { 0x3, {0xFF, 0x4F, 0x00}, {0x8B, 0x45, 0x00} }, // mov $imm(%ebp), %reg
+ { 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg
+ { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx
+ { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax
+ { 0x6, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0xE8, 0x00, 0x00, 0x00, 0x00, 0x58} }, // call $imm; pop %eax
+ { 0x0 }
+};
+#elif defined(__x86_64__)
+static AsmInstructionMatch possibleInstructions[] = {
+ { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x????????
+ { 0x1, {0xFF}, {0x90} }, // nop
+ { 0x1, {0xF8}, {0x50} }, // push %rX
+ { 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x89, 0xE5} }, // mov %rsp,%rbp
+ { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xEC, 0x00} }, // sub 0x??, %rsp
+ { 0x4, {0xFB, 0xFF, 0x00, 0x00}, {0x48, 0x89, 0x00, 0x00} }, // move onto rbp
+ { 0x4, {0xFF, 0xFF, 0xFF, 0xFF}, {0x40, 0x0f, 0xbe, 0xce} }, // movsbl %sil, %ecx
+ { 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX
+ { 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX
+ { 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg
+ { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi)
+ { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax
+ { 0x2, {0xFF, 0xFF}, {0x89, 0xF8} }, // mov %edi, %eax
+
+ //leaq offset(%rip),%rax
+ { 0x7, {0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00} },
+
+ { 0x0 }
+};
+#endif
+
+static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* instruction)
+{
+ Boolean match = true;
+
+ size_t i;
+ for (i=0; i<instruction->length; i++) {
+ unsigned char mask = instruction->mask[i];
+ unsigned char constraint = instruction->constraint[i];
+ unsigned char codeValue = code[i];
+
+ match = ((codeValue & mask) == constraint);
+ if (!match) break;
+ }
+
+ return match;
+}
+
+#if defined(__i386__) || defined(__x86_64__)
+ static Boolean
+eatKnownInstructions(
+ unsigned char *code,
+ uint64_t *newInstruction,
+ int *howManyEaten,
+ char *originalInstructions,
+ int *originalInstructionCount,
+ uint8_t *originalInstructionSizes )
+{
+ Boolean allInstructionsKnown = true;
+ int totalEaten = 0;
+ unsigned char* ptr = code;
+ int remainsToEat = 5; // a JMP instruction takes 5 bytes
+ int instructionIndex = 0;
+
+ if (howManyEaten) *howManyEaten = 0;
+ if (originalInstructionCount) *originalInstructionCount = 0;
+ while (remainsToEat > 0) {
+ Boolean curInstructionKnown = false;
+
+ // See if instruction matches one we know
+ AsmInstructionMatch* curInstr = possibleInstructions;
+ do {
+ if ((curInstructionKnown = codeMatchesInstruction(ptr, curInstr))) break;
+ curInstr++;
+ } while (curInstr->length > 0);
+
+ // if all instruction matches failed, we don't know current instruction then, stop here
+ if (!curInstructionKnown) {
+ allInstructionsKnown = false;
+ fprintf(stderr, "mach_override: some instructions unknown! Need to update mach_override.c\n");
+ break;
+ }
+
+ // At this point, we've matched curInstr
+ int eaten = curInstr->length;
+ ptr += eaten;
+ remainsToEat -= eaten;
+ totalEaten += eaten;
+
+ if (originalInstructionSizes) originalInstructionSizes[instructionIndex] = eaten;
+ instructionIndex += 1;
+ if (originalInstructionCount) *originalInstructionCount = instructionIndex;
+ }
+
+
+ if (howManyEaten) *howManyEaten = totalEaten;
+
+ if (originalInstructions) {
+ Boolean enoughSpaceForOriginalInstructions = (totalEaten < kOriginalInstructionsSize);
+
+ if (enoughSpaceForOriginalInstructions) {
+ memset(originalInstructions, 0x90 /* NOP */, kOriginalInstructionsSize); // fill instructions with NOP
+ bcopy(code, originalInstructions, totalEaten);
+ } else {
+ // printf ("Not enough space in island to store original instructions. Adapt the island definition and kOriginalInstructionsSize\n");
+ return false;
+ }
+ }
+
+ if (allInstructionsKnown) {
+ // save last 3 bytes of first 64bits of codre we'll replace
+ uint64_t currentFirst64BitsOfCode = *((uint64_t *)code);
+ currentFirst64BitsOfCode = OSSwapInt64(currentFirst64BitsOfCode); // back to memory representation
+ currentFirst64BitsOfCode &= 0x0000000000FFFFFFLL;
+
+ // keep only last 3 instructions bytes, first 5 will be replaced by JMP instr
+ *newInstruction &= 0xFFFFFFFFFF000000LL; // clear last 3 bytes
+ *newInstruction |= (currentFirst64BitsOfCode & 0x0000000000FFFFFFLL); // set last 3 bytes
+ }
+
+ return allInstructionsKnown;
+}
+
+ static void
+fixupInstructions(
+ uint32_t offset,
+ void *instructionsToFix,
+ int instructionCount,
+ uint8_t *instructionSizes )
+{
+ // The start of "leaq offset(%rip),%rax"
+ static const uint8_t LeaqHeader[] = {0x48, 0x8d, 0x05};
+
+ int index;
+ for (index = 0;index < instructionCount;index += 1)
+ {
+ if (*(uint8_t*)instructionsToFix == 0xE9) // 32-bit jump relative
+ {
+ uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1);
+ *jumpOffsetPtr += offset;
+ }
+
+ // leaq offset(%rip),%rax
+ if (memcmp(instructionsToFix, LeaqHeader, 3) == 0) {
+ uint32_t *LeaqOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 3);
+ *LeaqOffsetPtr += offset;
+ }
+
+ // 32-bit call relative to the next addr; pop %eax
+ if (*(uint8_t*)instructionsToFix == 0xE8)
+ {
+ // Just this call is larger than the jump we use, so we
+ // know this is the last instruction.
+ assert(index == (instructionCount - 1));
+ assert(instructionSizes[index] == 6);
+
+ // Insert "addl $offset, %eax" in the end so that when
+ // we jump to the rest of the function %eax has the
+ // value it would have if eip had been pushed by the
+ // call in its original position.
+ uint8_t *op = instructionsToFix;
+ op += 6;
+ *op = 0x05; // addl
+ uint32_t *addImmPtr = (uint32_t*)(op + 1);
+ *addImmPtr = offset;
+ }
+
+ instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]);
+ }
+}
+#endif
+
+#if defined(__i386__)
+__asm(
+ ".text;"
+ ".align 2, 0x90;"
+ "_atomic_mov64:;"
+ " pushl %ebp;"
+ " movl %esp, %ebp;"
+ " pushl %esi;"
+ " pushl %ebx;"
+ " pushl %ecx;"
+ " pushl %eax;"
+ " pushl %edx;"
+
+ // atomic push of value to an address
+ // we use cmpxchg8b, which compares content of an address with
+ // edx:eax. If they are equal, it atomically puts 64bit value
+ // ecx:ebx in address.
+ // We thus put contents of address in edx:eax to force ecx:ebx
+ // in address
+ " mov 8(%ebp), %esi;" // esi contains target address
+ " mov 12(%ebp), %ebx;"
+ " mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address
+ " mov (%esi), %eax;"
+ " mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address
+ " lock; cmpxchg8b (%esi);" // atomic move.
+
+ // restore registers
+ " popl %edx;"
+ " popl %eax;"
+ " popl %ecx;"
+ " popl %ebx;"
+ " popl %esi;"
+ " popl %ebp;"
+ " ret"
+);
+#elif defined(__x86_64__)
+void atomic_mov64(
+ uint64_t *targetAddress,
+ uint64_t value )
+{
+ *targetAddress = value;
+}
+#endif
+#endif
diff --git a/xpcom/build/mach_override.h b/xpcom/build/mach_override.h
new file mode 100644
index 0000000000..d9be988a34
--- /dev/null
+++ b/xpcom/build/mach_override.h
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ mach_override.h
+ Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: <http://rentzsch.com>
+ Some rights reserved: <http://opensource.org/licenses/mit-license.php>
+
+ ***************************************************************************/
+
+/***************************************************************************//**
+ @mainpage mach_override
+ @author Jonathan 'Wolf' Rentzsch: <http://rentzsch.com>
+
+ This package, coded in C to the Mach API, allows you to override ("patch")
+ program- and system-supplied functions at runtime. You can fully replace
+ functions with your implementations, or merely head- or tail-patch the
+ original implementations.
+
+ Use it by #include'ing mach_override.h from your .c, .m or .mm file(s).
+
+ @todo Discontinue use of Carbon's MakeDataExecutable() and
+ CompareAndSwap() calls and start using the Mach equivalents, if they
+ exist. If they don't, write them and roll them in. That way, this
+ code will be pure Mach, which will make it easier to use everywhere.
+ Update: MakeDataExecutable() has been replaced by
+ msync(MS_INVALIDATE). There is an OSCompareAndSwap in libkern, but
+ I'm currently unsure if I can link against it. May have to roll in
+ my own version...
+ @todo Stop using an entire 4K high-allocated VM page per 28-byte escape
+ branch island. Done right, this will dramatically speed up escape
+ island allocations when they number over 250. Then again, if you're
+ overriding more than 250 functions, maybe speed isn't your main
+ concern...
+ @todo Add detection of: b, bl, bla, bc, bcl, bcla, bcctrl, bclrl
+ first-instructions. Initially, we should refuse to override
+ functions beginning with these instructions. Eventually, we should
+ dynamically rewrite them to make them position-independent.
+ @todo Write mach_unoverride(), which would remove an override placed on a
+ function. Must be multiple-override aware, which means an almost
+ complete rewrite under the covers, because the target address can't
+ be spread across two load instructions like it is now since it will
+ need to be atomically updatable.
+ @todo Add non-rentry variants of overrides to test_mach_override.
+
+ ***************************************************************************/
+
+#ifndef _mach_override_
+#define _mach_override_
+
+#include <sys/types.h>
+#include <mach/error.h>
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/**
+ Returned if the function to be overrided begins with a 'mfctr' instruction.
+*/
+#define err_cannot_override (err_local|1)
+
+/************************************************************************************//**
+ Dynamically overrides the function implementation referenced by
+ originalFunctionAddress with the implentation pointed to by overrideFunctionAddress.
+ Optionally returns a pointer to a "reentry island" which, if jumped to, will resume
+ the original implementation.
+
+ @param originalFunctionAddress -> Required address of the function to
+ override (with overrideFunctionAddress).
+ @param overrideFunctionAddress -> Required address to the overriding
+ function.
+ @param originalFunctionReentryIsland <- Optional pointer to pointer to the
+ reentry island. Can be nullptr.
+ @result <- err_cannot_override if the original
+ function's implementation begins with
+ the 'mfctr' instruction.
+
+ ************************************************************************************/
+
+ mach_error_t
+mach_override_ptr(
+ void *originalFunctionAddress,
+ const void *overrideFunctionAddress,
+ void **originalFunctionReentryIsland );
+
+/************************************************************************************//**
+
+
+ ************************************************************************************/
+
+#ifdef __cplusplus
+
+#define MACH_OVERRIDE( ORIGINAL_FUNCTION_RETURN_TYPE, ORIGINAL_FUNCTION_NAME, ORIGINAL_FUNCTION_ARGS, ERR ) \
+ { \
+ static ORIGINAL_FUNCTION_RETURN_TYPE (*ORIGINAL_FUNCTION_NAME##_reenter)ORIGINAL_FUNCTION_ARGS; \
+ static bool ORIGINAL_FUNCTION_NAME##_overriden = false; \
+ class mach_override_class__##ORIGINAL_FUNCTION_NAME { \
+ public: \
+ static kern_return_t override(void *originalFunctionPtr) { \
+ kern_return_t result = err_none; \
+ if (!ORIGINAL_FUNCTION_NAME##_overriden) { \
+ ORIGINAL_FUNCTION_NAME##_overriden = true; \
+ result = mach_override_ptr( (void*)originalFunctionPtr, \
+ (void*)mach_override_class__##ORIGINAL_FUNCTION_NAME::replacement, \
+ (void**)&ORIGINAL_FUNCTION_NAME##_reenter ); \
+ } \
+ return result; \
+ } \
+ static ORIGINAL_FUNCTION_RETURN_TYPE replacement ORIGINAL_FUNCTION_ARGS {
+
+#define END_MACH_OVERRIDE( ORIGINAL_FUNCTION_NAME ) \
+ } \
+ }; \
+ \
+ err = mach_override_class__##ORIGINAL_FUNCTION_NAME::override((void*)ORIGINAL_FUNCTION_NAME); \
+ }
+
+#endif
+
+#ifdef __cplusplus
+ }
+#endif
+#endif // _mach_override_
diff --git a/xpcom/build/moz.build b/xpcom/build/moz.build
index 6226067897..c58b4db3f5 100644
--- a/xpcom/build/moz.build
+++ b/xpcom/build/moz.build
@@ -32,6 +32,13 @@ if CONFIG['OS_ARCH'] == 'WINNT':
'PoisonIOInterposerBase.cpp',
'PoisonIOInterposerWin.cpp',
]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ UNIFIED_SOURCES += [
+ 'PoisonIOInterposerBase.cpp',
+ 'PoisonIOInterposerMac.cpp',
+ ]
+ SOURCES += ['mach_override.c']
+ SOURCES['mach_override.c'].flags += ['-Wno-unused-function']
else:
SOURCES += ['PoisonIOInterposerStub.cpp']
@@ -88,3 +95,6 @@ if CONFIG['MOZ_VPX']:
LOCAL_INCLUDES += [
'/media/libvpx',
]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/xpcom/build/nsXPCOMPrivate.h b/xpcom/build/nsXPCOMPrivate.h
index 6c3a2bc0ab..c5f6553c03 100644
--- a/xpcom/build/nsXPCOMPrivate.h
+++ b/xpcom/build/nsXPCOMPrivate.h
@@ -254,8 +254,15 @@ void LogTerm();
#define XPCOM_DLL XUL_DLL
+// you have to love apple..
+#ifdef XP_MACOSX
+#define XPCOM_SEARCH_KEY "DYLD_LIBRARY_PATH"
+#define GRE_FRAMEWORK_NAME "XUL.framework"
+#define XUL_DLL "XUL"
+#else
#define XPCOM_SEARCH_KEY "LD_LIBRARY_PATH"
#define XUL_DLL "libxul" MOZ_DLL_SUFFIX
+#endif
#define GRE_CONF_NAME ".gre.config"
#define GRE_CONF_PATH "/etc/gre.conf"
diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h
index 18ccca4158..56d2496b26 100644
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -106,7 +106,7 @@
*/
#define XRE_SYS_SHARE_EXTENSION_PARENT_DIR "XRESysSExtPD"
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) || defined(XP_MACOSX)
/**
* Directory service keys for the system-wide and user-specific
* directories where host manifests used by the WebExtensions
diff --git a/xpcom/components/nsNativeModuleLoader.cpp b/xpcom/components/nsNativeModuleLoader.cpp
index 95b8349f19..bec3a11757 100644
--- a/xpcom/components/nsNativeModuleLoader.cpp
+++ b/xpcom/components/nsNativeModuleLoader.cpp
@@ -33,6 +33,10 @@
#include <windows.h>
#endif
+#ifdef XP_MACOSX
+#include <signal.h>
+#endif
+
#ifdef DEBUG
#define IMPLEMENT_BREAK_AFTER_LOAD
#endif
diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h
index 57228d6634..a3058faaaf 100644
--- a/xpcom/ds/nsMathUtils.h
+++ b/xpcom/ds/nsMathUtils.h
@@ -103,6 +103,10 @@ NS_finite(double aNum)
#ifdef WIN32
// NOTE: '!!' casts an int to bool without spamming MSVC warning C4800.
return !!_finite(aNum);
+#elif defined(XP_DARWIN)
+ // Darwin has deprecated |finite| and recommends |isfinite|. The former is
+ // not present in the iOS SDK.
+ return std::isfinite(aNum);
#else
return finite(aNum);
#endif
diff --git a/xpcom/glue/FileUtils.cpp b/xpcom/glue/FileUtils.cpp
index 04bba85cb4..8057cf8cb1 100644
--- a/xpcom/glue/FileUtils.cpp
+++ b/xpcom/glue/FileUtils.cpp
@@ -12,7 +12,16 @@
#include "mozilla/Assertions.h"
#include "mozilla/FileUtils.h"
-#if defined(XP_UNIX)
+#if defined(XP_MACOSX)
+#include <fcntl.h>
+#include <unistd.h>
+#include <mach/machine.h>
+#include <mach-o/fat.h>
+#include <mach-o/loader.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <limits.h>
+#elif defined(XP_UNIX)
#include <fcntl.h>
#include <unistd.h>
#if defined(LINUX)
@@ -47,6 +56,20 @@ mozilla::fallocate(PRFileDesc* aFD, int64_t aLength)
PR_Seek64(aFD, oldpos, PR_SEEK_SET);
return retval;
+#elif defined(XP_MACOSX)
+ int fd = PR_FileDesc2NativeHandle(aFD);
+ fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, aLength};
+ // Try to get a continous chunk of disk space
+ int ret = fcntl(fd, F_PREALLOCATE, &store);
+ if (ret == -1) {
+ // OK, perhaps we are too fragmented, allocate non-continuous
+ store.fst_flags = F_ALLOCATEALL;
+ ret = fcntl(fd, F_PREALLOCATE, &store);
+ if (ret == -1) {
+ return false;
+ }
+ }
+ return ftruncate(fd, aLength) == 0;
#elif defined(XP_UNIX)
// The following is copied from fcntlSizeHint in sqlite
/* If the OS does not have posix_fallocate(), fake it. First use
@@ -202,7 +225,7 @@ mozilla::ReadAheadLib(nsIFile* aFile)
return;
}
ReadAheadLib(path.get());
-#elif defined(LINUX)
+#elif defined(LINUX) || defined(XP_MACOSX)
nsAutoCString nativePath;
if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) {
return;
@@ -221,7 +244,7 @@ mozilla::ReadAheadFile(nsIFile* aFile, const size_t aOffset,
return;
}
ReadAheadFile(path.get(), aOffset, aCount, aOutFd);
-#elif defined(LINUX)
+#elif defined(LINUX) || defined(XP_MACOSX)
nsAutoCString nativePath;
if (!aFile || NS_FAILED(aFile->GetNativePath(nativePath))) {
return;
@@ -248,6 +271,64 @@ static const unsigned char ELFCLASS = ELFCLASS32;
typedef Elf32_Off Elf_Off;
#endif
+#elif defined(XP_MACOSX)
+
+#if defined(__i386__)
+static const uint32_t CPU_TYPE = CPU_TYPE_X86;
+#elif defined(__x86_64__)
+static const uint32_t CPU_TYPE = CPU_TYPE_X86_64;
+#elif defined(__ppc__)
+static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC;
+#elif defined(__ppc64__)
+static const uint32_t CPU_TYPE = CPU_TYPE_POWERPC64;
+#else
+#error Unsupported CPU type
+#endif
+
+#ifdef __LP64__
+#undef LC_SEGMENT
+#define LC_SEGMENT LC_SEGMENT_64
+#undef MH_MAGIC
+#define MH_MAGIC MH_MAGIC_64
+#define cpu_mach_header mach_header_64
+#define segment_command segment_command_64
+#else
+#define cpu_mach_header mach_header
+#endif
+
+class ScopedMMap
+{
+public:
+ explicit ScopedMMap(const char* aFilePath)
+ : buf(nullptr)
+ {
+ fd = open(aFilePath, O_RDONLY);
+ if (fd < 0) {
+ return;
+ }
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ return;
+ }
+ size = st.st_size;
+ buf = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ }
+ ~ScopedMMap()
+ {
+ if (buf) {
+ munmap(buf, size);
+ }
+ if (fd >= 0) {
+ close(fd);
+ }
+ }
+ operator char*() { return buf; }
+ int getFd() { return fd; }
+private:
+ int fd;
+ char* buf;
+ size_t size;
+};
#endif
void
@@ -304,6 +385,14 @@ mozilla::ReadAhead(mozilla::filedesc_t aFd, const size_t aOffset,
readahead(aFd, aOffset, aCount);
+#elif defined(XP_MACOSX)
+
+ struct radvisory ra;
+ ra.ra_offset = aOffset;
+ ra.ra_count = aCount;
+ // The F_RDADVISE fcntl is equivalent to Linux' readahead() system call.
+ fcntl(aFd, F_RDADVISE, &ra);
+
#endif
}
@@ -360,6 +449,62 @@ mozilla::ReadAheadLib(mozilla::pathstr_t aFilePath)
ReadAhead(fd, 0, end);
}
close(fd);
+#elif defined(XP_MACOSX)
+ ScopedMMap buf(aFilePath);
+ char* base = buf;
+ if (!base) {
+ return;
+ }
+
+ // An OSX binary might either be a fat (universal) binary or a
+ // Mach-O binary. A fat binary actually embeds several Mach-O
+ // binaries. If we have a fat binary, find the offset where the
+ // Mach-O binary for our CPU type can be found.
+ struct fat_header* fh = (struct fat_header*)base;
+
+ if (OSSwapBigToHostInt32(fh->magic) == FAT_MAGIC) {
+ uint32_t nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
+ struct fat_arch* arch = (struct fat_arch*)&buf[sizeof(struct fat_header)];
+ for (; nfat_arch; arch++, nfat_arch--) {
+ if (OSSwapBigToHostInt32(arch->cputype) == CPU_TYPE) {
+ base += OSSwapBigToHostInt32(arch->offset);
+ break;
+ }
+ }
+ if (base == buf) {
+ return;
+ }
+ }
+
+ // Check Mach-O magic in the Mach header
+ struct cpu_mach_header* mh = (struct cpu_mach_header*)base;
+ if (mh->magic != MH_MAGIC) {
+ return;
+ }
+
+ // The Mach header is followed by a sequence of load commands.
+ // Each command has a header containing the command type and the
+ // command size. LD_SEGMENT commands describes how the dynamic
+ // loader is going to map the file in memory. We use that
+ // information to find the biggest offset from the library that
+ // will be mapped in memory.
+ char* cmd = &base[sizeof(struct cpu_mach_header)];
+ uint32_t end = 0;
+ for (uint32_t ncmds = mh->ncmds; ncmds; ncmds--) {
+ struct segment_command* sh = (struct segment_command*)cmd;
+ if (sh->cmd != LC_SEGMENT) {
+ continue;
+ }
+ if (end < sh->fileoff + sh->filesize) {
+ end = sh->fileoff + sh->filesize;
+ }
+ cmd += sh->cmdsize;
+ }
+ // Let the kernel read ahead what the dynamic loader is going to
+ // map in memory soon after.
+ if (end > 0) {
+ ReadAhead(buf.getFd(), base - buf, end);
+ }
#endif
}
@@ -386,7 +531,7 @@ mozilla::ReadAheadFile(mozilla::pathstr_t aFilePath, const size_t aOffset,
if (!aOutFd) {
CloseHandle(fd);
}
-#elif defined(LINUX) || defined(XP_SOLARIS)
+#elif defined(LINUX) || defined(XP_MACOSX) || defined(XP_SOLARIS)
if (!aFilePath) {
if (aOutFd) {
*aOutFd = -1;
diff --git a/xpcom/glue/nsCRTGlue.h b/xpcom/glue/nsCRTGlue.h
index 6eda4bf8df..d3c666d05c 100644
--- a/xpcom/glue/nsCRTGlue.h
+++ b/xpcom/glue/nsCRTGlue.h
@@ -124,7 +124,9 @@ void NS_MakeRandomString(char* aBuf, int32_t aBufLen);
// identify or replace all known path separators.
#define KNOWN_PATH_SEPARATORS "\\/"
-#if defined(XP_WIN)
+#if defined(XP_MACOSX)
+ #define FILE_PATH_SEPARATOR "/"
+#elif defined(XP_WIN)
#define FILE_PATH_SEPARATOR "\\"
#elif defined(XP_UNIX)
#define FILE_PATH_SEPARATOR "/"
diff --git a/xpcom/glue/nsThreadUtils.cpp b/xpcom/glue/nsThreadUtils.cpp
index 8743c0d5fb..b6a37a7b83 100644
--- a/xpcom/glue/nsThreadUtils.cpp
+++ b/xpcom/glue/nsThreadUtils.cpp
@@ -19,6 +19,8 @@
#ifdef XP_WIN
#include <windows.h>
+#elif defined(XP_MACOSX)
+#include <sys/resource.h>
#endif
using namespace mozilla;
@@ -437,6 +439,12 @@ nsAutoLowPriorityIO::nsAutoLowPriorityIO()
#if defined(XP_WIN)
lowIOPrioritySet = SetThreadPriority(GetCurrentThread(),
THREAD_MODE_BACKGROUND_BEGIN);
+#elif defined(XP_MACOSX)
+ oldPriority = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD);
+ lowIOPrioritySet = oldPriority != -1 &&
+ setiopolicy_np(IOPOL_TYPE_DISK,
+ IOPOL_SCOPE_THREAD,
+ IOPOL_THROTTLE) != -1;
#else
lowIOPrioritySet = false;
#endif
@@ -449,5 +457,9 @@ nsAutoLowPriorityIO::~nsAutoLowPriorityIO()
// On Windows the old thread priority is automatically restored
SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END);
}
+#elif defined(XP_MACOSX)
+ if (MOZ_LIKELY(lowIOPrioritySet)) {
+ setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, oldPriority);
+ }
#endif
}
diff --git a/xpcom/glue/nsThreadUtils.h b/xpcom/glue/nsThreadUtils.h
index 9942a1060a..01270c1e94 100644
--- a/xpcom/glue/nsThreadUtils.h
+++ b/xpcom/glue/nsThreadUtils.h
@@ -1037,6 +1037,9 @@ public:
private:
bool lowIOPrioritySet;
+#if defined(XP_MACOSX)
+ int oldPriority;
+#endif
};
void
diff --git a/xpcom/glue/standalone/nsXPCOMGlue.cpp b/xpcom/glue/standalone/nsXPCOMGlue.cpp
index e4a5d8bd44..68dd58d1ed 100644
--- a/xpcom/glue/standalone/nsXPCOMGlue.cpp
+++ b/xpcom/glue/standalone/nsXPCOMGlue.cpp
@@ -97,7 +97,11 @@ static LibHandleType
GetLibHandle(pathstr_t aDependentLib)
{
LibHandleType libHandle = dlopen(aDependentLib,
- RTLD_GLOBAL | RTLD_LAZY);
+ RTLD_GLOBAL | RTLD_LAZY
+#ifdef XP_MACOSX
+ | RTLD_FIRST
+#endif
+ );
if (!libHandle) {
fprintf(stderr, "XPCOMGlueLoad error for file %s:\n%s\n", aDependentLib,
dlerror());
@@ -231,6 +235,22 @@ XPCOMGlueLoad(const char* aXPCOMFile)
char xpcomDir[MAXPATHLEN];
#ifdef XP_WIN
const char* lastSlash = ns_strrpbrk(aXPCOMFile, "/\\");
+#elif XP_MACOSX
+ // On OSX, the dependentlibs.list file lives under Contents/Resources.
+ // However, the actual libraries listed in dependentlibs.list live under
+ // Contents/MacOS. We want to read the list from Contents/Resources, then
+ // load the libraries from Contents/MacOS.
+ const char *tempSlash = strrchr(aXPCOMFile, '/');
+ size_t tempLen = size_t(tempSlash - aXPCOMFile);
+ if (tempLen > MAXPATHLEN) {
+ return nullptr;
+ }
+ char tempBuffer[MAXPATHLEN];
+ memcpy(tempBuffer, aXPCOMFile, tempLen);
+ tempBuffer[tempLen] = '\0';
+ const char *slash = strrchr(tempBuffer, '/');
+ tempLen = size_t(slash - tempBuffer);
+ const char *lastSlash = aXPCOMFile + tempLen;
#else
const char* lastSlash = strrchr(aXPCOMFile, '/');
#endif
@@ -239,11 +259,19 @@ XPCOMGlueLoad(const char* aXPCOMFile)
size_t len = size_t(lastSlash - aXPCOMFile);
if (len > MAXPATHLEN - sizeof(XPCOM_FILE_PATH_SEPARATOR
+#ifdef XP_MACOSX
+ "Resources"
+ XPCOM_FILE_PATH_SEPARATOR
+#endif
XPCOM_DEPENDENT_LIBS_LIST)) {
return nullptr;
}
memcpy(xpcomDir, aXPCOMFile, len);
strcpy(xpcomDir + len, XPCOM_FILE_PATH_SEPARATOR
+#ifdef XP_MACOSX
+ "Resources"
+ XPCOM_FILE_PATH_SEPARATOR
+#endif
XPCOM_DEPENDENT_LIBS_LIST);
cursor = xpcomDir + len + 1;
} else {
@@ -261,6 +289,14 @@ XPCOMGlueLoad(const char* aXPCOMFile)
return nullptr;
}
+#ifdef XP_MACOSX
+ tempLen = size_t(cursor - xpcomDir);
+ if (tempLen > MAXPATHLEN - sizeof("MacOS" XPCOM_FILE_PATH_SEPARATOR) - 1) {
+ return nullptr;
+ }
+ strcpy(cursor, "MacOS" XPCOM_FILE_PATH_SEPARATOR);
+ cursor += strlen(cursor);
+#endif
*cursor = '\0';
char buffer[MAXPATHLEN];
diff --git a/xpcom/io/CocoaFileUtils.h b/xpcom/io/CocoaFileUtils.h
new file mode 100644
index 0000000000..919c127162
--- /dev/null
+++ b/xpcom/io/CocoaFileUtils.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+// This namespace contains methods with Obj-C/Cocoa implementations. The header
+// is C/C++ for inclusion in C/C++-only files.
+
+#ifndef CocoaFileUtils_h_
+#define CocoaFileUtils_h_
+
+#include "nscore.h"
+#include <CoreFoundation/CoreFoundation.h>
+
+namespace CocoaFileUtils {
+
+nsresult RevealFileInFinder(CFURLRef aUrl);
+nsresult OpenURL(CFURLRef aUrl);
+nsresult GetFileCreatorCode(CFURLRef aUrl, OSType* aCreatorCode);
+nsresult SetFileCreatorCode(CFURLRef aUrl, OSType aCreatorCode);
+nsresult GetFileTypeCode(CFURLRef aUrl, OSType* aTypeCode);
+nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode);
+void AddOriginMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL);
+void AddQuarantineMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL,
+ const bool isFromWeb);
+CFURLRef GetTemporaryFolderCFURLRef();
+
+} // namespace CocoaFileUtils
+
+#endif
diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm
new file mode 100644
index 0000000000..a02b82ac1d
--- /dev/null
+++ b/xpcom/io/CocoaFileUtils.mm
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* 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 "CocoaFileUtils.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include <Cocoa/Cocoa.h>
+#include "nsObjCExceptions.h"
+#include "nsDebug.h"
+
+// Need to cope with us using old versions of the SDK and needing this on 10.10+
+#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
+const CFStringRef kCFURLQuarantinePropertiesKey = CFSTR("NSURLQuarantinePropertiesKey");
+#endif
+
+namespace CocoaFileUtils {
+
+nsresult RevealFileInFinder(CFURLRef url)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ BOOL success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)url path] inFileViewerRootedAtPath:@""];
+ [ap release];
+
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult OpenURL(CFURLRef url)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ BOOL success = [[NSWorkspace sharedWorkspace] openURL:(NSURL*)url];
+ [ap release];
+
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult GetFileCreatorCode(CFURLRef url, OSType *creatorCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url) || NS_WARN_IF(!creatorCode))
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoreleasePool localPool;
+
+ NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath];
+ if (!resolvedPath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil];
+ if (!dict) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSNumber* creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode];
+ if (!creatorNum) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *creatorCode = [creatorNum unsignedLongValue];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:creatorCode] forKey:NSFileHFSCreatorCode];
+ BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil];
+ [ap release];
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult GetFileTypeCode(CFURLRef url, OSType *typeCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url) || NS_WARN_IF(!typeCode))
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoreleasePool localPool;
+
+ NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath];
+ if (!resolvedPath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil];
+ if (!dict) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode];
+ if (!typeNum) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *typeCode = [typeNum unsignedLongValue];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult SetFileTypeCode(CFURLRef url, OSType typeCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:typeCode] forKey:NSFileHFSTypeCode];
+ BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil];
+ [ap release];
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void AddOriginMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL) {
+ typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, CFTypeRef);
+ static MDItemSetAttribute_type mdItemSetAttributeFunc = NULL;
+
+ static bool did_symbol_lookup = false;
+ if (!did_symbol_lookup) {
+ did_symbol_lookup = true;
+
+ CFBundleRef metadata_bundle = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata"));
+ if (!metadata_bundle) {
+ return;
+ }
+
+ mdItemSetAttributeFunc = (MDItemSetAttribute_type)
+ ::CFBundleGetFunctionPointerForName(metadata_bundle, CFSTR("MDItemSetAttribute"));
+ }
+ if (!mdItemSetAttributeFunc) {
+ return;
+ }
+
+ MDItemRef mdItem = ::MDItemCreate(NULL, filePath);
+ if (!mdItem) {
+ return;
+ }
+
+ CFMutableArrayRef list = ::CFArrayCreateMutable(kCFAllocatorDefault, 2, NULL);
+ if (!list) {
+ ::CFRelease(mdItem);
+ return;
+ }
+
+ // The first item in the list is the source URL of the downloaded file.
+ if (sourceURL) {
+ ::CFArrayAppendValue(list, ::CFURLGetString(sourceURL));
+ }
+
+ // If the referrer is known, store that in the second position.
+ if (referrerURL) {
+ ::CFArrayAppendValue(list, ::CFURLGetString(referrerURL));
+ }
+
+ mdItemSetAttributeFunc(mdItem, kMDItemWhereFroms, list);
+
+ ::CFRelease(list);
+ ::CFRelease(mdItem);
+}
+
+void AddQuarantineMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL,
+ const bool isFromWeb) {
+ CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
+ filePath,
+ kCFURLPOSIXPathStyle,
+ false);
+
+ // The properties key changed in 10.10:
+ CFStringRef quarantinePropKey;
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ quarantinePropKey = kCFURLQuarantinePropertiesKey;
+ } else {
+ quarantinePropKey = kLSItemQuarantineProperties;
+ }
+ CFDictionaryRef quarantineProps = NULL;
+ Boolean success = ::CFURLCopyResourcePropertyForKey(fileURL,
+ quarantinePropKey,
+ &quarantineProps,
+ NULL);
+
+ // If there aren't any quarantine properties then the user probably
+ // set up an exclusion and we don't need to add metadata.
+ if (!success || !quarantineProps) {
+ ::CFRelease(fileURL);
+ return;
+ }
+
+ // We don't know what to do if the props aren't a dictionary.
+ if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) {
+ ::CFRelease(fileURL);
+ ::CFRelease(quarantineProps);
+ return;
+ }
+
+ // Make a mutable copy of the properties.
+ CFMutableDictionaryRef mutQuarantineProps =
+ ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps);
+ ::CFRelease(quarantineProps);
+
+ // Add metadata that the OS couldn't infer.
+
+ if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineTypeKey)) {
+ CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload : kLSQuarantineTypeOtherDownload;
+ ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineTypeKey, type);
+ }
+
+ if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) && referrerURL) {
+ ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineOriginURLKey, referrerURL);
+ }
+
+ if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) && sourceURL) {
+ ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineDataURLKey, sourceURL);
+ }
+
+ // Set quarantine properties on file.
+ ::CFURLSetResourcePropertyForKey(fileURL,
+ quarantinePropKey,
+ mutQuarantineProps,
+ NULL);
+
+ ::CFRelease(fileURL);
+ ::CFRelease(mutQuarantineProps);
+}
+
+CFURLRef GetTemporaryFolderCFURLRef()
+{
+ NSString* tempDir = ::NSTemporaryDirectory();
+ return tempDir == nil ? NULL : (CFURLRef)[NSURL fileURLWithPath:tempDir
+ isDirectory:YES];
+}
+
+} // namespace CocoaFileUtils
diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build
index 467d61682b..43ce517c45 100644
--- a/xpcom/io/moz.build
+++ b/xpcom/io/moz.build
@@ -37,6 +37,11 @@ XPIDL_SOURCES += [
'nsIUnicharOutputStream.idl',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ XPIDL_SOURCES += [
+ 'nsILocalFileMac.idl',
+ ]
+
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
EXPORTS += ['nsLocalFileWin.h']
EXPORTS.mozilla += [
@@ -118,6 +123,11 @@ SOURCES += [
'FilePreferences.cpp',
]
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'CocoaFileUtils.mm',
+ ]
+
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
diff --git a/xpcom/io/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl
new file mode 100644
index 0000000000..d8655449bb
--- /dev/null
+++ b/xpcom/io/nsILocalFileMac.idl
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsILocalFile.idl"
+
+%{C++
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CoreFoundation.h>
+%}
+
+ native OSType(OSType);
+ native FSSpec(FSSpec);
+ native FSRef(FSRef);
+[ptr] native FSRefPtr(FSRef);
+ native CFURLRef(CFURLRef);
+
+[scriptable, builtinclass, uuid(623eca5b-c25d-4e27-be5a-789a66c4b2f7)]
+interface nsILocalFileMac : nsILocalFile
+{
+ /**
+ * initWithCFURL
+ *
+ * Init this object with a CFURLRef
+ *
+ * NOTE: Supported only for XP_MACOSX
+ * NOTE: If the path of the CFURL is /a/b/c, at least a/b must exist beforehand.
+ *
+ * @param aCFURL the CoreFoundation URL
+ *
+ */
+ [noscript] void initWithCFURL(in CFURLRef aCFURL);
+
+ /**
+ * initWithFSRef
+ *
+ * Init this object with an FSRef
+ *
+ * NOTE: Supported only for XP_MACOSX
+ *
+ * @param aFSRef the native FSRef
+ *
+ */
+ [noscript] void initWithFSRef([const] in FSRefPtr aFSRef);
+
+ /**
+ * getCFURL
+ *
+ * Returns the CFURLRef of the file object. The caller is
+ * responsible for calling CFRelease() on it.
+ *
+ * NOTE: Observes the state of the followLinks attribute.
+ * If the file object is an alias and followLinks is TRUE, returns
+ * the target of the alias. If followLinks is FALSE, returns
+ * the unresolved alias file.
+ *
+ * NOTE: Supported only for XP_MACOSX
+ *
+ * @return
+ *
+ */
+ [noscript] CFURLRef getCFURL();
+
+ /**
+ * getFSRef
+ *
+ * Returns the FSRef of the file object.
+ *
+ * NOTE: Observes the state of the followLinks attribute.
+ * If the file object is an alias and followLinks is TRUE, returns
+ * the target of the alias. If followLinks is FALSE, returns
+ * the unresolved alias file.
+ *
+ * NOTE: Supported only for XP_MACOSX
+ *
+ * @return
+ *
+ */
+ [noscript] FSRef getFSRef();
+
+ /**
+ * getFSSpec
+ *
+ * Returns the FSSpec of the file object.
+ *
+ * NOTE: Observes the state of the followLinks attribute.
+ * If the file object is an alias and followLinks is TRUE, returns
+ * the target of the alias. If followLinks is FALSE, returns
+ * the unresolved alias file.
+ *
+ * @return
+ *
+ */
+ [noscript] FSSpec getFSSpec();
+
+ /**
+ * fileSizeWithResFork
+ *
+ * Returns the combined size of both the data fork and the resource
+ * fork (if present) rather than just the size of the data fork
+ * as returned by GetFileSize()
+ *
+ */
+ readonly attribute int64_t fileSizeWithResFork;
+
+ /**
+ * fileType, creator
+ *
+ * File type and creator attributes
+ *
+ */
+ [noscript] attribute OSType fileType;
+ [noscript] attribute OSType fileCreator;
+
+ /**
+ * launchWithDoc
+ *
+ * Launch the application that this file points to with a document.
+ *
+ * @param aDocToLoad Must not be NULL. If no document, use nsIFile::launch
+ * @param aLaunchInBackground TRUE if the application should not come to the front.
+ *
+ */
+ void launchWithDoc(in nsIFile aDocToLoad, in boolean aLaunchInBackground);
+
+ /**
+ * openDocWithApp
+ *
+ * Open the document that this file points to with the given application.
+ *
+ * @param aAppToOpenWith The application with which to open the document.
+ * If NULL, the creator code of the document is used
+ * to determine the application.
+ * @param aLaunchInBackground TRUE if the application should not come to the front.
+ *
+ */
+ void openDocWithApp(in nsIFile aAppToOpenWith, in boolean aLaunchInBackground);
+
+ /**
+ * isPackage
+ *
+ * returns true if a directory is determined to be a package under Mac OS 9/X
+ *
+ */
+ boolean isPackage();
+
+ /**
+ * bundleDisplayName
+ *
+ * returns the display name of the application bundle (usually the human
+ * readable name of the application)
+ */
+ readonly attribute AString bundleDisplayName;
+
+ /**
+ * bundleIdentifier
+ *
+ * returns the identifier of the bundle
+ */
+ readonly attribute AUTF8String bundleIdentifier;
+
+ /**
+ * Last modified time of a bundle's contents (as opposed to its package
+ * directory). Our convention is to make the bundle's Info.plist file
+ * stand in for the rest of its contents -- since this file contains the
+ * bundle's version information and other identifiers. For non-bundles
+ * this is the same as lastModifiedTime.
+ */
+ readonly attribute int64_t bundleContentsLastModifiedTime;
+};
+
+%{C++
+extern "C"
+{
+NS_EXPORT nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowSymlinks, nsILocalFileMac** result);
+NS_EXPORT nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowSymlinks, nsILocalFileMac** result);
+}
+%}
diff --git a/xpcom/io/nsLocalFileUnix.h b/xpcom/io/nsLocalFileUnix.h
index 5bdc6a3da0..c49f448fd1 100644
--- a/xpcom/io/nsLocalFileUnix.h
+++ b/xpcom/io/nsLocalFileUnix.h
@@ -61,7 +61,9 @@
#define F_BSIZE f_bsize
#endif
-#if defined(HAVE_STAT64) && defined(HAVE_LSTAT64)
+// stat64 and lstat64 are deprecated on OS X. Normal stat and lstat are
+// 64-bit by default on OS X 10.6+.
+#if defined(HAVE_STAT64) && defined(HAVE_LSTAT64) && !defined(XP_DARWIN)
#define STAT stat64
#define LSTAT lstat64
#define HAVE_STATS64 1
diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S
index 92788667cd..131cfc3343 100644
--- a/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S
+++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S
@@ -2,6 +2,16 @@
# 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/.
+# Darwin gives a leading '_' to symbols defined in C code.
+#ifdef XP_DARWIN
+#define SYM(x) _ ## x
+#define CFI_STARTPROC
+#define CFI_ENDPROC
+#define CFI_DEF_CFA_OFFSET(offset)
+#define CFI_OFFSET(reg, offset)
+#define CFI_DEF_CFA_REGISTER(reg)
+#define CFI_DEF_CFA(reg, offset)
+#else
#define SYM(x) x
#define CFI_STARTPROC .cfi_startproc
#define CFI_ENDPROC .cfi_endproc
@@ -9,6 +19,7 @@
#define CFI_OFFSET(reg, offset) .cfi_offset reg, offset
#define CFI_DEF_CFA_REGISTER(reg) .cfi_def_cfa_register reg
#define CFI_DEF_CFA(reg, offset) .cfi_def_cfa reg, offset
+#endif
.intel_syntax noprefix
@@ -16,7 +27,9 @@
# uint32_t argc, nsXPTCVariant* argv);
.text
.global SYM(NS_InvokeByIndex)
+#ifndef XP_DARWIN
.type NS_InvokeByIndex, @function
+#endif
.align 4
SYM(NS_InvokeByIndex):
CFI_STARTPROC
@@ -103,5 +116,7 @@ SYM(NS_InvokeByIndex):
ret
CFI_ENDPROC
+#ifndef XP_DARWIN
// Magic indicating no need for an executable stack
.section .note.GNU-stack, "", @progbits ; .previous
+#endif
diff --git a/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp
index b6afb00ed2..d4a8a12fbc 100644
--- a/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp
+++ b/xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp
@@ -53,7 +53,9 @@ __asm__ (
is what xptcstubs uses. */
".align 2\n\t"
".globl " SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t"
+#ifndef XP_MACOSX
".type " SYMBOL_UNDERSCORE "NS_InvokeByIndex,@function\n"
+#endif
SYMBOL_UNDERSCORE "NS_InvokeByIndex:\n\t"
"pushl %ebp\n\t"
"movl %esp, %ebp\n\t"
@@ -89,5 +91,7 @@ __asm__ (
"movl %ebp, %esp\n\t"
"popl %ebp\n\t"
"ret\n"
+#ifndef XP_MACOSX
".size " SYMBOL_UNDERSCORE "NS_InvokeByIndex, . -" SYMBOL_UNDERSCORE "NS_InvokeByIndex\n\t"
+#endif
);
diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp
index cf68c0d6eb..78664dfcfb 100644
--- a/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp
+++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp
@@ -8,8 +8,8 @@
#include "xptcprivate.h"
#include "xptiprivate.h"
-#if !defined(__arm__) && !defined(LINUX)
-#error "This code is for Linux ARM only. Please check if it works for you, too.\nDepends strongly on gcc behaviour."
+#if !defined(__arm__) && !(defined(LINUX) || defined(XP_DARWIN))
+#error "This code is for Linux/iOS ARM only. Please check if it works for you, too.\nDepends strongly on gcc behaviour."
#endif
/* Specify explicitly a symbol for this function, don't try to guess the c++ mangled symbol. */
diff --git a/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp
index 5e2a9c17f8..6811a26ad6 100644
--- a/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp
+++ b/xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp
@@ -66,11 +66,20 @@ PrepareAndDispatch(uint32_t methodIndex, nsXPTCStubBase* self, uint32_t* args)
}
} // extern "C"
+#if !defined(XP_MACOSX)
+
#define STUB_HEADER(a, b) ".hidden " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t" \
".type " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,@function\n"
#define STUB_SIZE(a, b) ".size " SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev,.-" SYMBOL_UNDERSCORE "_ZN14nsXPTCStubBase" #a "Stub" #b "Ev\n\t"
+#else
+
+#define STUB_HEADER(a, b)
+#define STUB_SIZE(a, b)
+
+#endif
+
// gcc3 mangling tends to insert the length of the method name
#define STUB_ENTRY(n) \
asm(".text\n\t" \
@@ -103,12 +112,16 @@ asm(".text\n\t" \
// static nsresult SharedStub(uint32_t methodIndex) __attribute__((regparm(1)))
asm(".text\n\t"
".align 2\n\t"
+#if !defined(XP_MACOSX)
".type " SYMBOL_UNDERSCORE "SharedStub,@function\n\t"
+#endif
SYMBOL_UNDERSCORE "SharedStub:\n\t"
"leal 0x08(%esp), %ecx\n\t"
"movl 0x04(%esp), %edx\n\t"
"jmp " SYMBOL_UNDERSCORE "PrepareAndDispatch\n\t"
+#if !defined(XP_MACOSX)
".size " SYMBOL_UNDERSCORE "SharedStub,.-" SYMBOL_UNDERSCORE "SharedStub"
+#endif
);
#define SENTINEL_ENTRY(n) \
diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h
index 46227b3fd7..e4e07e2ccd 100644
--- a/xpcom/threads/SharedThreadPool.h
+++ b/xpcom/threads/SharedThreadPool.h
@@ -87,7 +87,7 @@ public:
// Use the system default in ASAN builds, because the default is assumed to be
// larger than the size we want to use and is hopefully sufficient for ASAN.
static const uint32_t kStackSize = nsIThreadManager::DEFAULT_STACK_SIZE;
-#elif defined(XP_WIN) || defined(LINUX)
+#elif defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)
static const uint32_t kStackSize = (256 * 1024);
#else
// All other platforms use their system defaults.
diff --git a/xpcom/threads/nsProcess.h b/xpcom/threads/nsProcess.h
index 21d28f1019..b9d0641770 100644
--- a/xpcom/threads/nsProcess.h
+++ b/xpcom/threads/nsProcess.h
@@ -19,7 +19,9 @@
#include "nsIWeakReferenceUtils.h"
#include "nsIObserver.h"
#include "nsString.h"
+#ifndef XP_MACOSX
#include "prproces.h"
+#endif
#if defined(PROCESSMODEL_WINAPI)
#include <windows.h>
#include <shellapi.h>
@@ -71,7 +73,7 @@ private:
int32_t mExitValue;
#if defined(PROCESSMODEL_WINAPI)
HANDLE mProcess;
-#else
+#elif !defined(XP_MACOSX)
PRProcess* mProcess;
#endif
};
diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp
index 7d490c5952..558f5e2890 100644
--- a/xpcom/threads/nsProcessCommon.cpp
+++ b/xpcom/threads/nsProcessCommon.cpp
@@ -33,12 +33,31 @@
#include "nsLiteralString.h"
#include "nsReadableUtils.h"
#else
+#ifdef XP_MACOSX
+#include <crt_externs.h>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <sys/errno.h>
+#endif
#include <sys/types.h>
#include <signal.h>
#endif
using namespace mozilla;
+#ifdef XP_MACOSX
+cpu_type_t pref_cpu_types[2] = {
+#if defined(__i386__)
+ CPU_TYPE_X86,
+#elif defined(__x86_64__)
+ CPU_TYPE_X86_64,
+#elif defined(__ppc__)
+ CPU_TYPE_POWERPC,
+#endif
+ CPU_TYPE_ANY
+};
+#endif
+
//-------------------------------------------------------------------//
// nsIProcess implementation
//-------------------------------------------------------------------//
@@ -55,7 +74,9 @@ nsProcess::nsProcess()
, mObserver(nullptr)
, mWeakObserver(nullptr)
, mExitValue(-1)
+#if !defined(XP_MACOSX)
, mProcess(nullptr)
+#endif
{
}
@@ -241,15 +262,33 @@ nsProcess::Monitor(void* aArg)
}
}
#else
+#ifdef XP_MACOSX
+ int exitCode = -1;
+ int status = 0;
+ pid_t result;
+ do {
+ result = waitpid(process->mPid, &status, 0);
+ } while (result == -1 && errno == EINTR);
+ if (result == process->mPid) {
+ if (WIFEXITED(status)) {
+ exitCode = WEXITSTATUS(status);
+ } else if (WIFSIGNALED(status)) {
+ exitCode = 256; // match NSPR's signal exit status
+ }
+ }
+#else
int32_t exitCode = -1;
if (PR_WaitProcess(process->mProcess, &exitCode) != PR_SUCCESS) {
exitCode = -1;
}
+#endif
// Lock in case Kill or GetExitCode are called during this
{
MutexAutoLock lock(process->mLock);
+#if !defined(XP_MACOSX)
process->mProcess = nullptr;
+#endif
process->mExitValue = exitCode;
if (process->mShutdown) {
return;
@@ -466,6 +505,34 @@ nsProcess::RunProcess(bool aBlocking, char** aMyArgv, nsIObserver* aObserver,
}
mPid = GetProcessId(mProcess);
+#elif defined(XP_MACOSX)
+ // Initialize spawn attributes.
+ posix_spawnattr_t spawnattr;
+ if (posix_spawnattr_init(&spawnattr) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set spawn attributes.
+ size_t attr_count = ArrayLength(pref_cpu_types);
+ size_t attr_ocount = 0;
+ if (posix_spawnattr_setbinpref_np(&spawnattr, attr_count, pref_cpu_types,
+ &attr_ocount) != 0 ||
+ attr_ocount != attr_count) {
+ posix_spawnattr_destroy(&spawnattr);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Note: |aMyArgv| is already null-terminated as required by posix_spawnp.
+ pid_t newPid = 0;
+ int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, &spawnattr, aMyArgv,
+ *_NSGetEnviron());
+ mPid = static_cast<int32_t>(newPid);
+
+ posix_spawnattr_destroy(&spawnattr);
+
+ if (result != 0) {
+ return NS_ERROR_FAILURE;
+ }
#else
mProcess = PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr);
if (!mProcess) {
@@ -545,6 +612,10 @@ nsProcess::Kill()
if (TerminateProcess(mProcess, 0) == 0) {
return NS_ERROR_FAILURE;
}
+#elif defined(XP_MACOSX)
+ if (kill(mPid, SIGKILL) != 0) {
+ return NS_ERROR_FAILURE;
+ }
#else
if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) {
return NS_ERROR_FAILURE;
diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp
index ed67fa6594..71e71822fa 100644
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -52,6 +52,11 @@
#define HAVE_SCHED_SETAFFINITY
#endif
+#ifdef XP_MACOSX
+#include <mach/mach.h>
+#include <mach/thread_policy.h>
+#endif
+
#ifdef MOZ_CANARY
# include <unistd.h>
# include <execinfo.h>
@@ -370,6 +375,16 @@ SetThreadAffinity(unsigned int cpu)
sched_setaffinity(0, sizeof(cpus), &cpus);
// Don't assert sched_setaffinity's return value because it intermittently (?)
// fails with EINVAL on Linux x64 try runs.
+#elif defined(XP_MACOSX)
+ // OS X does not provide APIs to pin threads to specific processors, but you
+ // can tag threads as belonging to the same "affinity set" and the OS will try
+ // to run them on the same processor. To run threads on different processors,
+ // tag them as belonging to different affinity sets. Tag 0, the default, means
+ // "no affinity" so let's pretend each CPU has its own tag `cpu+1`.
+ thread_affinity_policy_data_t policy;
+ policy.affinity_tag = cpu + 1;
+ MOZ_ALWAYS_TRUE(thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY,
+ &policy.affinity_tag, 1) == KERN_SUCCESS);
#elif defined(XP_WIN)
MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) != -1);
#endif
diff --git a/xpfe/appshell/nsAppShellService.cpp b/xpfe/appshell/nsAppShellService.cpp
index 427428fdf9..569b743fa6 100644
--- a/xpfe/appshell/nsAppShellService.cpp
+++ b/xpfe/appshell/nsAppShellService.cpp
@@ -112,8 +112,20 @@ nsAppShellService::CreateHiddenWindowHelper(bool aIsPrivate)
nsresult rv;
int32_t initialHeight = 100, initialWidth = 100;
+#ifdef XP_MACOSX
+ uint32_t chromeMask = 0;
+ nsAdoptingCString prefVal =
+ Preferences::GetCString("browser.hiddenWindowChromeURL");
+ const char* hiddenWindowURL = prefVal.get() ? prefVal.get() : DEFAULT_HIDDENWINDOW_URL;
+ if (aIsPrivate) {
+ hiddenWindowURL = DEFAULT_HIDDENWINDOW_URL;
+ } else {
+ mApplicationProvidedHiddenWindow = prefVal.get() ? true : false;
+ }
+#else
static const char hiddenWindowURL[] = DEFAULT_HIDDENWINDOW_URL;
uint32_t chromeMask = nsIWebBrowserChrome::CHROME_ALL;
+#endif
nsCOMPtr<nsIURI> url;
rv = NS_NewURI(getter_AddRefs(url), hiddenWindowURL);
@@ -543,12 +555,26 @@ nsAppShellService::CalculateWindowZLevel(nsIXULWindow *aParent,
else if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_LOWERED)
zLevel = nsIXULWindow::loweredZ;
+#ifdef XP_MACOSX
+ /* Platforms on which modal windows are always application-modal, not
+ window-modal (that's just the Mac, right?) want modal windows to
+ be stacked on top of everyone else.
+
+ On Mac OS X, bind modality to parent window instead of app (ala Mac OS 9)
+ */
+ uint32_t modalDepMask = nsIWebBrowserChrome::CHROME_MODAL |
+ nsIWebBrowserChrome::CHROME_DEPENDENT;
+ if (aParent && (aChromeMask & modalDepMask)) {
+ aParent->GetZLevel(&zLevel);
+ }
+#else
/* Platforms with native support for dependent windows (that's everyone
but pre-Mac OS X, right?) know how to stack dependent windows. On these
platforms, give the dependent window the same level as its parent,
so we won't try to override the normal platform behaviour. */
if ((aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT) && aParent)
aParent->GetZLevel(&zLevel);
+#endif
return zLevel;
}
@@ -635,6 +661,23 @@ nsAppShellService::JustCreateTopWindow(nsIXULWindow *aParent,
if (aChromeMask & nsIWebBrowserChrome::CHROME_MAC_SUPPRESS_ANIMATION)
widgetInitData.mIsAnimationSuppressed = true;
+#ifdef XP_MACOSX
+ // Mac OS X sheet support
+ // Adding CHROME_OPENAS_CHROME to sheetMask makes modal windows opened from
+ // nsGlobalWindow::ShowModalDialog() be dialogs (not sheets), while modal
+ // windows opened from nsPromptService::DoDialog() still are sheets. This
+ // fixes bmo bug 395465 (see nsCocoaWindow::StandardCreate() and
+ // nsCocoaWindow::SetModal()).
+ uint32_t sheetMask = nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
+ nsIWebBrowserChrome::CHROME_MODAL |
+ nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
+ if (parent &&
+ (parent != mHiddenWindow && parent != mHiddenPrivateWindow) &&
+ ((aChromeMask & sheetMask) == sheetMask)) {
+ widgetInitData.mWindowType = eWindowType_sheet;
+ }
+#endif
+
#if defined(XP_WIN)
if (widgetInitData.mWindowType == eWindowType_toplevel ||
widgetInitData.mWindowType == eWindowType_dialog)
diff --git a/xpfe/appshell/nsContentTreeOwner.cpp b/xpfe/appshell/nsContentTreeOwner.cpp
index c916f74d0e..8e9a065a9c 100644
--- a/xpfe/appshell/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/nsContentTreeOwner.cpp
@@ -40,6 +40,9 @@
#include "nsIScriptObjectPrincipal.h"
#include "nsIURI.h"
#include "nsIDocument.h"
+#if defined(XP_MACOSX)
+#include "nsThreadUtils.h"
+#endif
#include "mozilla/Preferences.h"
#include "mozilla/dom/Element.h"
@@ -903,6 +906,28 @@ nsContentTreeOwner::ProvideWindow(mozIDOMWindowProxy* aParent,
// nsContentTreeOwner: Accessors
//*****************************************************************************
+#if defined(XP_MACOSX)
+class nsContentTitleSettingEvent : public Runnable
+{
+public:
+ nsContentTitleSettingEvent(dom::Element* dse, const nsAString& wtm)
+ : mElement(dse),
+ mTitleDefault(wtm) {}
+
+ NS_IMETHOD Run() override
+ {
+ ErrorResult rv;
+ mElement->SetAttribute(NS_LITERAL_STRING("titledefault"), mTitleDefault, rv);
+ mElement->RemoveAttribute(NS_LITERAL_STRING("titlemodifier"), rv);
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<dom::Element> mElement;
+ nsString mTitleDefault;
+};
+#endif
+
void nsContentTreeOwner::XULWindow(nsXULWindow* aXULWindow)
{
mXULWindow = aXULWindow;
@@ -921,6 +946,18 @@ void nsContentTreeOwner::XULWindow(nsXULWindow* aXULWindow)
docShellElement->GetAttribute(NS_LITERAL_STRING("titledefault"), mTitleDefault);
docShellElement->GetAttribute(NS_LITERAL_STRING("titlemodifier"), mWindowTitleModifier);
docShellElement->GetAttribute(NS_LITERAL_STRING("titlepreface"), mTitlePreface);
+
+#if defined(XP_MACOSX)
+ // On OS X, treat the titlemodifier like it's the titledefault, and don't ever append
+ // the separator + appname.
+ if (mTitleDefault.IsEmpty()) {
+ NS_DispatchToCurrentThread(
+ new nsContentTitleSettingEvent(docShellElement,
+ mWindowTitleModifier));
+ mTitleDefault = mWindowTitleModifier;
+ mWindowTitleModifier.Truncate();
+ }
+#endif
docShellElement->GetAttribute(NS_LITERAL_STRING("titlemenuseparator"), mTitleSeparator);
}
}
diff --git a/xpfe/appshell/nsWebShellWindow.cpp b/xpfe/appshell/nsWebShellWindow.cpp
index 7ec39a9cdf..2893e78682 100644
--- a/xpfe/appshell/nsWebShellWindow.cpp
+++ b/xpfe/appshell/nsWebShellWindow.cpp
@@ -73,7 +73,7 @@
#include "nsPIWindowRoot.h"
-#if defined(MOZ_WIDGET_GTK)
+#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
#include "nsINativeMenuService.h"
#define USE_NATIVE_MENUS
#endif
diff --git a/xpfe/appshell/nsXULWindow.cpp b/xpfe/appshell/nsXULWindow.cpp
index 45403b2b0a..5850850651 100644
--- a/xpfe/appshell/nsXULWindow.cpp
+++ b/xpfe/appshell/nsXULWindow.cpp
@@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 ci et: */
/* 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/. */
@@ -1039,7 +1040,7 @@ void nsXULWindow::OnChromeLoaded()
bool positionSet = !mIgnoreXULPosition;
nsCOMPtr<nsIXULWindow> parentWindow(do_QueryReferent(mParentWindow));
-#if defined(XP_UNIX)
+#if defined(XP_UNIX) && !defined(XP_MACOSX)
// don't override WM placement on unix for independent, top-level windows
// (however, we think the benefits of intelligent dependent window placement
// trump that override.)