From 13fcc4a046ef86641b371b1f1209b345c147bfbe Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Tue, 26 Apr 2022 11:34:08 -0500 Subject: [PATCH] Issue #1829 - Revert "Issue #1751" --- accessible/base/Platform.h | 4 +- accessible/base/moz.build | 4 + accessible/generic/moz.build | 4 + accessible/html/moz.build | 4 + accessible/ipc/moz.build | 4 + accessible/ipc/other/moz.build | 4 + accessible/mac/ARIAGridAccessibleWrap.h | 22 + accessible/mac/AccessibleWrap.h | 103 + accessible/mac/AccessibleWrap.mm | 256 + accessible/mac/ApplicationAccessibleWrap.h | 22 + accessible/mac/DocAccessibleWrap.h | 25 + accessible/mac/DocAccessibleWrap.mm | 21 + accessible/mac/HTMLTableAccessibleWrap.h | 24 + accessible/mac/HyperTextAccessibleWrap.h | 20 + accessible/mac/ImageAccessibleWrap.h | 22 + accessible/mac/MacUtils.h | 26 + accessible/mac/MacUtils.mm | 32 + accessible/mac/Platform.mm | 174 + accessible/mac/RootAccessibleWrap.h | 34 + accessible/mac/RootAccessibleWrap.mm | 53 + accessible/mac/TextLeafAccessibleWrap.h | 19 + accessible/mac/XULListboxAccessibleWrap.h | 20 + accessible/mac/XULMenuAccessibleWrap.h | 19 + accessible/mac/XULTreeGridAccessibleWrap.h | 20 + accessible/mac/moz.build | 44 + accessible/mac/mozAccessible.h | 181 + accessible/mac/mozAccessible.mm | 1197 ++++ accessible/mac/mozAccessibleProtocol.h | 69 + accessible/mac/mozActionElements.h | 37 + accessible/mac/mozActionElements.mm | 340 + accessible/mac/mozDocAccessible.h | 31 + accessible/mac/mozDocAccessible.mm | 111 + accessible/mac/mozHTMLAccessible.h | 16 + accessible/mac/mozHTMLAccessible.mm | 141 + accessible/mac/mozTableAccessible.h | 28 + accessible/mac/mozTableAccessible.mm | 281 + accessible/mac/mozTextAccessible.h | 17 + accessible/mac/mozTextAccessible.mm | 627 ++ accessible/moz.build | 2 + accessible/xpcom/moz.build | 4 + accessible/xul/moz.build | 4 + build/macosx/build-cctools.sh | 26 + build/macosx/cross-mozconfig.common | 47 + build/macosx/local-mozconfig.common | 46 + build/macosx/mozconfig.common | 5 + build/macosx/permissions/chown_revert.c | 20 + build/macosx/permissions/chown_root.c | 14 + build/macosx/universal/mozconfig | 11 + build/macosx/universal/mozconfig.common | 55 + build/macosx/universal/unify | 1525 ++++ build/moz.configure/init.configure | 14 + chrome/nsChromeRegistry.cpp | 2 + chrome/nsChromeRegistryChrome.cpp | 2 + config/external/nspr/prcpucfg.h | 4 +- config/rules.mk | 5 + db/mork/src/morkConfig.h | 29 +- db/sqlite3/src/moz.build | 4 + .../dev-edition-promo/dev-edition-promo.css | 4 + .../framework/toolbox-process-window.js | 10 + devtools/client/jar.mn | 2 +- dom/base/Navigator.cpp | 6 + dom/base/nsContentUtils.cpp | 5 + dom/base/nsFocusManager.cpp | 13 +- dom/base/nsGlobalWindow.cpp | 21 + dom/base/nsJSEnvironment.cpp | 4 + dom/base/nsObjectLoadingContent.cpp | 19 + dom/base/nsWindowRoot.cpp | 6 + dom/canvas/WebGL2Context.cpp | 7 + dom/canvas/WebGLBuffer.cpp | 2 +- dom/canvas/WebGLContext.h | 13 + dom/canvas/WebGLContextBuffers.cpp | 16 + dom/canvas/WebGLContextDraw.cpp | 7 + dom/canvas/WebGLContextValidate.cpp | 12 + dom/canvas/WebGLProgram.cpp | 34 + dom/canvas/WebGLShaderValidator.cpp | 37 +- dom/events/EventStateManager.cpp | 52 +- dom/events/TextComposition.cpp | 13 + dom/gamepad/cocoa/CocoaGamepad.cpp | 590 ++ dom/gamepad/moz.build | 4 + dom/geolocation/moz.build | 4 + dom/html/HTMLButtonElement.cpp | 6 +- dom/html/HTMLImageElement.cpp | 3 + dom/html/HTMLInputElement.cpp | 10 +- dom/html/HTMLInputElement.h | 2 +- dom/html/HTMLObjectElement.cpp | 141 + dom/html/HTMLObjectElement.h | 12 + dom/html/HTMLSharedObjectElement.cpp | 26 + dom/html/HTMLSharedObjectElement.h | 5 + dom/html/HTMLSummaryElement.cpp | 9 +- dom/html/nsGenericHTMLElement.cpp | 4 + dom/media/GraphDriver.cpp | 82 + dom/media/GraphDriver.h | 5 + dom/media/eme/MediaKeySystemAccessManager.cpp | 3 + dom/media/gmp/GMPChild.cpp | 6 +- dom/media/gmp/rlz/GMPDeviceBinding.cpp | 48 + .../platforms/ffmpeg/FFmpegRuntimeLinker.cpp | 9 + dom/media/standalone/moz.build | 3 + dom/media/systemservices/LoadMonitor.cpp | 27 + .../systemservices/OSXRunLoopSingleton.cpp | 44 + .../systemservices/OSXRunLoopSingleton.h | 25 + dom/media/systemservices/moz.build | 8 + dom/media/webaudio/AudioContext.cpp | 2 + .../webrtc/MediaEngineCameraVideoSource.cpp | 7 + .../cocoa/OSXSpeechSynthesizerModule.cpp | 56 + .../synth/cocoa/OSXSpeechSynthesizerService.h | 43 + .../cocoa/OSXSpeechSynthesizerService.mm | 498 ++ dom/media/webspeech/synth/cocoa/moz.build | 11 + dom/media/webspeech/synth/moz.build | 3 + dom/moz.build | 2 +- dom/plugins/base/PluginPRLibrary.cpp | 18 +- dom/plugins/base/PluginPRLibrary.h | 25 +- dom/plugins/base/moz.build | 6 + dom/plugins/base/npapi.h | 209 +- dom/plugins/base/npfunctions.h | 32 + dom/plugins/base/nsNPAPIPlugin.cpp | 113 +- dom/plugins/base/nsNPAPIPlugin.h | 4 + dom/plugins/base/nsNPAPIPluginInstance.cpp | 41 +- dom/plugins/base/nsNPAPIPluginInstance.h | 18 + dom/plugins/base/nsPluginHost.cpp | 15 + dom/plugins/base/nsPluginInstanceOwner.cpp | 896 ++- dom/plugins/base/nsPluginInstanceOwner.h | 63 +- dom/plugins/base/nsPluginNativeWindow.cpp | 2 +- dom/plugins/base/nsPluginTags.cpp | 4 +- dom/plugins/base/nsPluginsDirDarwin.cpp | 572 ++ dom/plugins/ipc/NPEventOSX.h | 193 + dom/plugins/ipc/PluginInstanceChild.cpp | 389 +- dom/plugins/ipc/PluginInstanceChild.h | 17 +- dom/plugins/ipc/PluginInstanceParent.cpp | 258 +- dom/plugins/ipc/PluginInstanceParent.h | 5 +- dom/plugins/ipc/PluginInterposeOSX.h | 137 + dom/plugins/ipc/PluginInterposeOSX.mm | 1158 ++++ dom/plugins/ipc/PluginLibrary.h | 9 +- dom/plugins/ipc/PluginMessageUtils.cpp | 5 +- dom/plugins/ipc/PluginMessageUtils.h | 188 +- dom/plugins/ipc/PluginModuleChild.cpp | 14 +- dom/plugins/ipc/PluginModuleParent.cpp | 108 +- dom/plugins/ipc/PluginModuleParent.h | 15 +- dom/plugins/ipc/PluginProcessChild.cpp | 55 + dom/plugins/ipc/PluginProcessParent.cpp | 7 + dom/plugins/ipc/PluginQuirks.cpp | 10 + dom/plugins/ipc/PluginUtilsOSX.h | 92 + dom/plugins/ipc/PluginUtilsOSX.mm | 462 ++ dom/plugins/ipc/interpose/moz.build | 12 + .../ipc/interpose/plugin_child_interpose.mm | 134 + dom/plugins/ipc/moz.build | 16 + dom/system/OSFileConstants.cpp | 56 +- dom/system/mac/CoreLocationLocationProvider.h | 59 + .../mac/CoreLocationLocationProvider.mm | 268 + dom/system/mac/moz.build | 14 + dom/system/moz.build | 2 + dom/xbl/builtin/mac/jar.mn | 6 + dom/xbl/builtin/mac/moz.build | 6 + dom/xbl/builtin/mac/platformHTMLBindings.xml | 72 + dom/xbl/builtin/moz.build | 2 + dom/xbl/nsXBLPrototypeHandler.cpp | 7 +- dom/xul/nsXULElement.cpp | 12 + embedding/components/build/moz.build | 5 + embedding/components/printingui/mac/moz.build | 15 + .../printingui/mac/nsPrintProgress.cpp | 213 + .../printingui/mac/nsPrintProgress.h | 44 + .../printingui/mac/nsPrintProgressParams.cpp | 47 + .../printingui/mac/nsPrintProgressParams.h | 28 + .../printingui/mac/nsPrintingPromptService.h | 43 + .../mac/nsPrintingPromptServiceX.mm | 128 + embedding/components/printingui/moz.build | 2 + extensions/auth/gssapi.h | 9 + extensions/auth/nsAuthGSSAPI.cpp | 47 + gfx/2d/2D.h | 5 + gfx/2d/Factory.cpp | 30 + gfx/2d/JobScheduler_posix.cpp | 4 +- gfx/2d/MacIOSurface.cpp | 615 ++ gfx/2d/MacIOSurface.h | 216 + gfx/2d/NativeFontResourceMac.cpp | 67 + gfx/2d/NativeFontResourceMac.h | 42 + gfx/2d/PathCG.cpp | 435 ++ gfx/2d/PathCG.h | 114 + gfx/2d/QuartzSupport.h | 98 + gfx/2d/QuartzSupport.mm | 625 ++ gfx/2d/SFNTNameTable.cpp | 96 + gfx/2d/SFNTNameTable.h | 5 + gfx/2d/ScaledFontMac.cpp | 247 + gfx/2d/ScaledFontMac.h | 79 + gfx/2d/moz.build | 20 +- gfx/cairo/cairo/src/moz.build | 4 +- gfx/cairo/libpixman/src/moz.build | 19 +- gfx/gl/ForceDiscreteGPUHelperCGL.h | 36 + gfx/gl/GLBlitHelper.cpp | 111 + gfx/gl/GLBlitHelper.h | 3 + gfx/gl/GLContext.cpp | 78 + gfx/gl/GLContextCGL.h | 67 + gfx/gl/GLContextEAGL.h | 80 + gfx/gl/GLContextProvider.h | 7 + gfx/gl/GLContextProviderCGL.mm | 397 ++ gfx/gl/GLContextProviderEAGL.mm | 275 + gfx/gl/GLScreenBuffer.cpp | 8 +- gfx/gl/SharedSurfaceIO.cpp | 248 + gfx/gl/SharedSurfaceIO.h | 100 + gfx/gl/moz.build | 32 +- gfx/layers/ImageContainer.cpp | 4 + gfx/layers/ImageContainer.h | 6 + .../basic/MacIOSurfaceTextureHostBasic.cpp | 107 + .../basic/MacIOSurfaceTextureHostBasic.h | 97 + gfx/layers/basic/TextureHostBasic.cpp | 10 + gfx/layers/client/TextureClient.cpp | 10 + .../composite/LayerManagerComposite.cpp | 6 + gfx/layers/composite/TextureHost.cpp | 4 + gfx/layers/ipc/ShadowLayerUtils.h | 8 +- gfx/layers/ipc/ShadowLayerUtilsMac.cpp | 40 + gfx/layers/moz.build | 25 + gfx/layers/opengl/GLManager.cpp | 70 + gfx/layers/opengl/GLManager.h | 44 + .../opengl/MacIOSurfaceTextureClientOGL.cpp | 140 + .../opengl/MacIOSurfaceTextureClientOGL.h | 58 + .../opengl/MacIOSurfaceTextureHostOGL.cpp | 180 + .../opengl/MacIOSurfaceTextureHostOGL.h | 114 + gfx/layers/opengl/TextureHostOGL.cpp | 13 + gfx/skia/generate_mozbuild.py | 3 +- gfx/skia/moz.build | 3 +- gfx/src/nsDeviceContext.cpp | 38 +- gfx/src/nsDeviceContext.h | 3 + gfx/thebes/PrintTargetCG.cpp | 120 + gfx/thebes/PrintTargetCG.h | 42 + gfx/thebes/gfxCoreTextShaper.cpp | 800 +++ gfx/thebes/gfxCoreTextShaper.h | 71 + gfx/thebes/gfxFontUtils.cpp | 41 +- gfx/thebes/gfxMacFont.cpp | 475 ++ gfx/thebes/gfxMacFont.h | 102 + gfx/thebes/gfxMacPlatformFontList.h | 182 + gfx/thebes/gfxMacPlatformFontList.mm | 1444 ++++ gfx/thebes/gfxPlatform.cpp | 7 +- gfx/thebes/gfxPlatformMac.cpp | 617 ++ gfx/thebes/gfxPlatformMac.h | 93 + gfx/thebes/gfxPrefs.h | 3 + gfx/thebes/gfxQuartzNativeDrawing.cpp | 74 + gfx/thebes/gfxQuartzNativeDrawing.h | 71 + gfx/thebes/gfxQuartzSurface.cpp | 137 + gfx/thebes/gfxQuartzSurface.h | 43 + gfx/thebes/gfxTextRun.cpp | 5 + gfx/thebes/moz.build | 25 +- .../{convert.patch.outdated => convert.patch} | 0 gfx/ycbcr/yuv_row_posix.cpp | 31 +- hal/cocoa/CocoaSensor.mm | 149 + hal/cocoa/smslib.h | 159 + hal/cocoa/smslib.mm | 938 +++ hal/moz.build | 13 + image/decoders/icon/mac/moz.build | 10 + image/decoders/icon/mac/nsIconChannel.h | 59 + image/decoders/icon/mac/nsIconChannelCocoa.mm | 565 ++ image/decoders/icon/moz.build | 3 + image/decoders/moz.build | 3 + image/imgFrame.cpp | 10 +- intl/locale/mac/moz.build | 15 + intl/locale/mac/nsCollationMacUC.cpp | 253 + intl/locale/mac/nsCollationMacUC.h | 44 + intl/locale/mac/nsDateTimeFormatMac.cpp | 266 + intl/locale/mac/nsDateTimeFormatMac.h | 61 + intl/locale/mac/nsMacCharset.cpp | 59 + intl/locale/moz.build | 2 + intl/locale/nsIDateTimeFormat.cpp | 7 +- intl/locale/nsLocaleConstructors.h | 11 +- intl/locale/nsLocaleService.cpp | 37 +- intl/lwbrk/moz.build | 4 + intl/lwbrk/nsCarbonBreaker.cpp | 44 + ipc/glue/GeckoChildProcessHost.h | 6 + ipc/glue/SharedMemoryBasic.h | 4 + ipc/glue/SharedMemoryBasic_mach.h | 83 + ipc/glue/SharedMemoryBasic_mach.mm | 664 ++ ipc/glue/moz.build | 8 +- .../ds/MemoryProtectionExceptionHandler.cpp | 466 +- js/src/jit/JitOptions.cpp | 8 +- js/src/jsmath.cpp | 2 +- js/src/jsnativestack.cpp | 7 +- js/src/jsstr.cpp | 2 +- js/src/old-configure.in | 6 + js/src/threading/posix/Thread.cpp | 4 +- js/src/vm/Runtime.cpp | 6 +- js/src/vm/Runtime.h | 7 + js/src/wasm/WasmSignalHandlers.cpp | 367 +- js/src/wasm/WasmSignalHandlers.h | 29 +- js/xpconnect/shell/moz.build | 5 + js/xpconnect/shell/xpcshell.cpp | 11 + js/xpconnect/shell/xpcshellMacUtils.h | 8 + js/xpconnect/shell/xpcshellMacUtils.mm | 18 + js/xpconnect/src/Sandbox.cpp | 11 + js/xpconnect/src/XPCJSContext.cpp | 7 +- js/xpconnect/src/XPCShellImpl.cpp | 24 + layout/base/nsCSSFrameConstructor.cpp | 33 + layout/base/nsCSSFrameConstructor.h | 4 + layout/base/nsPresContext.cpp | 14 + layout/base/nsPresShell.cpp | 2 + layout/build/moz.build | 4 + layout/forms/nsListControlFrame.cpp | 14 + layout/generic/nsFrame.cpp | 11 + layout/generic/nsPluginFrame.cpp | 127 +- layout/generic/nsSelection.cpp | 18 + layout/printing/nsPrintEngine.cpp | 5 + layout/reftests/reftest.list | 3 + layout/style/nsComputedDOMStyle.cpp | 25 +- layout/style/res/forms.css | 6 + layout/xul/nsButtonBoxFrame.cpp | 3 + layout/xul/nsMenuBarListener.cpp | 12 +- layout/xul/nsMenuFrame.cpp | 16 +- layout/xul/nsMenuPopupFrame.cpp | 11 +- layout/xul/nsRepeatService.h | 4 + layout/xul/nsSliderFrame.cpp | 12 +- layout/xul/nsXULPopupManager.cpp | 28 + layout/xul/tree/nsTreeBodyFrame.cpp | 53 + mailnews/addrbook/public/nsAbBaseCID.h | 41 + mailnews/base/ispdata/moz.build | 4 +- mailnews/base/prefs/content/AccountWizard.xul | 4 + mailnews/base/search/src/nsMsgFilter.cpp | 5 + mailnews/base/src/moz.build | 2 + mailnews/base/src/nsMessenger.cpp | 6 + mailnews/base/src/nsMessengerOSXIntegration.h | 63 + .../base/src/nsMessengerOSXIntegration.mm | 700 ++ mailnews/base/src/nsMsgMailSession.cpp | 8 +- mailnews/base/src/nsStatusBarBiffManager.cpp | 2 + mailnews/base/util/nsMsgUtils.cpp | 4 +- mailnews/build/moz.build | 4 + mailnews/build/nsMailModule.cpp | 46 + mailnews/compose/src/moz.build | 10 + mailnews/compose/src/nsMsgAppleCodes.h | 106 + mailnews/compose/src/nsMsgAppleDouble.h | 207 + .../compose/src/nsMsgAppleDoubleEncode.cpp | 266 + mailnews/compose/src/nsMsgAppleEncode.cpp | 703 ++ .../compose/src/nsMsgAttachmentHandler.cpp | 351 +- mailnews/compose/src/nsMsgAttachmentHandler.h | 50 + mailnews/compose/src/nsMsgSend.cpp | 4 + mailnews/imap/src/nsIMAPBodyShell.cpp | 21 +- mailnews/import/applemail/src/moz.build | 14 + .../applemail/src/nsAppleMailImport.cpp | 623 ++ .../import/applemail/src/nsAppleMailImport.h | 78 + .../import/applemail/src/nsEmlxHelperUtils.h | 55 + .../import/applemail/src/nsEmlxHelperUtils.mm | 240 + mailnews/import/build/moz.build | 7 + mailnews/import/build/nsImportModule.cpp | 31 + mailnews/import/content/importDialog.xul | 4 + mailnews/jar.mn | 2 + mailnews/mailnews.js | 22 + mailnews/mime/src/mimeebod.cpp | 14 + mailnews/mime/src/mimemapl.cpp | 24 +- mailnews/mime/src/mimemult.cpp | 17 + mailnews/moz.build | 5 + media/ffvpx/config.h | 2 + media/ffvpx/config_darwin64.asm | 642 ++ media/ffvpx/config_darwin64.h | 658 ++ media/libav/config.h | 2 + media/libav/config_darwin.asm | 249 + media/libav/config_darwin.h | 259 + media/libcubeb/src/cubeb_osx_run_loop.c | 11 + media/libcubeb/src/cubeb_osx_run_loop.h | 22 + media/libcubeb/src/moz.build | 4 + memory/build/mozmemory.h | 15 +- memory/build/mozmemory_wrap.c | 2 + memory/build/mozmemory_wrap.h | 2 +- memory/build/replace_malloc.c | 111 +- memory/mozalloc/mozalloc.cpp | 19 +- memory/mozalloc/mozalloc.h | 14 +- memory/volatile/VolatileBuffer.h | 4 +- mfbt/ThreadLocal.h | 5 +- modules/libmar/tool/mar.c | 22 +- modules/libpref/Preferences.cpp | 4 +- modules/libpref/init/all.js | 410 +- mozglue/misc/StackWalk.cpp | 122 +- mozglue/misc/TimeStamp.h | 9 + netwerk/base/NetworkInfoServiceCocoa.cpp | 103 + netwerk/base/moz.build | 9 + netwerk/base/nsSocketTransport2.cpp | 11 + netwerk/base/nsURLHelperOSX.cpp | 216 + netwerk/build/moz.build | 7 +- .../mdns/libmdns/MDNSResponderOperator.cpp | 779 +++ .../dns/mdns/libmdns/MDNSResponderOperator.h | 152 + .../dns/mdns/libmdns/MDNSResponderReply.cpp | 302 + netwerk/dns/mdns/libmdns/MDNSResponderReply.h | 164 + netwerk/dns/mdns/libmdns/moz.build | 38 +- .../mdns/libmdns/nsDNSServiceDiscovery.cpp | 262 + .../dns/mdns/libmdns/nsDNSServiceDiscovery.h | 49 + netwerk/streamconv/converters/moz.build | 6 +- netwerk/system/mac/moz.build | 13 + netwerk/system/mac/nsNetworkLinkService.h | 54 + netwerk/system/mac/nsNetworkLinkService.mm | 526 ++ netwerk/system/moz.build | 2 + old-configure.in | 222 +- python/mozbuild/mozbuild/artifacts.py | 4 + testing/crashtest/crashtests.list | 1 + .../components/apppicker/content/appPicker.js | 8 + .../blocklist/nsBlocklistService.js | 9 + .../downloads/nsDownloadManager.cpp | 42 +- .../jsdownloads/src/DownloadIntegration.jsm | 8 +- .../jsdownloads/src/DownloadPlatform.cpp | 80 +- .../jsdownloads/src/DownloadUIHelper.jsm | 7 + toolkit/components/jsdownloads/src/moz.build | 2 +- toolkit/components/parentalcontrols/moz.build | 2 + .../nsParentalControlsServiceCocoa.mm | 79 + .../passwordmgr/content/passwordManager.js | 5 + .../passwordmgr/content/passwordManager.xul | 4 +- .../perfmonitoring/nsPerformanceStats.cpp | 36 +- toolkit/components/places/PlacesUtils.jsm | 8 +- toolkit/components/places/moz.build | 3 +- toolkit/components/printing/jar.mn | 2 + .../prompts/content/commonDialog.xul | 12 +- .../components/prompts/content/tabprompts.xml | 17 +- toolkit/components/prompts/jar.mn | 2 +- .../satchel/nsFormFillController.cpp | 16 + toolkit/components/startup/moz.build | 6 +- toolkit/components/startup/nsAppStartup.cpp | 43 + toolkit/components/startup/nsUserInfoMac.h | 25 + toolkit/components/startup/nsUserInfoMac.mm | 84 + .../components/thumbnails/PageThumbUtils.jsm | 12 + toolkit/components/thumbnails/moz.build | 2 +- .../viewsource/content/viewPartialSource.xul | 10 +- .../viewsource/content/viewSource.xul | 16 +- toolkit/components/viewsource/jar.mn | 2 +- toolkit/content/aboutProfiles.js | 2 + toolkit/content/aboutSupport.xhtml | 8 + toolkit/content/customizeToolbar.js | 4 + toolkit/content/dialogOverlay.xul | 57 +- toolkit/content/globalOverlay.js | 7 + toolkit/content/jar.mn | 15 +- toolkit/content/license.html | 2 +- toolkit/content/macWindowMenu.inc | 40 + toolkit/content/macWindowMenu.js | 51 + toolkit/content/widgets/dialog.xml | 4 + toolkit/content/widgets/findbar.xml | 33 + toolkit/content/widgets/optionsDialog.xml | 11 + toolkit/content/widgets/preferences.xml | 4 + toolkit/content/widgets/tree.xml | 6 +- toolkit/content/widgets/wizard.xml | 49 + toolkit/content/xul.css | 21 + toolkit/library/moz.build | 42 +- toolkit/modules/AppConstants.jsm | 2 + toolkit/modules/LightweightThemeConsumer.jsm | 23 + toolkit/modules/UpdateUtils.jsm | 11 + toolkit/modules/WindowDraggingUtils.jsm | 2 +- toolkit/modules/moz.build | 4 +- toolkit/moz.build | 2 + toolkit/moz.configure | 8 +- .../mozapps/downloads/content/downloads.xul | 12 +- .../downloads/content/unknownContentType.xul | 6 +- toolkit/mozapps/downloads/nsHelperAppDlg.js | 10 + toolkit/mozapps/extensions/GMPUtils.jsm | 15 +- .../mozapps/extensions/content/extensions.js | 4 + toolkit/mozapps/extensions/content/update.xul | 24 +- toolkit/mozapps/extensions/jar.mn | 2 +- .../mozapps/update/common/updatecommon.cpp | 4 +- toolkit/mozapps/update/common/updatedefines.h | 4 + toolkit/mozapps/update/nsUpdateService.js | 137 +- toolkit/mozapps/update/updater/Makefile.in | 13 + .../mozapps/update/updater/launchchild_osx.mm | 384 + .../updater/macbuild/Contents/Info.plist.in | 39 + .../update/updater/macbuild/Contents/PkgInfo | 1 + .../English.lproj/InfoPlist.strings.in | 7 + .../English.lproj/MainMenu.nib/classes.nib | 19 + .../English.lproj/MainMenu.nib/info.nib | 22 + .../MainMenu.nib/keyedobjects.nib | Bin 0 -> 5567 bytes .../macbuild/Contents/Resources/updater.icns | Bin 0 -> 55969 bytes toolkit/mozapps/update/updater/moz.build | 15 +- toolkit/mozapps/update/updater/progressui.h | 3 + .../mozapps/update/updater/progressui_osx.mm | 144 + .../update/updater/updater-common.build | 18 + toolkit/mozapps/update/updater/updater.cpp | 277 +- .../profile/content/createProfileWizard.js | 4 + .../profile/content/createProfileWizard.xul | 4 + toolkit/profile/content/profileSelection.js | 2 + toolkit/profile/jar.mn | 4 +- toolkit/profile/nsProfileLock.cpp | 72 +- toolkit/profile/nsToolkitProfileService.cpp | 33 +- toolkit/system/osxproxy/ProxyUtils.h | 21 + toolkit/system/osxproxy/ProxyUtils.mm | 182 + toolkit/system/osxproxy/moz.build | 13 + .../osxproxy/nsOSXSystemProxySettings.mm | 326 + .../tests/gtest/TestProxyBypassRules.cpp | 41 + toolkit/system/osxproxy/tests/gtest/moz.build | 17 + toolkit/themes/moz.build | 4 +- .../osx/global/10pct_transparent_grey.png | Bin 0 -> 123 bytes .../osx/global/50pct_transparent_grey.png | Bin 0 -> 107 bytes toolkit/themes/osx/global/alerts/alert.css | 30 + toolkit/themes/osx/global/arrow.css | 38 + .../themes/osx/global/arrow/arrow-dn-dis.gif | Bin 0 -> 65 bytes .../themes/osx/global/arrow/arrow-dn-dis.png | Bin 0 -> 185 bytes .../osx/global/arrow/arrow-dn-sharp.gif | Bin 0 -> 51 bytes toolkit/themes/osx/global/arrow/arrow-dn.gif | Bin 0 -> 56 bytes toolkit/themes/osx/global/arrow/arrow-dn.png | Bin 0 -> 191 bytes .../themes/osx/global/arrow/arrow-lft-dis.gif | Bin 0 -> 105 bytes .../themes/osx/global/arrow/arrow-lft-hov.gif | Bin 0 -> 57 bytes .../osx/global/arrow/arrow-lft-sharp-end.gif | Bin 0 -> 56 bytes .../osx/global/arrow/arrow-lft-sharp.gif | Bin 0 -> 53 bytes toolkit/themes/osx/global/arrow/arrow-lft.gif | Bin 0 -> 57 bytes .../themes/osx/global/arrow/arrow-rit-dis.gif | Bin 0 -> 105 bytes .../themes/osx/global/arrow/arrow-rit-hov.gif | Bin 0 -> 57 bytes .../osx/global/arrow/arrow-rit-sharp-end.gif | Bin 0 -> 56 bytes .../osx/global/arrow/arrow-rit-sharp.gif | Bin 0 -> 53 bytes toolkit/themes/osx/global/arrow/arrow-rit.gif | Bin 0 -> 57 bytes .../themes/osx/global/arrow/arrow-up-dis.gif | Bin 0 -> 65 bytes .../osx/global/arrow/arrow-up-sharp.gif | Bin 0 -> 52 bytes toolkit/themes/osx/global/arrow/arrow-up.gif | Bin 0 -> 56 bytes .../global/arrow/panelarrow-horizontal.png | Bin 0 -> 117 bytes .../global/arrow/panelarrow-horizontal@2x.png | Bin 0 -> 267 bytes .../osx/global/arrow/panelarrow-vertical.png | Bin 0 -> 133 bytes .../global/arrow/panelarrow-vertical@2x.png | Bin 0 -> 227 bytes toolkit/themes/osx/global/autocomplete.css | 174 + toolkit/themes/osx/global/button.css | 85 + toolkit/themes/osx/global/checkbox.css | 39 + .../osx/global/checkbox/cbox-check-dis.gif | Bin 0 -> 60 bytes .../themes/osx/global/checkbox/cbox-check.gif | Bin 0 -> 54 bytes toolkit/themes/osx/global/colorpicker.css | 41 + toolkit/themes/osx/global/commonDialog.css | 35 + .../global/console/console-error-caret.gif | Bin 0 -> 55 bytes .../osx/global/console/console-error-dash.gif | Bin 0 -> 48 bytes toolkit/themes/osx/global/console/console.css | 165 + .../themes/osx/global/customizeToolbar.css | 38 + toolkit/themes/osx/global/datetimepicker.css | 126 + toolkit/themes/osx/global/dialog.css | 77 + .../osx/global/dirListing/dirListing.css | 104 + .../themes/osx/global/dirListing/folder.png | Bin 0 -> 325 bytes .../themes/osx/global/dirListing/remote.png | Bin 0 -> 563 bytes toolkit/themes/osx/global/dirListing/up.png | Bin 0 -> 617 bytes toolkit/themes/osx/global/dropmarker.css | 31 + toolkit/themes/osx/global/filefield.css | 38 + toolkit/themes/osx/global/filters.svg | 14 + toolkit/themes/osx/global/findBar.css | 270 + toolkit/themes/osx/global/global.css | 378 + toolkit/themes/osx/global/groupbox.css | 30 + toolkit/themes/osx/global/icons/Error.png | Bin 0 -> 1439 bytes .../global/icons/autocomplete-dropmarker.png | Bin 0 -> 234 bytes .../osx/global/icons/autocomplete-search.svg | 22 + .../themes/osx/global/icons/autoscroll.png | Bin 0 -> 2983 bytes .../themes/osx/global/icons/blacklist_64.png | Bin 0 -> 3771 bytes .../osx/global/icons/blacklist_favicon.png | Bin 0 -> 543 bytes toolkit/themes/osx/global/icons/checkbox.png | Bin 0 -> 1737 bytes .../themes/osx/global/icons/checkbox@2x.png | Bin 0 -> 1824 bytes .../osx/global/icons/chevron-inverted.png | Bin 0 -> 247 bytes .../osx/global/icons/chevron-inverted@2x.png | Bin 0 -> 481 bytes toolkit/themes/osx/global/icons/chevron.png | Bin 0 -> 251 bytes .../themes/osx/global/icons/chevron@2x.png | Bin 0 -> 462 bytes toolkit/themes/osx/global/icons/close.png | Bin 0 -> 1240 bytes toolkit/themes/osx/global/icons/close@2x.png | Bin 0 -> 2768 bytes toolkit/themes/osx/global/icons/error-16.png | Bin 0 -> 677 bytes toolkit/themes/osx/global/icons/error-64.png | Bin 0 -> 2533 bytes .../themes/osx/global/icons/error-large.png | Bin 0 -> 1996 bytes .../osx/global/icons/glyph-dropdown.png | Bin 0 -> 99 bytes .../osx/global/icons/glyph-dropdown@2x.png | Bin 0 -> 130 bytes .../osx/global/icons/information-16.png | Bin 0 -> 818 bytes .../osx/global/icons/information-24.png | Bin 0 -> 1289 bytes .../osx/global/icons/information-32.png | Bin 0 -> 1773 bytes .../osx/global/icons/information-64.png | Bin 0 -> 3687 bytes .../osx/global/icons/information-large.png | Bin 0 -> 2592 bytes .../themes/osx/global/icons/loading_16.png | Bin 0 -> 9025 bytes .../osx/global/icons/menulist-dropmarker.png | Bin 0 -> 158 bytes toolkit/themes/osx/global/icons/notfound.png | Bin 0 -> 597 bytes .../themes/osx/global/icons/notloading_16.png | Bin 0 -> 686 bytes .../osx/global/icons/panebutton-active.png | Bin 0 -> 400 bytes .../osx/global/icons/panebutton-inactive.png | Bin 0 -> 257 bytes .../osx/global/icons/panel-dropmarker.png | Bin 0 -> 161 bytes .../themes/osx/global/icons/question-16.png | Bin 0 -> 866 bytes .../themes/osx/global/icons/question-32.png | Bin 0 -> 1962 bytes .../themes/osx/global/icons/question-64.png | Bin 0 -> 3970 bytes .../osx/global/icons/question-large.png | Bin 0 -> 2851 bytes .../themes/osx/global/icons/resizer-rtl.png | Bin 0 -> 192 bytes .../osx/global/icons/resizer-rtl@2x.png | Bin 0 -> 284 bytes toolkit/themes/osx/global/icons/resizer.png | Bin 0 -> 196 bytes .../themes/osx/global/icons/resizer@2x.png | Bin 0 -> 288 bytes .../osx/global/icons/search-textbox.svg | 13 + .../osx/global/icons/searchfield-cancel.svg | 20 + .../themes/osx/global/icons/sslWarning.png | Bin 0 -> 4120 bytes .../osx/global/icons/tabprompts-bgtexture.png | Bin 0 -> 5940 bytes .../themes/osx/global/icons/warning-16.png | Bin 0 -> 690 bytes .../themes/osx/global/icons/warning-32.png | Bin 0 -> 1483 bytes .../themes/osx/global/icons/warning-64.png | Bin 0 -> 3308 bytes .../themes/osx/global/icons/warning-large.png | Bin 0 -> 2281 bytes .../themes/osx/global/in-content/common.css | 121 + .../osx/global/in-content/info-pages.css | 1 + toolkit/themes/osx/global/inContentUI.css | 144 + toolkit/themes/osx/global/jar.mn | 156 + toolkit/themes/osx/global/linkTree.css | 32 + toolkit/themes/osx/global/listbox.css | 113 + toolkit/themes/osx/global/menu.css | 187 + toolkit/themes/osx/global/menulist.css | 65 + toolkit/themes/osx/global/moz.build | 6 + .../themes/osx/global/nativescrollbars.css | 89 + toolkit/themes/osx/global/netError.css | 145 + toolkit/themes/osx/global/notification.css | 206 + .../themes/osx/global/notification/close.png | Bin 0 -> 795 bytes .../osx/global/notification/error-icon.png | Bin 0 -> 518 bytes .../osx/global/notification/info-icon.png | Bin 0 -> 533 bytes .../osx/global/notification/warning-icon.png | Bin 0 -> 626 bytes toolkit/themes/osx/global/numberbox.css | 33 + toolkit/themes/osx/global/popup.css | 141 + toolkit/themes/osx/global/preferences.css | 64 + toolkit/themes/osx/global/progressmeter.css | 22 + toolkit/themes/osx/global/radio.css | 43 + toolkit/themes/osx/global/resizer.css | 69 + toolkit/themes/osx/global/richlistbox.css | 27 + toolkit/themes/osx/global/scale.css | 46 + .../osx/global/scale/scale-tray-horiz.gif | Bin 0 -> 50 bytes .../osx/global/scale/scale-tray-vert.gif | Bin 0 -> 50 bytes toolkit/themes/osx/global/scrollbox.css | 62 + toolkit/themes/osx/global/shared.inc | 20 + toolkit/themes/osx/global/spinbuttons.css | 31 + toolkit/themes/osx/global/splitter.css | 124 + toolkit/themes/osx/global/splitter/dimple.png | Bin 0 -> 155 bytes .../osx/global/splitter/grip-bottom.gif | Bin 0 -> 145 bytes .../themes/osx/global/splitter/grip-left.gif | Bin 0 -> 157 bytes .../themes/osx/global/splitter/grip-right.gif | Bin 0 -> 157 bytes .../themes/osx/global/splitter/grip-top.gif | Bin 0 -> 144 bytes toolkit/themes/osx/global/tabbox.css | 148 + toolkit/themes/osx/global/tabprompts.css | 67 + toolkit/themes/osx/global/textbox.css | 102 + toolkit/themes/osx/global/toolbar.css | 127 + toolkit/themes/osx/global/toolbar/spring.png | Bin 0 -> 239 bytes .../osx/global/toolbar/toolbar-separator.png | Bin 0 -> 115 bytes toolkit/themes/osx/global/toolbarbutton.css | 124 + toolkit/themes/osx/global/tree.css | 296 + .../osx/global/tree/arrow-disclosure.svg | 28 + .../themes/osx/global/tree/columnpicker.gif | Bin 0 -> 68 bytes toolkit/themes/osx/global/tree/folder.png | Bin 0 -> 320 bytes toolkit/themes/osx/global/tree/folder@2x.png | Bin 0 -> 589 bytes toolkit/themes/osx/global/viewbuttons.css | 36 + toolkit/themes/osx/global/wizard.css | 62 + toolkit/themes/osx/mochitests/.eslintrc.js | 7 + toolkit/themes/osx/mochitests/chrome.ini | 3 + .../themes/osx/mochitests/test_bug510426.xul | 54 + toolkit/themes/osx/moz.build | 8 + .../themes/osx/mozapps/downloads/buttons.png | Bin 0 -> 2288 bytes .../osx/mozapps/downloads/downloadIcon.png | Bin 0 -> 1301 bytes .../osx/mozapps/downloads/downloads.css | 123 + .../mozapps/downloads/unknownContentType.css | 30 + .../themes/osx/mozapps/extensions/about.css | 78 + .../mozapps/extensions/alerticon-error.png | Bin 0 -> 3402 bytes .../extensions/alerticon-info-negative.png | Bin 0 -> 1564 bytes .../extensions/alerticon-info-positive.png | Bin 0 -> 1338 bytes .../mozapps/extensions/alerticon-warning.png | Bin 0 -> 1567 bytes .../osx/mozapps/extensions/blocklist.css | 20 + .../themes/osx/mozapps/extensions/cancel.png | Bin 0 -> 115 bytes .../mozapps/extensions/category-available.png | Bin 0 -> 1671 bytes .../extensions/category-dictionaries.png | Bin 0 -> 1769 bytes .../mozapps/extensions/category-discover.png | Bin 0 -> 1324 bytes .../extensions/category-experiments.png | Bin 0 -> 822 bytes .../mozapps/extensions/category-plugins.png | Bin 0 -> 886 bytes .../mozapps/extensions/category-recent.png | Bin 0 -> 1642 bytes .../mozapps/extensions/category-search.png | Bin 0 -> 2600 bytes .../extensions/category-searchengines.png | Bin 0 -> 2814 bytes .../mozapps/extensions/category-service.png | Bin 0 -> 2063 bytes .../extensions/dictionaryGeneric-16.png | Bin 0 -> 742 bytes .../mozapps/extensions/dictionaryGeneric.png | Bin 0 -> 1769 bytes .../osx/mozapps/extensions/discover-logo.png | Bin 0 -> 12007 bytes .../themes/osx/mozapps/extensions/eula.css | 47 + .../mozapps/extensions/experimentGeneric.png | Bin 0 -> 822 bytes .../extensions/extensionGeneric-16.png | Bin 0 -> 554 bytes .../mozapps/extensions/extensionGeneric.png | Bin 0 -> 1302 bytes .../osx/mozapps/extensions/extensions.css | 1206 ++++ .../themes/osx/mozapps/extensions/heart.png | Bin 0 -> 2949 bytes .../osx/mozapps/extensions/localeGeneric.png | Bin 0 -> 2410 bytes .../osx/mozapps/extensions/navigation.png | Bin 0 -> 586 bytes .../osx/mozapps/extensions/newaddon.css | 112 + .../osx/mozapps/extensions/rating-not-won.png | Bin 0 -> 1559 bytes .../osx/mozapps/extensions/rating-won.png | Bin 0 -> 1662 bytes .../themes/osx/mozapps/extensions/search.png | Bin 0 -> 423 bytes .../osx/mozapps/extensions/selectAddons.css | 163 + .../extensions/stripes-compatibility.png | Bin 0 -> 1041 bytes .../osx/mozapps/extensions/stripes-error.png | Bin 0 -> 1979 bytes .../extensions/stripes-info-negative.png | Bin 0 -> 2027 bytes .../extensions/stripes-info-positive.png | Bin 0 -> 1852 bytes .../mozapps/extensions/stripes-warning.png | Bin 0 -> 2177 bytes .../mozapps/extensions/themeGeneric-16.png | Bin 0 -> 710 bytes .../osx/mozapps/extensions/themeGeneric.png | Bin 0 -> 2185 bytes .../extensions/toolbarbutton-dropmarker.png | Bin 0 -> 147 bytes .../themes/osx/mozapps/extensions/update.css | 26 + .../mozapps/extensions/xpinstallConfirm.css | 90 + .../themes/osx/mozapps/handling/handling.css | 30 + toolkit/themes/osx/mozapps/jar.mn | 79 + toolkit/themes/osx/mozapps/moz.build | 6 + .../themes/osx/mozapps/passwordmgr/key-16.png | Bin 0 -> 773 bytes .../themes/osx/mozapps/passwordmgr/key-64.png | Bin 0 -> 6142 bytes .../themes/osx/mozapps/passwordmgr/key.png | Bin 0 -> 658 bytes .../mozapps/plugins/notifyPluginGeneric.png | Bin 0 -> 313 bytes .../osx/mozapps/plugins/pluginBlocked-64.png | Bin 0 -> 4563 bytes .../osx/mozapps/plugins/pluginBlocked.png | Bin 0 -> 2152 bytes .../osx/mozapps/plugins/pluginGeneric-16.png | Bin 0 -> 759 bytes .../osx/mozapps/plugins/pluginGeneric.png | Bin 0 -> 1939 bytes .../osx/mozapps/plugins/pluginHelp-16.png | Bin 0 -> 620 bytes .../osx/mozapps/profile/profileSelection.css | 29 + .../mozapps/profile/profileicon-selected.png | Bin 0 -> 502 bytes .../osx/mozapps/profile/profileicon.png | Bin 0 -> 588 bytes toolkit/themes/osx/mozapps/update/buttons.png | Bin 0 -> 2288 bytes toolkit/themes/osx/mozapps/update/updates.css | 171 + .../osx/mozapps/viewsource/viewsource.css | 5 + toolkit/themes/osx/reftests/482681-ref.xul | 21 + toolkit/themes/osx/reftests/482681.xul | 22 + toolkit/themes/osx/reftests/baseline.xul | 175 + .../themes/osx/reftests/checkboxsize-ref.xul | 32 + toolkit/themes/osx/reftests/checkboxsize.xul | 31 + toolkit/themes/osx/reftests/nostretch-ref.xul | 107 + toolkit/themes/osx/reftests/nostretch.xul | 120 + toolkit/themes/osx/reftests/radiosize-ref.xul | 32 + toolkit/themes/osx/reftests/radiosize.xul | 31 + .../themes/osx/reftests/reftest-stylo.list | 6 + toolkit/themes/osx/reftests/reftest.list | 5 + toolkit/themes/shared/aboutReader.css | 14 +- toolkit/themes/shared/jar.inc.mn | 2 +- toolkit/themes/shared/media/videocontrols.css | 12 + toolkit/xre/MacApplicationDelegate.h | 16 + toolkit/xre/MacApplicationDelegate.mm | 396 ++ toolkit/xre/MacAutoreleasePool.h | 31 + toolkit/xre/MacAutoreleasePool.mm | 20 + toolkit/xre/MacLaunchHelper.h | 23 + toolkit/xre/MacLaunchHelper.mm | 137 + toolkit/xre/moz.build | 15 + toolkit/xre/nsAppRunner.cpp | 100 + toolkit/xre/nsEmbedFunctions.cpp | 140 + toolkit/xre/nsSigHandlers.cpp | 16 + toolkit/xre/nsUpdateDriver.cpp | 143 +- toolkit/xre/nsXREDirProvider.cpp | 153 +- toolkit/xre/nsXREDirProvider.h | 2 +- .../libfuzzer/harness/LibFuzzerTestHarness.h | 8 + tools/profiler/tasktracer/GeckoTaskTracer.cpp | 5 + .../exthandler/mac/nsDecodeAppleFile.cpp | 389 ++ uriloader/exthandler/mac/nsDecodeAppleFile.h | 118 + .../exthandler/mac/nsLocalHandlerAppMac.h | 26 + .../exthandler/mac/nsLocalHandlerAppMac.mm | 84 + uriloader/exthandler/mac/nsMIMEInfoMac.h | 34 + uriloader/exthandler/mac/nsMIMEInfoMac.mm | 114 + .../exthandler/mac/nsOSHelperAppService.h | 48 + .../exthandler/mac/nsOSHelperAppService.mm | 569 ++ uriloader/exthandler/moz.build | 10 +- .../exthandler/nsExternalHelperAppService.cpp | 58 +- uriloader/exthandler/nsLocalHandlerApp.h | 9 +- view/nsView.cpp | 4 +- view/nsViewManager.cpp | 6 +- widget/CompositorWidget.h | 6 +- widget/GfxInfoBase.cpp | 8 + widget/NativeKeyToDOMCodeName.h | 6 + widget/NativeKeyToDOMKeyName.h | 4 + widget/TextEvents.h | 31 + widget/WidgetEventImpl.cpp | 4 + widget/cocoa/ComplexTextInputPanel.h | 48 + widget/cocoa/ComplexTextInputPanel.mm | 261 + widget/cocoa/CustomCocoaEvents.h | 18 + widget/cocoa/GfxInfo.h | 95 + widget/cocoa/GfxInfo.mm | 426 ++ widget/cocoa/NativeKeyBindings.h | 48 + widget/cocoa/NativeKeyBindings.mm | 292 + widget/cocoa/OSXNotificationCenter.h | 55 + widget/cocoa/OSXNotificationCenter.mm | 589 ++ widget/cocoa/RectTextureImage.h | 80 + widget/cocoa/RectTextureImage.mm | 171 + widget/cocoa/SwipeTracker.h | 95 + widget/cocoa/SwipeTracker.mm | 219 + widget/cocoa/TextInputHandler.h | 1194 ++++ widget/cocoa/TextInputHandler.mm | 4533 ++++++++++++ widget/cocoa/VibrancyManager.h | 120 + widget/cocoa/VibrancyManager.mm | 244 + widget/cocoa/ViewRegion.h | 52 + widget/cocoa/ViewRegion.mm | 70 + widget/cocoa/WidgetTraceEvent.mm | 85 + widget/cocoa/crashtests/373122-1-inner.html | 39 + widget/cocoa/crashtests/373122-1.html | 9 + widget/cocoa/crashtests/397209-1.html | 7 + widget/cocoa/crashtests/403296-1.xhtml | 10 + widget/cocoa/crashtests/419737-1.html | 8 + widget/cocoa/crashtests/435223-1.html | 8 + widget/cocoa/crashtests/444260-1.xul | 3 + widget/cocoa/crashtests/444864-1.html | 6 + widget/cocoa/crashtests/449111-1.html | 4 + widget/cocoa/crashtests/460349-1.xhtml | 4 + widget/cocoa/crashtests/460387-1.html | 2 + widget/cocoa/crashtests/464589-1.html | 20 + widget/cocoa/crashtests/crashtests.list | 11 + widget/cocoa/cursors/arrowN.png | Bin 0 -> 256 bytes widget/cocoa/cursors/arrowN@2x.png | Bin 0 -> 655 bytes widget/cocoa/cursors/arrowS.png | Bin 0 -> 256 bytes widget/cocoa/cursors/arrowS@2x.png | Bin 0 -> 649 bytes widget/cocoa/cursors/cell.png | Bin 0 -> 268 bytes widget/cocoa/cursors/cell@2x.png | Bin 0 -> 647 bytes widget/cocoa/cursors/colResize.png | Bin 0 -> 321 bytes widget/cocoa/cursors/colResize@2x.png | Bin 0 -> 836 bytes widget/cocoa/cursors/help.png | Bin 0 -> 723 bytes widget/cocoa/cursors/help@2x.png | Bin 0 -> 1693 bytes widget/cocoa/cursors/move.png | Bin 0 -> 284 bytes widget/cocoa/cursors/move@2x.png | Bin 0 -> 621 bytes widget/cocoa/cursors/rowResize.png | Bin 0 -> 331 bytes widget/cocoa/cursors/rowResize@2x.png | Bin 0 -> 852 bytes widget/cocoa/cursors/sizeNE.png | Bin 0 -> 279 bytes widget/cocoa/cursors/sizeNE@2x.png | Bin 0 -> 794 bytes widget/cocoa/cursors/sizeNESW.png | Bin 0 -> 300 bytes widget/cocoa/cursors/sizeNESW@2x.png | Bin 0 -> 975 bytes widget/cocoa/cursors/sizeNS.png | Bin 0 -> 282 bytes widget/cocoa/cursors/sizeNS@2x.png | Bin 0 -> 660 bytes widget/cocoa/cursors/sizeNW.png | Bin 0 -> 278 bytes widget/cocoa/cursors/sizeNW@2x.png | Bin 0 -> 790 bytes widget/cocoa/cursors/sizeNWSE.png | Bin 0 -> 295 bytes widget/cocoa/cursors/sizeNWSE@2x.png | Bin 0 -> 976 bytes widget/cocoa/cursors/sizeSE.png | Bin 0 -> 270 bytes widget/cocoa/cursors/sizeSE@2x.png | Bin 0 -> 802 bytes widget/cocoa/cursors/sizeSW.png | Bin 0 -> 271 bytes widget/cocoa/cursors/sizeSW@2x.png | Bin 0 -> 806 bytes widget/cocoa/cursors/vtIBeam.png | Bin 0 -> 120 bytes widget/cocoa/cursors/vtIBeam@2x.png | Bin 0 -> 336 bytes widget/cocoa/cursors/zoomIn.png | Bin 0 -> 655 bytes widget/cocoa/cursors/zoomIn@2x.png | Bin 0 -> 1717 bytes widget/cocoa/cursors/zoomOut.png | Bin 0 -> 650 bytes widget/cocoa/cursors/zoomOut@2x.png | Bin 0 -> 1714 bytes widget/cocoa/moz.build | 140 + widget/cocoa/mozView.h | 62 + widget/cocoa/nsAppShell.h | 71 + widget/cocoa/nsAppShell.mm | 907 +++ widget/cocoa/nsBidiKeyboard.h | 24 + widget/cocoa/nsBidiKeyboard.mm | 42 + widget/cocoa/nsChangeObserver.h | 44 + widget/cocoa/nsChildView.h | 666 ++ widget/cocoa/nsChildView.mm | 6151 +++++++++++++++++ widget/cocoa/nsClipboard.h | 55 + widget/cocoa/nsClipboard.mm | 775 +++ widget/cocoa/nsCocoaDebugUtils.h | 136 + widget/cocoa/nsCocoaDebugUtils.mm | 284 + widget/cocoa/nsCocoaFeatures.h | 46 + widget/cocoa/nsCocoaFeatures.mm | 209 + widget/cocoa/nsCocoaUtils.h | 389 ++ widget/cocoa/nsCocoaUtils.mm | 1022 +++ widget/cocoa/nsCocoaWindow.h | 426 ++ widget/cocoa/nsCocoaWindow.mm | 3881 +++++++++++ widget/cocoa/nsColorPicker.h | 50 + widget/cocoa/nsColorPicker.mm | 188 + widget/cocoa/nsCursorManager.h | 65 + widget/cocoa/nsCursorManager.mm | 308 + widget/cocoa/nsDeviceContextSpecX.h | 41 + widget/cocoa/nsDeviceContextSpecX.mm | 165 + widget/cocoa/nsDragService.h | 55 + widget/cocoa/nsDragService.mm | 669 ++ widget/cocoa/nsFilePicker.h | 74 + widget/cocoa/nsFilePicker.mm | 676 ++ widget/cocoa/nsIdleServiceX.h | 33 + widget/cocoa/nsIdleServiceX.mm | 77 + widget/cocoa/nsLookAndFeel.h | 46 + widget/cocoa/nsLookAndFeel.mm | 584 ++ widget/cocoa/nsMacCursor.h | 105 + widget/cocoa/nsMacCursor.mm | 382 + widget/cocoa/nsMacDockSupport.h | 41 + widget/cocoa/nsMacDockSupport.mm | 174 + widget/cocoa/nsMacWebAppUtils.h | 22 + widget/cocoa/nsMacWebAppUtils.mm | 82 + widget/cocoa/nsMenuBarX.h | 128 + widget/cocoa/nsMenuBarX.mm | 979 +++ widget/cocoa/nsMenuBaseX.h | 79 + widget/cocoa/nsMenuGroupOwnerX.h | 61 + widget/cocoa/nsMenuGroupOwnerX.mm | 261 + widget/cocoa/nsMenuItemIconX.h | 66 + widget/cocoa/nsMenuItemIconX.mm | 466 ++ widget/cocoa/nsMenuItemX.h | 75 + widget/cocoa/nsMenuItemX.mm | 369 + widget/cocoa/nsMenuUtilsX.h | 31 + widget/cocoa/nsMenuUtilsX.mm | 223 + widget/cocoa/nsMenuX.h | 101 + widget/cocoa/nsMenuX.mm | 1051 +++ widget/cocoa/nsNativeThemeCocoa.h | 178 + widget/cocoa/nsNativeThemeCocoa.mm | 3961 +++++++++++ widget/cocoa/nsNativeThemeColors.h | 57 + widget/cocoa/nsPIWidgetCocoa.idl | 37 + widget/cocoa/nsPrintDialogX.h | 68 + widget/cocoa/nsPrintDialogX.mm | 682 ++ widget/cocoa/nsPrintOptionsX.h | 44 + widget/cocoa/nsPrintOptionsX.mm | 349 + widget/cocoa/nsPrintSettingsX.h | 82 + widget/cocoa/nsPrintSettingsX.mm | 272 + widget/cocoa/nsSandboxViolationSink.h | 36 + widget/cocoa/nsSandboxViolationSink.mm | 115 + widget/cocoa/nsScreenCocoa.h | 41 + widget/cocoa/nsScreenCocoa.mm | 147 + widget/cocoa/nsScreenManagerCocoa.h | 33 + widget/cocoa/nsScreenManagerCocoa.mm | 152 + widget/cocoa/nsSound.h | 27 + widget/cocoa/nsSound.mm | 108 + widget/cocoa/nsStandaloneNativeMenu.h | 40 + widget/cocoa/nsStandaloneNativeMenu.mm | 213 + widget/cocoa/nsSystemStatusBarCocoa.h | 40 + widget/cocoa/nsSystemStatusBarCocoa.mm | 74 + widget/cocoa/nsToolkit.h | 53 + widget/cocoa/nsToolkit.mm | 326 + widget/cocoa/nsWidgetFactory.mm | 219 + widget/cocoa/nsWindowMap.h | 62 + widget/cocoa/nsWindowMap.mm | 311 + .../cocoa/resources/MainMenu.nib/classes.nib | 4 + widget/cocoa/resources/MainMenu.nib/info.nib | 21 + .../resources/MainMenu.nib/keyedobjects.nib | Bin 0 -> 1877 bytes widget/moz.build | 22 +- widget/nsBaseWidget.cpp | 23 +- widget/nsBaseWidget.h | 2 +- widget/nsGUIEventIPC.h | 14 + widget/nsIMacDockSupport.idl | 39 + widget/nsIMacWebAppUtils.idl | 35 + widget/nsISystemStatusBar.idl | 36 + widget/nsIWidget.h | 4 + widget/nsNativeTheme.cpp | 14 +- widget/uikit/GfxInfo.cpp | 222 + widget/uikit/GfxInfo.h | 78 + widget/uikit/moz.build | 18 + widget/uikit/nsAppShell.h | 57 + widget/uikit/nsAppShell.mm | 271 + widget/uikit/nsLookAndFeel.h | 35 + widget/uikit/nsLookAndFeel.mm | 401 ++ widget/uikit/nsScreenManager.h | 60 + widget/uikit/nsScreenManager.mm | 146 + widget/uikit/nsWidgetFactory.mm | 71 + widget/uikit/nsWindow.h | 125 + widget/uikit/nsWindow.mm | 862 +++ xpcom/base/MacHelpers.h | 17 + xpcom/base/MacHelpers.mm | 40 + xpcom/base/moz.build | 17 +- xpcom/base/nsDebugImpl.cpp | 16 +- xpcom/base/nsIMacUtils.idl | 32 + xpcom/base/nsMacUtilsImpl.cpp | 146 + xpcom/base/nsMacUtilsImpl.h | 41 + xpcom/base/nsMemoryReporterManager.cpp | 166 +- xpcom/base/nsSystemInfo.cpp | 77 + xpcom/base/nsUUIDGenerator.cpp | 18 +- xpcom/base/nsUUIDGenerator.h | 2 +- xpcom/build/BinaryPath.h | 42 + xpcom/build/PoisonIOInterposer.h | 19 +- xpcom/build/PoisonIOInterposerMac.cpp | 385 ++ xpcom/build/XPCOMInit.cpp | 3 + xpcom/build/mach_override.c | 789 +++ xpcom/build/mach_override.h | 121 + xpcom/build/moz.build | 10 + xpcom/build/nsXPCOMPrivate.h | 7 + xpcom/build/nsXULAppAPI.h | 2 +- xpcom/components/nsNativeModuleLoader.cpp | 4 + xpcom/ds/nsMathUtils.h | 4 + xpcom/glue/FileUtils.cpp | 153 +- xpcom/glue/nsCRTGlue.h | 4 +- xpcom/glue/nsThreadUtils.cpp | 12 + xpcom/glue/nsThreadUtils.h | 3 + xpcom/glue/standalone/nsXPCOMGlue.cpp | 38 +- xpcom/io/CocoaFileUtils.h | 34 + xpcom/io/CocoaFileUtils.mm | 267 + xpcom/io/moz.build | 10 + xpcom/io/nsILocalFileMac.idl | 179 + xpcom/io/nsLocalFileUnix.h | 4 +- .../md/unix/xptcinvoke_asm_x86_64_unix.S | 15 + .../md/unix/xptcinvoke_gcc_x86_unix.cpp | 4 + .../reflect/xptcall/md/unix/xptcstubs_arm.cpp | 4 +- .../md/unix/xptcstubs_gcc_x86_unix.cpp | 13 + xpcom/threads/SharedThreadPool.h | 2 +- xpcom/threads/nsProcess.h | 4 +- xpcom/threads/nsProcessCommon.cpp | 71 + xpcom/threads/nsThread.cpp | 15 + xpfe/appshell/nsAppShellService.cpp | 43 + xpfe/appshell/nsContentTreeOwner.cpp | 37 + xpfe/appshell/nsWebShellWindow.cpp | 2 +- xpfe/appshell/nsXULWindow.cpp | 3 +- 949 files changed, 95662 insertions(+), 444 deletions(-) create mode 100644 accessible/mac/ARIAGridAccessibleWrap.h create mode 100644 accessible/mac/AccessibleWrap.h create mode 100644 accessible/mac/AccessibleWrap.mm create mode 100644 accessible/mac/ApplicationAccessibleWrap.h create mode 100644 accessible/mac/DocAccessibleWrap.h create mode 100644 accessible/mac/DocAccessibleWrap.mm create mode 100644 accessible/mac/HTMLTableAccessibleWrap.h create mode 100644 accessible/mac/HyperTextAccessibleWrap.h create mode 100644 accessible/mac/ImageAccessibleWrap.h create mode 100644 accessible/mac/MacUtils.h create mode 100644 accessible/mac/MacUtils.mm create mode 100644 accessible/mac/Platform.mm create mode 100644 accessible/mac/RootAccessibleWrap.h create mode 100644 accessible/mac/RootAccessibleWrap.mm create mode 100644 accessible/mac/TextLeafAccessibleWrap.h create mode 100644 accessible/mac/XULListboxAccessibleWrap.h create mode 100644 accessible/mac/XULMenuAccessibleWrap.h create mode 100644 accessible/mac/XULTreeGridAccessibleWrap.h create mode 100644 accessible/mac/moz.build create mode 100644 accessible/mac/mozAccessible.h create mode 100644 accessible/mac/mozAccessible.mm create mode 100644 accessible/mac/mozAccessibleProtocol.h create mode 100644 accessible/mac/mozActionElements.h create mode 100644 accessible/mac/mozActionElements.mm create mode 100644 accessible/mac/mozDocAccessible.h create mode 100644 accessible/mac/mozDocAccessible.mm create mode 100644 accessible/mac/mozHTMLAccessible.h create mode 100644 accessible/mac/mozHTMLAccessible.mm create mode 100644 accessible/mac/mozTableAccessible.h create mode 100644 accessible/mac/mozTableAccessible.mm create mode 100644 accessible/mac/mozTextAccessible.h create mode 100644 accessible/mac/mozTextAccessible.mm create mode 100644 build/macosx/build-cctools.sh create mode 100644 build/macosx/cross-mozconfig.common create mode 100644 build/macosx/local-mozconfig.common create mode 100644 build/macosx/mozconfig.common create mode 100644 build/macosx/permissions/chown_revert.c create mode 100644 build/macosx/permissions/chown_root.c create mode 100644 build/macosx/universal/mozconfig create mode 100644 build/macosx/universal/mozconfig.common create mode 100644 build/macosx/universal/unify create mode 100644 dom/gamepad/cocoa/CocoaGamepad.cpp create mode 100644 dom/media/systemservices/OSXRunLoopSingleton.cpp create mode 100644 dom/media/systemservices/OSXRunLoopSingleton.h create mode 100644 dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerModule.cpp create mode 100644 dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.h create mode 100644 dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm create mode 100644 dom/media/webspeech/synth/cocoa/moz.build create mode 100644 dom/plugins/base/nsPluginsDirDarwin.cpp create mode 100644 dom/plugins/ipc/NPEventOSX.h create mode 100644 dom/plugins/ipc/PluginInterposeOSX.h create mode 100644 dom/plugins/ipc/PluginInterposeOSX.mm create mode 100644 dom/plugins/ipc/PluginUtilsOSX.h create mode 100644 dom/plugins/ipc/PluginUtilsOSX.mm create mode 100644 dom/plugins/ipc/interpose/moz.build create mode 100644 dom/plugins/ipc/interpose/plugin_child_interpose.mm create mode 100644 dom/system/mac/CoreLocationLocationProvider.h create mode 100644 dom/system/mac/CoreLocationLocationProvider.mm create mode 100644 dom/system/mac/moz.build create mode 100644 dom/xbl/builtin/mac/jar.mn create mode 100644 dom/xbl/builtin/mac/moz.build create mode 100644 dom/xbl/builtin/mac/platformHTMLBindings.xml create mode 100644 embedding/components/printingui/mac/moz.build create mode 100644 embedding/components/printingui/mac/nsPrintProgress.cpp create mode 100644 embedding/components/printingui/mac/nsPrintProgress.h create mode 100644 embedding/components/printingui/mac/nsPrintProgressParams.cpp create mode 100644 embedding/components/printingui/mac/nsPrintProgressParams.h create mode 100644 embedding/components/printingui/mac/nsPrintingPromptService.h create mode 100644 embedding/components/printingui/mac/nsPrintingPromptServiceX.mm create mode 100644 gfx/2d/MacIOSurface.cpp create mode 100644 gfx/2d/MacIOSurface.h create mode 100644 gfx/2d/NativeFontResourceMac.cpp create mode 100644 gfx/2d/NativeFontResourceMac.h create mode 100644 gfx/2d/PathCG.cpp create mode 100644 gfx/2d/PathCG.h create mode 100644 gfx/2d/QuartzSupport.h create mode 100644 gfx/2d/QuartzSupport.mm create mode 100644 gfx/2d/ScaledFontMac.cpp create mode 100644 gfx/2d/ScaledFontMac.h create mode 100644 gfx/gl/ForceDiscreteGPUHelperCGL.h create mode 100644 gfx/gl/GLContextCGL.h create mode 100644 gfx/gl/GLContextEAGL.h create mode 100644 gfx/gl/GLContextProviderCGL.mm create mode 100644 gfx/gl/GLContextProviderEAGL.mm create mode 100644 gfx/gl/SharedSurfaceIO.cpp create mode 100644 gfx/gl/SharedSurfaceIO.h create mode 100644 gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp create mode 100644 gfx/layers/basic/MacIOSurfaceTextureHostBasic.h create mode 100644 gfx/layers/ipc/ShadowLayerUtilsMac.cpp create mode 100644 gfx/layers/opengl/GLManager.cpp create mode 100644 gfx/layers/opengl/GLManager.h create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h create mode 100644 gfx/thebes/PrintTargetCG.cpp create mode 100644 gfx/thebes/PrintTargetCG.h create mode 100644 gfx/thebes/gfxCoreTextShaper.cpp create mode 100644 gfx/thebes/gfxCoreTextShaper.h create mode 100644 gfx/thebes/gfxMacFont.cpp create mode 100644 gfx/thebes/gfxMacFont.h create mode 100644 gfx/thebes/gfxMacPlatformFontList.h create mode 100644 gfx/thebes/gfxMacPlatformFontList.mm create mode 100644 gfx/thebes/gfxPlatformMac.cpp create mode 100644 gfx/thebes/gfxPlatformMac.h create mode 100644 gfx/thebes/gfxQuartzNativeDrawing.cpp create mode 100644 gfx/thebes/gfxQuartzNativeDrawing.h create mode 100644 gfx/thebes/gfxQuartzSurface.cpp create mode 100644 gfx/thebes/gfxQuartzSurface.h rename gfx/ycbcr/{convert.patch.outdated => convert.patch} (100%) create mode 100644 hal/cocoa/CocoaSensor.mm create mode 100644 hal/cocoa/smslib.h create mode 100644 hal/cocoa/smslib.mm create mode 100644 image/decoders/icon/mac/moz.build create mode 100644 image/decoders/icon/mac/nsIconChannel.h create mode 100644 image/decoders/icon/mac/nsIconChannelCocoa.mm create mode 100644 intl/locale/mac/moz.build create mode 100644 intl/locale/mac/nsCollationMacUC.cpp create mode 100644 intl/locale/mac/nsCollationMacUC.h create mode 100644 intl/locale/mac/nsDateTimeFormatMac.cpp create mode 100644 intl/locale/mac/nsDateTimeFormatMac.h create mode 100644 intl/locale/mac/nsMacCharset.cpp create mode 100644 intl/lwbrk/nsCarbonBreaker.cpp create mode 100644 ipc/glue/SharedMemoryBasic_mach.h create mode 100644 ipc/glue/SharedMemoryBasic_mach.mm create mode 100644 js/xpconnect/shell/xpcshellMacUtils.h create mode 100644 js/xpconnect/shell/xpcshellMacUtils.mm create mode 100644 mailnews/base/src/nsMessengerOSXIntegration.h create mode 100644 mailnews/base/src/nsMessengerOSXIntegration.mm create mode 100644 mailnews/compose/src/nsMsgAppleCodes.h create mode 100644 mailnews/compose/src/nsMsgAppleDouble.h create mode 100644 mailnews/compose/src/nsMsgAppleDoubleEncode.cpp create mode 100644 mailnews/compose/src/nsMsgAppleEncode.cpp create mode 100644 mailnews/import/applemail/src/moz.build create mode 100644 mailnews/import/applemail/src/nsAppleMailImport.cpp create mode 100644 mailnews/import/applemail/src/nsAppleMailImport.h create mode 100644 mailnews/import/applemail/src/nsEmlxHelperUtils.h create mode 100644 mailnews/import/applemail/src/nsEmlxHelperUtils.mm create mode 100644 media/ffvpx/config_darwin64.asm create mode 100644 media/ffvpx/config_darwin64.h create mode 100644 media/libav/config_darwin.asm create mode 100644 media/libav/config_darwin.h create mode 100644 media/libcubeb/src/cubeb_osx_run_loop.c create mode 100644 media/libcubeb/src/cubeb_osx_run_loop.h create mode 100644 netwerk/base/NetworkInfoServiceCocoa.cpp create mode 100644 netwerk/base/nsURLHelperOSX.cpp create mode 100644 netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp create mode 100644 netwerk/dns/mdns/libmdns/MDNSResponderOperator.h create mode 100644 netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp create mode 100644 netwerk/dns/mdns/libmdns/MDNSResponderReply.h create mode 100644 netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp create mode 100644 netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h create mode 100644 netwerk/system/mac/moz.build create mode 100644 netwerk/system/mac/nsNetworkLinkService.h create mode 100644 netwerk/system/mac/nsNetworkLinkService.mm create mode 100644 toolkit/components/parentalcontrols/nsParentalControlsServiceCocoa.mm create mode 100644 toolkit/components/startup/nsUserInfoMac.h create mode 100644 toolkit/components/startup/nsUserInfoMac.mm create mode 100644 toolkit/content/macWindowMenu.inc create mode 100644 toolkit/content/macWindowMenu.js create mode 100644 toolkit/mozapps/update/updater/launchchild_osx.mm create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Info.plist.in create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/PkgInfo create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/classes.nib create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/info.nib create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib create mode 100644 toolkit/mozapps/update/updater/macbuild/Contents/Resources/updater.icns create mode 100644 toolkit/mozapps/update/updater/progressui_osx.mm create mode 100644 toolkit/system/osxproxy/ProxyUtils.h create mode 100644 toolkit/system/osxproxy/ProxyUtils.mm create mode 100644 toolkit/system/osxproxy/moz.build create mode 100644 toolkit/system/osxproxy/nsOSXSystemProxySettings.mm create mode 100644 toolkit/system/osxproxy/tests/gtest/TestProxyBypassRules.cpp create mode 100644 toolkit/system/osxproxy/tests/gtest/moz.build create mode 100644 toolkit/themes/osx/global/10pct_transparent_grey.png create mode 100644 toolkit/themes/osx/global/50pct_transparent_grey.png create mode 100644 toolkit/themes/osx/global/alerts/alert.css create mode 100644 toolkit/themes/osx/global/arrow.css create mode 100644 toolkit/themes/osx/global/arrow/arrow-dn-dis.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-dn-dis.png create mode 100644 toolkit/themes/osx/global/arrow/arrow-dn-sharp.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-dn.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-dn.png create mode 100644 toolkit/themes/osx/global/arrow/arrow-lft-dis.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-lft-hov.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-lft-sharp-end.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-lft-sharp.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-lft.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-rit-dis.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-rit-hov.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-rit-sharp-end.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-rit-sharp.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-rit.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-up-dis.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-up-sharp.gif create mode 100644 toolkit/themes/osx/global/arrow/arrow-up.gif create mode 100644 toolkit/themes/osx/global/arrow/panelarrow-horizontal.png create mode 100644 toolkit/themes/osx/global/arrow/panelarrow-horizontal@2x.png create mode 100644 toolkit/themes/osx/global/arrow/panelarrow-vertical.png create mode 100644 toolkit/themes/osx/global/arrow/panelarrow-vertical@2x.png create mode 100644 toolkit/themes/osx/global/autocomplete.css create mode 100644 toolkit/themes/osx/global/button.css create mode 100644 toolkit/themes/osx/global/checkbox.css create mode 100644 toolkit/themes/osx/global/checkbox/cbox-check-dis.gif create mode 100644 toolkit/themes/osx/global/checkbox/cbox-check.gif create mode 100644 toolkit/themes/osx/global/colorpicker.css create mode 100644 toolkit/themes/osx/global/commonDialog.css create mode 100644 toolkit/themes/osx/global/console/console-error-caret.gif create mode 100644 toolkit/themes/osx/global/console/console-error-dash.gif create mode 100644 toolkit/themes/osx/global/console/console.css create mode 100644 toolkit/themes/osx/global/customizeToolbar.css create mode 100644 toolkit/themes/osx/global/datetimepicker.css create mode 100644 toolkit/themes/osx/global/dialog.css create mode 100644 toolkit/themes/osx/global/dirListing/dirListing.css create mode 100644 toolkit/themes/osx/global/dirListing/folder.png create mode 100644 toolkit/themes/osx/global/dirListing/remote.png create mode 100644 toolkit/themes/osx/global/dirListing/up.png create mode 100644 toolkit/themes/osx/global/dropmarker.css create mode 100644 toolkit/themes/osx/global/filefield.css create mode 100644 toolkit/themes/osx/global/filters.svg create mode 100644 toolkit/themes/osx/global/findBar.css create mode 100644 toolkit/themes/osx/global/global.css create mode 100644 toolkit/themes/osx/global/groupbox.css create mode 100644 toolkit/themes/osx/global/icons/Error.png create mode 100644 toolkit/themes/osx/global/icons/autocomplete-dropmarker.png create mode 100644 toolkit/themes/osx/global/icons/autocomplete-search.svg create mode 100644 toolkit/themes/osx/global/icons/autoscroll.png create mode 100644 toolkit/themes/osx/global/icons/blacklist_64.png create mode 100644 toolkit/themes/osx/global/icons/blacklist_favicon.png create mode 100644 toolkit/themes/osx/global/icons/checkbox.png create mode 100644 toolkit/themes/osx/global/icons/checkbox@2x.png create mode 100644 toolkit/themes/osx/global/icons/chevron-inverted.png create mode 100644 toolkit/themes/osx/global/icons/chevron-inverted@2x.png create mode 100644 toolkit/themes/osx/global/icons/chevron.png create mode 100644 toolkit/themes/osx/global/icons/chevron@2x.png create mode 100644 toolkit/themes/osx/global/icons/close.png create mode 100644 toolkit/themes/osx/global/icons/close@2x.png create mode 100644 toolkit/themes/osx/global/icons/error-16.png create mode 100644 toolkit/themes/osx/global/icons/error-64.png create mode 100644 toolkit/themes/osx/global/icons/error-large.png create mode 100644 toolkit/themes/osx/global/icons/glyph-dropdown.png create mode 100644 toolkit/themes/osx/global/icons/glyph-dropdown@2x.png create mode 100644 toolkit/themes/osx/global/icons/information-16.png create mode 100644 toolkit/themes/osx/global/icons/information-24.png create mode 100644 toolkit/themes/osx/global/icons/information-32.png create mode 100644 toolkit/themes/osx/global/icons/information-64.png create mode 100644 toolkit/themes/osx/global/icons/information-large.png create mode 100644 toolkit/themes/osx/global/icons/loading_16.png create mode 100644 toolkit/themes/osx/global/icons/menulist-dropmarker.png create mode 100644 toolkit/themes/osx/global/icons/notfound.png create mode 100644 toolkit/themes/osx/global/icons/notloading_16.png create mode 100644 toolkit/themes/osx/global/icons/panebutton-active.png create mode 100644 toolkit/themes/osx/global/icons/panebutton-inactive.png create mode 100644 toolkit/themes/osx/global/icons/panel-dropmarker.png create mode 100644 toolkit/themes/osx/global/icons/question-16.png create mode 100644 toolkit/themes/osx/global/icons/question-32.png create mode 100644 toolkit/themes/osx/global/icons/question-64.png create mode 100644 toolkit/themes/osx/global/icons/question-large.png create mode 100644 toolkit/themes/osx/global/icons/resizer-rtl.png create mode 100644 toolkit/themes/osx/global/icons/resizer-rtl@2x.png create mode 100644 toolkit/themes/osx/global/icons/resizer.png create mode 100644 toolkit/themes/osx/global/icons/resizer@2x.png create mode 100644 toolkit/themes/osx/global/icons/search-textbox.svg create mode 100644 toolkit/themes/osx/global/icons/searchfield-cancel.svg create mode 100644 toolkit/themes/osx/global/icons/sslWarning.png create mode 100644 toolkit/themes/osx/global/icons/tabprompts-bgtexture.png create mode 100644 toolkit/themes/osx/global/icons/warning-16.png create mode 100644 toolkit/themes/osx/global/icons/warning-32.png create mode 100644 toolkit/themes/osx/global/icons/warning-64.png create mode 100644 toolkit/themes/osx/global/icons/warning-large.png create mode 100644 toolkit/themes/osx/global/in-content/common.css create mode 100644 toolkit/themes/osx/global/in-content/info-pages.css create mode 100644 toolkit/themes/osx/global/inContentUI.css create mode 100644 toolkit/themes/osx/global/jar.mn create mode 100644 toolkit/themes/osx/global/linkTree.css create mode 100644 toolkit/themes/osx/global/listbox.css create mode 100644 toolkit/themes/osx/global/menu.css create mode 100644 toolkit/themes/osx/global/menulist.css create mode 100644 toolkit/themes/osx/global/moz.build create mode 100644 toolkit/themes/osx/global/nativescrollbars.css create mode 100644 toolkit/themes/osx/global/netError.css create mode 100644 toolkit/themes/osx/global/notification.css create mode 100644 toolkit/themes/osx/global/notification/close.png create mode 100644 toolkit/themes/osx/global/notification/error-icon.png create mode 100644 toolkit/themes/osx/global/notification/info-icon.png create mode 100644 toolkit/themes/osx/global/notification/warning-icon.png create mode 100644 toolkit/themes/osx/global/numberbox.css create mode 100644 toolkit/themes/osx/global/popup.css create mode 100644 toolkit/themes/osx/global/preferences.css create mode 100644 toolkit/themes/osx/global/progressmeter.css create mode 100644 toolkit/themes/osx/global/radio.css create mode 100644 toolkit/themes/osx/global/resizer.css create mode 100644 toolkit/themes/osx/global/richlistbox.css create mode 100644 toolkit/themes/osx/global/scale.css create mode 100644 toolkit/themes/osx/global/scale/scale-tray-horiz.gif create mode 100644 toolkit/themes/osx/global/scale/scale-tray-vert.gif create mode 100644 toolkit/themes/osx/global/scrollbox.css create mode 100644 toolkit/themes/osx/global/shared.inc create mode 100644 toolkit/themes/osx/global/spinbuttons.css create mode 100644 toolkit/themes/osx/global/splitter.css create mode 100644 toolkit/themes/osx/global/splitter/dimple.png create mode 100644 toolkit/themes/osx/global/splitter/grip-bottom.gif create mode 100644 toolkit/themes/osx/global/splitter/grip-left.gif create mode 100644 toolkit/themes/osx/global/splitter/grip-right.gif create mode 100644 toolkit/themes/osx/global/splitter/grip-top.gif create mode 100644 toolkit/themes/osx/global/tabbox.css create mode 100644 toolkit/themes/osx/global/tabprompts.css create mode 100644 toolkit/themes/osx/global/textbox.css create mode 100644 toolkit/themes/osx/global/toolbar.css create mode 100644 toolkit/themes/osx/global/toolbar/spring.png create mode 100644 toolkit/themes/osx/global/toolbar/toolbar-separator.png create mode 100644 toolkit/themes/osx/global/toolbarbutton.css create mode 100644 toolkit/themes/osx/global/tree.css create mode 100644 toolkit/themes/osx/global/tree/arrow-disclosure.svg create mode 100644 toolkit/themes/osx/global/tree/columnpicker.gif create mode 100644 toolkit/themes/osx/global/tree/folder.png create mode 100644 toolkit/themes/osx/global/tree/folder@2x.png create mode 100644 toolkit/themes/osx/global/viewbuttons.css create mode 100644 toolkit/themes/osx/global/wizard.css create mode 100644 toolkit/themes/osx/mochitests/.eslintrc.js create mode 100644 toolkit/themes/osx/mochitests/chrome.ini create mode 100644 toolkit/themes/osx/mochitests/test_bug510426.xul create mode 100644 toolkit/themes/osx/moz.build create mode 100644 toolkit/themes/osx/mozapps/downloads/buttons.png create mode 100644 toolkit/themes/osx/mozapps/downloads/downloadIcon.png create mode 100644 toolkit/themes/osx/mozapps/downloads/downloads.css create mode 100644 toolkit/themes/osx/mozapps/downloads/unknownContentType.css create mode 100644 toolkit/themes/osx/mozapps/extensions/about.css create mode 100644 toolkit/themes/osx/mozapps/extensions/alerticon-error.png create mode 100644 toolkit/themes/osx/mozapps/extensions/alerticon-info-negative.png create mode 100644 toolkit/themes/osx/mozapps/extensions/alerticon-info-positive.png create mode 100644 toolkit/themes/osx/mozapps/extensions/alerticon-warning.png create mode 100644 toolkit/themes/osx/mozapps/extensions/blocklist.css create mode 100644 toolkit/themes/osx/mozapps/extensions/cancel.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-available.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-dictionaries.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-discover.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-experiments.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-plugins.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-recent.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-search.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-searchengines.png create mode 100644 toolkit/themes/osx/mozapps/extensions/category-service.png create mode 100644 toolkit/themes/osx/mozapps/extensions/dictionaryGeneric-16.png create mode 100644 toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png create mode 100644 toolkit/themes/osx/mozapps/extensions/discover-logo.png create mode 100644 toolkit/themes/osx/mozapps/extensions/eula.css create mode 100644 toolkit/themes/osx/mozapps/extensions/experimentGeneric.png create mode 100644 toolkit/themes/osx/mozapps/extensions/extensionGeneric-16.png create mode 100644 toolkit/themes/osx/mozapps/extensions/extensionGeneric.png create mode 100644 toolkit/themes/osx/mozapps/extensions/extensions.css create mode 100644 toolkit/themes/osx/mozapps/extensions/heart.png create mode 100644 toolkit/themes/osx/mozapps/extensions/localeGeneric.png create mode 100644 toolkit/themes/osx/mozapps/extensions/navigation.png create mode 100644 toolkit/themes/osx/mozapps/extensions/newaddon.css create mode 100644 toolkit/themes/osx/mozapps/extensions/rating-not-won.png create mode 100644 toolkit/themes/osx/mozapps/extensions/rating-won.png create mode 100644 toolkit/themes/osx/mozapps/extensions/search.png create mode 100644 toolkit/themes/osx/mozapps/extensions/selectAddons.css create mode 100644 toolkit/themes/osx/mozapps/extensions/stripes-compatibility.png create mode 100644 toolkit/themes/osx/mozapps/extensions/stripes-error.png create mode 100644 toolkit/themes/osx/mozapps/extensions/stripes-info-negative.png create mode 100644 toolkit/themes/osx/mozapps/extensions/stripes-info-positive.png create mode 100644 toolkit/themes/osx/mozapps/extensions/stripes-warning.png create mode 100644 toolkit/themes/osx/mozapps/extensions/themeGeneric-16.png create mode 100644 toolkit/themes/osx/mozapps/extensions/themeGeneric.png create mode 100644 toolkit/themes/osx/mozapps/extensions/toolbarbutton-dropmarker.png create mode 100644 toolkit/themes/osx/mozapps/extensions/update.css create mode 100644 toolkit/themes/osx/mozapps/extensions/xpinstallConfirm.css create mode 100644 toolkit/themes/osx/mozapps/handling/handling.css create mode 100644 toolkit/themes/osx/mozapps/jar.mn create mode 100644 toolkit/themes/osx/mozapps/moz.build create mode 100644 toolkit/themes/osx/mozapps/passwordmgr/key-16.png create mode 100644 toolkit/themes/osx/mozapps/passwordmgr/key-64.png create mode 100644 toolkit/themes/osx/mozapps/passwordmgr/key.png create mode 100644 toolkit/themes/osx/mozapps/plugins/notifyPluginGeneric.png create mode 100644 toolkit/themes/osx/mozapps/plugins/pluginBlocked-64.png create mode 100644 toolkit/themes/osx/mozapps/plugins/pluginBlocked.png create mode 100644 toolkit/themes/osx/mozapps/plugins/pluginGeneric-16.png create mode 100644 toolkit/themes/osx/mozapps/plugins/pluginGeneric.png create mode 100644 toolkit/themes/osx/mozapps/plugins/pluginHelp-16.png create mode 100644 toolkit/themes/osx/mozapps/profile/profileSelection.css create mode 100644 toolkit/themes/osx/mozapps/profile/profileicon-selected.png create mode 100644 toolkit/themes/osx/mozapps/profile/profileicon.png create mode 100644 toolkit/themes/osx/mozapps/update/buttons.png create mode 100644 toolkit/themes/osx/mozapps/update/updates.css create mode 100644 toolkit/themes/osx/mozapps/viewsource/viewsource.css create mode 100644 toolkit/themes/osx/reftests/482681-ref.xul create mode 100644 toolkit/themes/osx/reftests/482681.xul create mode 100644 toolkit/themes/osx/reftests/baseline.xul create mode 100644 toolkit/themes/osx/reftests/checkboxsize-ref.xul create mode 100644 toolkit/themes/osx/reftests/checkboxsize.xul create mode 100644 toolkit/themes/osx/reftests/nostretch-ref.xul create mode 100644 toolkit/themes/osx/reftests/nostretch.xul create mode 100644 toolkit/themes/osx/reftests/radiosize-ref.xul create mode 100644 toolkit/themes/osx/reftests/radiosize.xul create mode 100644 toolkit/themes/osx/reftests/reftest-stylo.list create mode 100644 toolkit/themes/osx/reftests/reftest.list create mode 100644 toolkit/xre/MacApplicationDelegate.h create mode 100644 toolkit/xre/MacApplicationDelegate.mm create mode 100644 toolkit/xre/MacAutoreleasePool.h create mode 100644 toolkit/xre/MacAutoreleasePool.mm create mode 100644 toolkit/xre/MacLaunchHelper.h create mode 100644 toolkit/xre/MacLaunchHelper.mm create mode 100644 uriloader/exthandler/mac/nsDecodeAppleFile.cpp create mode 100644 uriloader/exthandler/mac/nsDecodeAppleFile.h create mode 100644 uriloader/exthandler/mac/nsLocalHandlerAppMac.h create mode 100644 uriloader/exthandler/mac/nsLocalHandlerAppMac.mm create mode 100644 uriloader/exthandler/mac/nsMIMEInfoMac.h create mode 100644 uriloader/exthandler/mac/nsMIMEInfoMac.mm create mode 100644 uriloader/exthandler/mac/nsOSHelperAppService.h create mode 100644 uriloader/exthandler/mac/nsOSHelperAppService.mm create mode 100644 widget/cocoa/ComplexTextInputPanel.h create mode 100644 widget/cocoa/ComplexTextInputPanel.mm create mode 100644 widget/cocoa/CustomCocoaEvents.h create mode 100644 widget/cocoa/GfxInfo.h create mode 100644 widget/cocoa/GfxInfo.mm create mode 100644 widget/cocoa/NativeKeyBindings.h create mode 100644 widget/cocoa/NativeKeyBindings.mm create mode 100644 widget/cocoa/OSXNotificationCenter.h create mode 100644 widget/cocoa/OSXNotificationCenter.mm create mode 100644 widget/cocoa/RectTextureImage.h create mode 100644 widget/cocoa/RectTextureImage.mm create mode 100644 widget/cocoa/SwipeTracker.h create mode 100644 widget/cocoa/SwipeTracker.mm create mode 100644 widget/cocoa/TextInputHandler.h create mode 100644 widget/cocoa/TextInputHandler.mm create mode 100644 widget/cocoa/VibrancyManager.h create mode 100644 widget/cocoa/VibrancyManager.mm create mode 100644 widget/cocoa/ViewRegion.h create mode 100644 widget/cocoa/ViewRegion.mm create mode 100644 widget/cocoa/WidgetTraceEvent.mm create mode 100644 widget/cocoa/crashtests/373122-1-inner.html create mode 100644 widget/cocoa/crashtests/373122-1.html create mode 100644 widget/cocoa/crashtests/397209-1.html create mode 100644 widget/cocoa/crashtests/403296-1.xhtml create mode 100644 widget/cocoa/crashtests/419737-1.html create mode 100644 widget/cocoa/crashtests/435223-1.html create mode 100644 widget/cocoa/crashtests/444260-1.xul create mode 100644 widget/cocoa/crashtests/444864-1.html create mode 100644 widget/cocoa/crashtests/449111-1.html create mode 100644 widget/cocoa/crashtests/460349-1.xhtml create mode 100644 widget/cocoa/crashtests/460387-1.html create mode 100644 widget/cocoa/crashtests/464589-1.html create mode 100644 widget/cocoa/crashtests/crashtests.list create mode 100644 widget/cocoa/cursors/arrowN.png create mode 100644 widget/cocoa/cursors/arrowN@2x.png create mode 100644 widget/cocoa/cursors/arrowS.png create mode 100644 widget/cocoa/cursors/arrowS@2x.png create mode 100644 widget/cocoa/cursors/cell.png create mode 100644 widget/cocoa/cursors/cell@2x.png create mode 100644 widget/cocoa/cursors/colResize.png create mode 100644 widget/cocoa/cursors/colResize@2x.png create mode 100644 widget/cocoa/cursors/help.png create mode 100644 widget/cocoa/cursors/help@2x.png create mode 100644 widget/cocoa/cursors/move.png create mode 100644 widget/cocoa/cursors/move@2x.png create mode 100644 widget/cocoa/cursors/rowResize.png create mode 100644 widget/cocoa/cursors/rowResize@2x.png create mode 100644 widget/cocoa/cursors/sizeNE.png create mode 100644 widget/cocoa/cursors/sizeNE@2x.png create mode 100644 widget/cocoa/cursors/sizeNESW.png create mode 100644 widget/cocoa/cursors/sizeNESW@2x.png create mode 100644 widget/cocoa/cursors/sizeNS.png create mode 100644 widget/cocoa/cursors/sizeNS@2x.png create mode 100644 widget/cocoa/cursors/sizeNW.png create mode 100644 widget/cocoa/cursors/sizeNW@2x.png create mode 100644 widget/cocoa/cursors/sizeNWSE.png create mode 100644 widget/cocoa/cursors/sizeNWSE@2x.png create mode 100644 widget/cocoa/cursors/sizeSE.png create mode 100644 widget/cocoa/cursors/sizeSE@2x.png create mode 100644 widget/cocoa/cursors/sizeSW.png create mode 100644 widget/cocoa/cursors/sizeSW@2x.png create mode 100644 widget/cocoa/cursors/vtIBeam.png create mode 100644 widget/cocoa/cursors/vtIBeam@2x.png create mode 100644 widget/cocoa/cursors/zoomIn.png create mode 100644 widget/cocoa/cursors/zoomIn@2x.png create mode 100644 widget/cocoa/cursors/zoomOut.png create mode 100644 widget/cocoa/cursors/zoomOut@2x.png create mode 100644 widget/cocoa/moz.build create mode 100644 widget/cocoa/mozView.h create mode 100644 widget/cocoa/nsAppShell.h create mode 100644 widget/cocoa/nsAppShell.mm create mode 100644 widget/cocoa/nsBidiKeyboard.h create mode 100644 widget/cocoa/nsBidiKeyboard.mm create mode 100644 widget/cocoa/nsChangeObserver.h create mode 100644 widget/cocoa/nsChildView.h create mode 100644 widget/cocoa/nsChildView.mm create mode 100644 widget/cocoa/nsClipboard.h create mode 100644 widget/cocoa/nsClipboard.mm create mode 100644 widget/cocoa/nsCocoaDebugUtils.h create mode 100644 widget/cocoa/nsCocoaDebugUtils.mm create mode 100644 widget/cocoa/nsCocoaFeatures.h create mode 100644 widget/cocoa/nsCocoaFeatures.mm create mode 100644 widget/cocoa/nsCocoaUtils.h create mode 100644 widget/cocoa/nsCocoaUtils.mm create mode 100644 widget/cocoa/nsCocoaWindow.h create mode 100644 widget/cocoa/nsCocoaWindow.mm create mode 100644 widget/cocoa/nsColorPicker.h create mode 100644 widget/cocoa/nsColorPicker.mm create mode 100644 widget/cocoa/nsCursorManager.h create mode 100644 widget/cocoa/nsCursorManager.mm create mode 100644 widget/cocoa/nsDeviceContextSpecX.h create mode 100644 widget/cocoa/nsDeviceContextSpecX.mm create mode 100644 widget/cocoa/nsDragService.h create mode 100644 widget/cocoa/nsDragService.mm create mode 100644 widget/cocoa/nsFilePicker.h create mode 100644 widget/cocoa/nsFilePicker.mm create mode 100644 widget/cocoa/nsIdleServiceX.h create mode 100644 widget/cocoa/nsIdleServiceX.mm create mode 100644 widget/cocoa/nsLookAndFeel.h create mode 100644 widget/cocoa/nsLookAndFeel.mm create mode 100644 widget/cocoa/nsMacCursor.h create mode 100644 widget/cocoa/nsMacCursor.mm create mode 100644 widget/cocoa/nsMacDockSupport.h create mode 100644 widget/cocoa/nsMacDockSupport.mm create mode 100644 widget/cocoa/nsMacWebAppUtils.h create mode 100644 widget/cocoa/nsMacWebAppUtils.mm create mode 100644 widget/cocoa/nsMenuBarX.h create mode 100644 widget/cocoa/nsMenuBarX.mm create mode 100644 widget/cocoa/nsMenuBaseX.h create mode 100644 widget/cocoa/nsMenuGroupOwnerX.h create mode 100644 widget/cocoa/nsMenuGroupOwnerX.mm create mode 100644 widget/cocoa/nsMenuItemIconX.h create mode 100644 widget/cocoa/nsMenuItemIconX.mm create mode 100644 widget/cocoa/nsMenuItemX.h create mode 100644 widget/cocoa/nsMenuItemX.mm create mode 100644 widget/cocoa/nsMenuUtilsX.h create mode 100644 widget/cocoa/nsMenuUtilsX.mm create mode 100644 widget/cocoa/nsMenuX.h create mode 100644 widget/cocoa/nsMenuX.mm create mode 100644 widget/cocoa/nsNativeThemeCocoa.h create mode 100644 widget/cocoa/nsNativeThemeCocoa.mm create mode 100644 widget/cocoa/nsNativeThemeColors.h create mode 100644 widget/cocoa/nsPIWidgetCocoa.idl create mode 100644 widget/cocoa/nsPrintDialogX.h create mode 100644 widget/cocoa/nsPrintDialogX.mm create mode 100644 widget/cocoa/nsPrintOptionsX.h create mode 100644 widget/cocoa/nsPrintOptionsX.mm create mode 100644 widget/cocoa/nsPrintSettingsX.h create mode 100644 widget/cocoa/nsPrintSettingsX.mm create mode 100644 widget/cocoa/nsSandboxViolationSink.h create mode 100644 widget/cocoa/nsSandboxViolationSink.mm create mode 100644 widget/cocoa/nsScreenCocoa.h create mode 100644 widget/cocoa/nsScreenCocoa.mm create mode 100644 widget/cocoa/nsScreenManagerCocoa.h create mode 100644 widget/cocoa/nsScreenManagerCocoa.mm create mode 100644 widget/cocoa/nsSound.h create mode 100644 widget/cocoa/nsSound.mm create mode 100644 widget/cocoa/nsStandaloneNativeMenu.h create mode 100644 widget/cocoa/nsStandaloneNativeMenu.mm create mode 100644 widget/cocoa/nsSystemStatusBarCocoa.h create mode 100644 widget/cocoa/nsSystemStatusBarCocoa.mm create mode 100644 widget/cocoa/nsToolkit.h create mode 100644 widget/cocoa/nsToolkit.mm create mode 100644 widget/cocoa/nsWidgetFactory.mm create mode 100644 widget/cocoa/nsWindowMap.h create mode 100644 widget/cocoa/nsWindowMap.mm create mode 100644 widget/cocoa/resources/MainMenu.nib/classes.nib create mode 100644 widget/cocoa/resources/MainMenu.nib/info.nib create mode 100644 widget/cocoa/resources/MainMenu.nib/keyedobjects.nib create mode 100644 widget/nsIMacDockSupport.idl create mode 100644 widget/nsIMacWebAppUtils.idl create mode 100644 widget/nsISystemStatusBar.idl create mode 100644 widget/uikit/GfxInfo.cpp create mode 100644 widget/uikit/GfxInfo.h create mode 100644 widget/uikit/moz.build create mode 100644 widget/uikit/nsAppShell.h create mode 100644 widget/uikit/nsAppShell.mm create mode 100644 widget/uikit/nsLookAndFeel.h create mode 100644 widget/uikit/nsLookAndFeel.mm create mode 100644 widget/uikit/nsScreenManager.h create mode 100644 widget/uikit/nsScreenManager.mm create mode 100644 widget/uikit/nsWidgetFactory.mm create mode 100644 widget/uikit/nsWindow.h create mode 100644 widget/uikit/nsWindow.mm create mode 100644 xpcom/base/MacHelpers.h create mode 100644 xpcom/base/MacHelpers.mm create mode 100644 xpcom/base/nsIMacUtils.idl create mode 100644 xpcom/base/nsMacUtilsImpl.cpp create mode 100644 xpcom/base/nsMacUtilsImpl.h create mode 100644 xpcom/build/PoisonIOInterposerMac.cpp create mode 100644 xpcom/build/mach_override.c create mode 100644 xpcom/build/mach_override.h create mode 100644 xpcom/io/CocoaFileUtils.h create mode 100644 xpcom/io/CocoaFileUtils.mm create mode 100644 xpcom/io/nsILocalFileMac.idl 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 d2452c89fa..f096279865 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 e729d8248e..77ac4fcc3e 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 4c7fed3a68..eb7c236bd7 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 efcab44852..14f9e44fad 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 + +#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(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(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 + +#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(aProxy) | IS_PROXY; + mozAccessible* mozWrapper = [[type alloc] initWithAccessible:accWrap]; + aProxy->SetWrapper(reinterpret_cast(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 + +#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 +GetObjectOrRepresentedView(id 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(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 +{ + /** + * 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 )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 + +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(mGeckoAccessible); +} + +- (mozilla::a11y::ProxyAccessible*)getProxyAccessible +{ + // Check if mGeckoAccessible points at a proxy + if (!(mGeckoAccessible & IS_PROXY)) + return nil; + + return reinterpret_cast(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 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 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 + // 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 attributes = accWrap->Attributes(); + nsAccUtils::GetAccAttr(attributes, nsGkAtoms::linethickness_, thickness); + } else { + AutoTArray 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 )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(rect.x) / scaleFactor, + [mainView frame].size.height - static_cast(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(rect.width) / scaleFactor, + static_cast(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 + // 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(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 + +#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 +#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 +#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 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 +getNativeViewFromRootAccessible(Accessible* aAccessible) +{ + RootAccessibleWrap* root = + static_cast(aAccessible->AsRoot()); + id 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)[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& 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& 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 headerCells; + cell->RowHeaderCells(&headerCells); + return ConvertToNSArray(headerCells); + } + if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) { + AutoTArray 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 headerCells; + proxy->RowHeaderCells(&headerCells); + return ConvertToNSArray(headerCells); + } + if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) { + nsTArray 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 c22a085f01..53367e175b 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 8c52ce5475..64879fef39 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 138847f9cb..7cfb8a2959 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 100644 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 +#include + +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 + +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 100644 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 - Mac OS X universal binary packager + +=head1 SYNOPSIS + +B +I +I +I +[B<--dry-run>] +[B<--only-one> I] +[B<--verbosity> I] +[B<--unify-with-sort> I] + +=head1 DESCRIPTION + +I 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. 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, zip archives are not +equivalent, regular files are not identical, or any other error occurs, +B 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 + +=item I + +The paths to directory trees containing PowerPC and x86 builds, +respectively. I and I are permitted to contain files +that are already "fat," and only the appropriate architecture's images will +be used. + +I and I are also permitted to both be files, in which +case B operates solely on those files, and produces an appropriate +merged file at I. + +=item I + +The path to the merged file or directory tree. This path will be created, +and it must not exist prior to running B. + +=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, and an approximation of a command that performs a similar +task is printed. + +=item B<--only-one> I + +Controls handling of files that are only present in one of the two source +trees. I 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 is copy. + +=item B<--verbosity> I + +Adjusts the level of loudness of B. The possible values for +I are: + 0 - B never prints anything. + (Other programs that B 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 is 2. + +=item B<--unify-with-sort> I + +Allows merging files matching I 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 is that the L 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, L, L + +=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 = ; + 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 \n". +" [--dry-run] (print what would be done)\n". +" [--only-one ] (skip, copy, fail; default=copy)\n". +" [--verbosity ] (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 a6ab97d126..0874e4b935 100644 --- a/build/moz.configure/init.configure +++ b/build/moz.configure/init.configure @@ -542,6 +542,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': diff --git a/chrome/nsChromeRegistry.cpp b/chrome/nsChromeRegistry.cpp index e25e7bbcf6..454d517ffd 100644 --- a/chrome/nsChromeRegistry.cpp +++ b/chrome/nsChromeRegistry.cpp @@ -298,6 +298,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 fef2afc37b..c25083d763 100644 --- a/chrome/nsChromeRegistryChrome.cpp +++ b/chrome/nsChromeRegistryChrome.cpp @@ -11,6 +11,8 @@ #if defined(XP_WIN) #include +#elif defined(XP_MACOSX) +#include #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 07b347a94d..567971b197 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 #include #include @@ -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,7 +21,11 @@ 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 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 fa61c54be1..30401118b8 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -1406,6 +1406,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 fdffd95531..668e20780e 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -6842,6 +6842,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; } @@ -6864,6 +6868,7 @@ nsContentUtils::HasPluginWithUncontrolledEventDispatch(nsIContent* aContent) } return !isWindowless; +#endif } /* static */ diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index bfd2e334ea..637e3954e1 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -66,7 +66,9 @@ #include "nsAccessibilityService.h" #endif +#ifndef XP_MACOSX #include "nsIScriptError.h" +#endif using namespace mozilla; using namespace mozilla::dom; @@ -1268,10 +1270,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 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() && @@ -1283,6 +1287,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 770eaf2bbd..b9bb459eba 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -6228,7 +6228,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) @@ -7066,6 +7077,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 27e4e7b7e9..e508a22f3e 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; } @@ -2937,6 +2948,10 @@ nsObjectLoadingContent::PluginCrashed(nsIPluginTag* aPluginTag, nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); +#ifdef XP_MACOSX + HTMLObjectElement::HandlePluginCrashed(thisContent->AsElement()); +#endif + PluginDestroyed(); // Switch to fallback/crashed state, notify @@ -3175,6 +3190,10 @@ nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, return; } +#if defined(XP_MACOSX) + aInstanceOwner->HidePluginWindow(); +#endif + RefPtr 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 30360eb1f4..8175877148 100644 --- a/dom/base/nsWindowRoot.cpp +++ b/dom/base/nsWindowRoot.cpp @@ -37,8 +37,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 87c4827fab..c8f1acdb55 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -95,6 +95,10 @@ #include "Units.h" #include "mozilla/layers/APZCTreeManager.h" +#ifdef XP_MACOSX +#import +#endif + namespace mozilla { using namespace dom; @@ -1513,6 +1517,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 @@ -2829,6 +2841,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, @@ -3061,7 +3096,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()); } } @@ -3473,6 +3511,18 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, } break; +#ifdef XP_MACOSX + case eMouseActivate: + if (mCurrentTarget) { + nsCOMPtr 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 +#include +#include +#include + +#include +#include + +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 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..09e00613e1 100644 --- a/toolkit/content/dialogOverlay.xul +++ b/toolkit/content/dialogOverlay.xul @@ -1,11 +1,11 @@ - +# -*- Mode: HTML -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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 +14,44 @@ + 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 0000000000000000000000000000000000000000..04da26a25248313c15a6088cdb192bf8fab01301 GIT binary patch literal 2288 zcmV!$0vZ&VzsXzMWrN-MhjA$s2LqUX{1GE8k5?X zR{YV18rzg2ZT*xKtql^ZI5WzFf-TB`d`4S3F{$#G=`d@*oI}nW&bjwu(I%5(4U}9aQHt)F9hC>;z{UZ>>W0S zZG+YV-*18+i6R;r1bu{=e}pzdzX87Q|DG2|0APOvK1g<%ilcXM-dLu7`jx1N-*vJI`&qcI_%B+sc(IKjt>V5Y|0~;7u$H zW4U`(RaF^bVPQ^`N4p`>(b4Hwu3Y&#JUskeT>h6mb{p6V)`Ksi@aK3qyabqe6%`ey zySlo17A#ocbh%vT9S+CgzP>(=zX|-O48I!1!yJF$z=6c5sHh0MrxM@mOH53R1HO92 ziWSHE`}_H#{WtLW+<}FBI>}B@UD0@weqhU3174Bw+zF~H8c%Wu_zxI9N5*p}7+!8X zNgwcUa?mgGoI}rbh;^y)?(YiqAzSP1+qb*Ly3}|XKV)m|?Ck6n>OE592l3p29;x@z z__V31sVPP8Cu2>lP;9B<_Bbe*^Uw2X11XP_4TLy{QQ#0hUw&9ga;2E{Hxg_-n41c+w?3j=SGeknMF2q z$Z4|S-TTZI@zSMB57B}>eE6`-Znx)>4b!QTY?+yvpXe5`4-E78$&7|N)~#FjH*wmK z4KJt^rp(vC-eGfi6pELi?yVIK4a>>NsS&3Q+2EW@m@*#&-^=j#qgVr_cG&Qp>4k-b z_2RT48`|(aUq8@yfbSF!GCD<{ws??SluuLODf~#Vh^G;0lJGN8q*%m%2K=|GP=dCy znI@qG#WO!?LV-XOF(#n|#WQUh{NpGVn1m8v(hOI|LP9VXqzUF4Ee3O;&_F4e`xmsv zVlYSN(q!nP%uJ(w4O(Fs%-xIPZM(+Wi$k@`<)U-)48X{(b z3O{Pps4*KiZp^xT`SL2oXo#3#72em^cl4SyYnBuj7cW+fMzH9>{YDdR^5n@;iqY^D z>JJ&=LqbBr6{8_Kd_XfJyk-0dfe+Yqz?PPl z%JlT~y@>5^8SnvZFCsK|rKP2P1a8q`zy}^Sz<TG}|oQPlLhIAv;u>+>v>_c7^po4^Oo%Lkc<4|w>1hYxu8!0*oo zyd|(dn}3h9@W89Ox;o3x&(ELPV%ak`13r+-b~B2Vyw06F_YzKEzQd)mXKV(1Ae(Ij z7iqq`U(m2wQmd>eZZCvu2H9_afP|HU%GW$~zE(z<306 zf0{mh`e=5~$)2?-_`u8Z4&-n+=9H9_qz48D`m%d3vuABuJ}`g&e1GBt-QC@VbR7^7 z5Ps~~vGrE@02=)u@qvzxj-BpUJt*jj6DLlrw8{sjO`A4`_y7XHKQsK){QUeBOMGDY z^5p?Xj~;!yt*xykB_(BpJ7#EauS-fw+GT|gBqt}2J9X;RYuBz_yPlYs_$Jx#uB%R` z^L;CPAR;2dAK^o0Yinye+ET#q&8t_h{!GOOhU5n*_Q&zvICt*c61v73ScM)fQ2aWr z%nwlP-_X#|0!Q3Gy<-ie;a$awU#FG%0gC<6h95R>-uw++V+}l)ot<5#_;p&HA3y+C z6c-n_N)rRw%<=NBTw6@@12=BmsM)b&$20N^wr$%s@A~!YF0=dqhQB^HH#bVg=j7x#urYjR zmLCAVDJv^$2#*G}4F$eY<^$exY@e5x_aS=QdGh4RruhLAG0qRbI~u0rx@)ui z0G>TSs=GGJ57gDwy-upTwwxa@5!3vDiJ0aGOvHMAKqHp(0~)cMAJE9~^8@mxG2Hxs z0lxok@yh=TSrqO6g)EBp|3Vf;%?E}d?V8x$swnxuFhtJ>EQlc=upowfz=90&0dKa= zk)e7R-dFR+GdwkDhYxu8z^|462`~VMhV18mi%}8)0000< KMNUMnLSTZewr+U< literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/downloads/downloadIcon.png b/toolkit/themes/osx/mozapps/downloads/downloadIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..42dc4943d7a5375bd4452c435a28993745a2d5fc GIT binary patch literal 1301 zcmV+w1?u{VP)%;?B~W(ZKc+w%ujH$6)dvbv)73BR0S%xp~~Pg znKMuA835bP>X`DxMMG5*D!!{guk$K;7rsH4vjZK@HeR%*J(`h+yt*^`MU%zE?g}8O zm90~is+P<9PUFVnbu`Y_BX2ARM@CGWie}^?FJ;uFnq^Z*X7i2!^iBF6eU1L9WyTD% zs}sjYk05<870ClE*gpNHq8WL}OBu>iM@mb| zXh+UeHj=E`HB$A8`o}AP zxFF8mXm5aaj6wW82_yqz#QhxmTrr5a`i@Tkzpvjyz>j_&1>W+5;I==)hlHrGmE%C} z0k>CxnA})gSU3;)umo2em$6qLi`|Ui!2s0ES0mzs2zUjEEfg=;+H0U1S0Lth5xy9| zwClsoW1R^6^=*U?3sGi2g~+@}@4o@bN$KN{hd%=IP=$A`p$PiTALjASogdC+sUu`0 z5aP)w z3ZQOQ_sCkL$Z=&uI2FVzWSoQegM*lOaR~BKhO#R?$6d!j-HHaqC9eQXN8h$9r0i2h9YAFu}BN2)aki8f7e`@iX#}mok*yv-bi`tL9`}RovXc;)Bc4pGHG= zQ3{zw_DtNG_Gm^P^1jqJBTbvSK-r}1RWvHElW0wQG<)@Ox?TMTSkOdN=g#IH00000 LNkvXXu0mjfJyK@= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8740e4911a857dd0d2e479529594fa4bb17adf11 GIT binary patch literal 3402 zcmV-Q4Yl%#P)5r00009a7bBm000XU z000XU0RWnu7ytkYPiaF#P*7-ZbZ>KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0007YNklck{!EpeF=v>+3LqzH;`7DSf?-71l! z34ug;72QM^6?RcrU_@r16tOUhFv?QM(AkwrkelY*md@?%qM@LZ&h_v4aX5#EEXzWn zX$l1WI-ytN94o-d>)%GAg*bst#!l$jp~>nJrLf?1^#j-oAhDifg~H8&;F%J^>&i8S z3SD8_@{<2mYkPYkmTg?US~)p&7HQhNt)_M$9jJb336WYF8ag{W`NRKT0VBF#a$Z@w zyC!m|>+Zb+U3c1kxjj#0DrpYgxY@ow!`2MOV&X+5JV!>fc zC4IAF-z%>K>r8{eZ!iWIC)lGC$G&zyKC(fe6FPlTC{s$<7=uBC>A|;qTn>AKlQHOw zK@q`A5L3^3TLCy$1MWB2UMZjvAqyZWYwb2$J20Vw8#)VufRgpIRZZYf}J4$6lWS%^m>ryqS9j*my9 zu_9P4lGaLRzcW4|C$DG-t%q(R7Jvw9|xXwVoPbqVfpNCk@vr1Q+Y g)E4!9p{IWb0Q6nD-z~wYHUIzs07*qoM6N<$f`#N&H2?qr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2c5f628ab6001ba2f20a19e20f5d6c556b8f0169 GIT binary patch literal 1564 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~q!3HGX7W?Z1DajJoh?3y^w370~qErUQl>DSr z1<%~X^wgl##FWaylc_cg49rTIArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XP}#GU}m6TW~gUq zY+`P1uA^XNU}&IkV5Dzoq-$tyWo%?+V4wg6Nh+i#(Mch>H3D2mX;thjEr=FDs+o0^GXscbn}XpVJ5hw7AF^F7L;V>=P7_pOiaoz zEwNPsx)kDt+yY-;xWReF(0~F4nSMoLfxe-hfqrf-$X{U9#U(+h2xnkbT^v$bkg6Y) zTAW{6lnjiIG-a4(VA$ce2&53`8Y`Fl?$S+WE4mMTrO-#(89#j!qr6ysy79TzX|Hq2RcR{6tPGV4HE*U9uO0r zY=Inj!cWZuruHIWvR;}z)qsJ4X@aMVV@SoVmLPABkVFx?|G(|rWo184Tx6!T^MsjX zhobmK~WgTW#%}>vOB~ z|I5Bn)Qa0NyLjLK-}{Qsxu2hB$!A@!;i0*=-+=jc>FwCtT|4`9U%wL*x%br6z|Ebz zyK8pW=~c6sL`xq#CkF~AZsOf;%;*$5D^2EEm0-bL{u!?iT5r$)maMhs-aM9=0z;3M zooZ9nySlv91rMH#$XWUOR&TUp%S$;Hj`o&S;o;Y=*z{U?6wF z~^62-<^o#217ngPKXZiH3c~xT3vL6SsjgBS!JNrj5)V$wN?USjG z28ZiX{W!nauAUxgk6lV<*|TkynH@G*|C&|RAv9j@g%!i<_{XU`bAEM%ta~tv^WK|# z8qz0Iz8?D(l~efeZOlE!^qc@K#?T&{6NNDo-q*j;USG3y|F(ao2Dx`jFQ&N&tZ-_b zd2?s?#D~Z0F5L;aoj7Y>+*$>O%HKx0JLmB7^7c-g_pZ1iR^$?CUwu#DSr z1<%~X^wgl##FWaylc_cg49rTIArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XP}#GU}m6TW~gUq zY+`P1uA^XNU}&IkV5Dzoq-$tyWo%?+V4wg6Nh+i#(Mch>H3D2mX;thjEr=FDs+o0^GXscbn}XpVJ5hw7AF^F7L;V>=P7_pOiaoz zEwNPsx)kDt+yY-;xWReF(0~F4nSMoLfxe-hfqrf-$X{U9#U(+h2xnkbT^v$bkg6Y) zTAW{6lnjiIG-a4(VA$ce2&53`8Y`Flt zp`oFRo28MfqqCE?$S+WE4mMTrO-#(89#j!qr6ysy79TzX|Hq2RcR{6tPGV4HE*U9uO0r zY=Inj!cWZuruHIWvi@zbYcT@@<2p|l$B>F!OD660W_A=fnx6Ju&&~gclEM<<5T34S zWnp1mnYmMzbl!;M`p3SlsWnt#YVU%jp;6q@+^e@V32?BOsHNH0RM)XwRC;wtu6p<9 z+27O3Dy!1x{WDzlL|;VsY2*&QsB2I83mQe^SNFtSUHmKVztBA9nCU&|UK;wIx$~>H zS%-7}{)2)m1yus(@XTe%Qr8M&y}`DI!s*#bcZ)r)7iRe>U3z?x@mi#Y{^n_30n;KB4lO^oV^gh}NW$%{EV5N?Ma;Zei*{F9 zaFl)e@uu>1c;PaZ(#LFijPL6X6gb|i4w~$3dEUvKWA(y7){8c$nUbBf8^jahLv+fY zWE?K@Jzv6BAdwk(PjI24@8^uJ6>FcZ;hsCMp|rJbi(t?e+uPe@n|CnExcw43+-5$5 zZ`oXJ;d6zlw<}&p{HncP%B!OGJ8A!c`J8raLM*J=E8>sk`$XC@F5-H9P%-CMe*Tlt a21bU@ajS~uCcW$cm1drDSr z1<%~X^wgl##FWaylc_cg49rTIArU1JzCKpT`MG+DAT@dwxdlMo3=B5*6$OdO*{LN8 zNvY|XdA3ULckfqH$V{%1*XSQL?vFu&J;D8jzb> zlBiITo0C^;Rbi_HHrEQs1_|pcDS(xfWZNo192Makpx~Tel&WB=XP}#GU}m6TW~gUq zY+`P1uA^XNU}&IkV5Dzoq-$tyWo%?+V4wg6Nh+i#(Mch>H3D2mX;thjEr=FDs+o0^GXscbn}XpVJ5hw7AF^F7L;V>=P7_pOiaoz zEwNPsx)kDt+yY-;xWReF(0~F4nSMoLfxe-hfqrf-$X{U9#U(+h2xnkbT^v$bkg6Y) zTAW{6lnjiIG-a4(VA$ce2&53`8Y`Fl?$S+WE4mMTrO-#(89#j!qr6ysy79TzX|Hq2RcR{6tPGV4HE*U9uO0r zY=Inj!cWZuruHIWvbOb+xW&N0G}+U|F{I*FOOUTeNT7(_^L^F#Yu}%p{k;0|nFyC4 zqZElKj+;u+EQ(u%lC~5?3T)wG(VoiDJYkdB+a@u|C(Dd-5+_~ccXDasYP6rZEN_j^ z=`=T)^k@6t&7NaqYIVB!{Lg*w>+T=y+iab%*{PvpS(5ldmPwLp#4P}O?Av(Z#OH0#ZoL*}xqd?7bq_a-Zq9~WwnMRer?0uBe}31vbLICb&QcLc8dlc7 z)*LbxGiP4FwXH=)=)-N189&0FRBB$53{VVS7{th^oqLYwPVTDQ-wTuu?q}R{!>O{u z>U`n?X%UOw{MbJxF&!-}Jbi+pstHmY%Oq@neSEuD%W3MGuKNrzN3YeiuiAC8!HCJq z{fE`hRX(3*w)4ntw)GN}+3)@Qn3evO7-@4`(UhrsjdCWhchsxE_&_8;~i-@mMW`01eNBK@?m zT;-y}- z^?dDejo^(lq3&I6^5S~FJR-Cl9WcNNQ$uHGz3&sS}0vuB+Tk!NC2m?T`zza?C~u2cmWeb9kZmk zq!FPCEeUE$6_|c%b72&%FIH*ZTq{NWIdlD_wo} zoaZd}-0wpY|4)r>@iPY=0!)$s%w(VaC27a9fS{f=Prv?P_nP|(Kw{-=>@_z?{c{o! zO4hNAadwH^|9XhqHy;Qk_iQ;9*9~C8eF5kbtqJS-pG_&0A{`QzOYh-mZyj%w$dSl( ze}d!cg(-znq(x%Y&ocmXUmK$aAmPYDaxb66^6^gMD`T-W*I<#bOOHCG#|k9!B=V(5 zoAlL964??CiS%xPl#@j} zCk_v==C#=IR%zOnKuyD}y@o>8wJA2KR(HChW?zxX=IaHp25e0$mRNu57SZ#b93B83 zU~|12$AFsN8-PF18~%W;Jc*Qd-vb^R2_Tf5<8KYB^}S}mDPi~daQbdj(Am!7(lQ`y z5CC8%^Tc`_?o;8zG+anpo4$WH0N=hMQ-M!xk$7HWlZ4YR9DYCcPCwZfDoHr9q(8vg zLyO35RdkUQmOD88gpO%(_>r;`ms;!kxUg575)IgOA;?jl+}x`#ib1D0pJT* zPkcnaG0hxONLstbuJ$P6*;a)od$c^ot&Wb45j8i~rN|tQ!n0ZxmgP}!Nk<_13W=X~ zidDOQ1xy^60TUaKr(^9@_qYv7ZP(&wYh*2{leM&7)~)s0^NVZpdc7m6{3ms?ZL5`S zTb*p%>h!%e)e7F!AzqZ&E>YCzA#VS?{s0*#HU!yk>k+3h_3V;4yGu`e=Zel>Y>~&~ zxhI`G)2v6oRVCAQzu2LGHc2>}Tl-`l6DwbN8mp@ghcQMS5(QG&x*Ph&iYvNY)vT75 zmQm^Kznb*K{3?yza8s1~#0%XJUXl61`GF+}VbauTr2OU`Hc1pp>Og;RvnC{_|^@!^l|=z@p7pY zv9d|Ub+ULL#fCwa$KJPk%Iw}4sWEWUF`G@a!#paD)cEL zwo&^IRH?46Zb7%peJl!$IZ_ztC5jD|v~~bmqqqlXZt#* zBoIw-mRHd;FX{Jxs8(%lZ9naOwOZpBG|IH0z2{59VSsG*ij}+n#MC9J!1N(A&@&HU z4E9UErMSOy3MFi93XS(_(sY;pa;!#fw_9$vTZbyE^>DOH!SNj;U1FnTw%iVYIE+tw z<;z=*Ak8E0h=5?6jeqN@-DbnNQ^MvG8>O*vHx)h0r4LV6>*)K{n)H-Qp)0*&oeTiK zLM>f^q1K&^groa`F?ZaO0imov{dazN^fas9coXZX1_~roo?lpffn9K>6t=8IGv-z) z`kO9|U4B!hRG&;KzFvq-iNs%DB=(IrNj`p#XDSa3{1p!eU;@SiUjn{J`tiNpKP9Zj zHatrrLkdc|Epu|0NRvo4lqRu3BKx+Om7D`iG;B>6nEtyu7m#=;*Vy}u)vy1Roccg? z=@MC9ks*<6DDk6f#J_ui%zBa5Djr`DGnCU&os3vo@m*H!JHVKbg}`JI-~JsIiFB`6 z_+lk%-}xXAy;saD-T_P|YR)oNAAB2_^tq2_KoBqv2nI~d%KSR1XFSZ$+YNjL_%iX8 ze`M|Zp8`_=bKiIF>*2KLzcC|WwozNN(K8P+;|Ftw>hk*zFx+`>G$-P|`X9!iEZKI& RA!q;q002ovPDHLkV1jy~C2s%# literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..54ae4f93fc5c8179b44d062f61cae2bee4a652dc GIT binary patch literal 1769 zcmV!Ck9eR~5(M-xzbQz4u9S z^3ulEXb`I)L?QUr3sHok)`F--Y<D!Z zFf$~Y8SR}^IQQA%MmPE0Af4L|wj_aFH*5X@BGfWhL}%D?mc@pqg)bJzP>tyaA3Y5sFh zKKUd^u08&7GX`M8j4^X+d%K>y=R;qvM~;2>^qqHc=FA!X8{;_g$iwH^y7BGX{&*!oG2$thsdl2kiZRo0GSm;OtvYuv~4iS{+8EA~UQM5|E&`PV|X3 zZs@%ez0t-sx!G{GPqaJU{w?Fe&w`;40K{S_j4g;1p+GV=t%KXUfx=AXAYZTB>g3kI;Z(&819#XOmh?zcVpTfk&U({0v{<7DFK=#@4BB42z0@olWDh zXRjfNS}ICo>K!l)G3U%kvMEH5K){qDNxT}EQ{CuOqa~O-r4~YvDfTvv_4X;w-hD3@ zFYho;6R!|hF))mo%8VJf!x$nXDF&M<*|f&8tmq2<{M;^YI=14(p(VxP#=5h!9(kc0 z<8xp68f6&JwBe--4>Pq+9SS}<5(hX3S%xr)o?_~WnIMfG$;B%>yz|4KU}trbtJl}8 zH=P?(=SnR3%(<_FiD-@ORi#S|L*XBri77e;#*8x~pk>lSq#!eyB1w{UYus_?M>+Gp z5Ad;j@8{Wmm@B(Cczidw=ZohkZuDv7=|4Tn6Tdl+AyQ&d3n|`^Gh<*faEFvg2^R<> zGC8^$h(6)KC-3_LSM3OQ-}fa}%PrbC^1>63@z;lbL@m%$^bk`|q9-YgnaYd=t&1rI z5#K!Oaz%$Rd# zBv}L$1CxV^X$o$P6I1K76gSolGI-**=ec_MDK7l%$5Ukb{{qSDA4$3ljy= zLWH12kV<+tcG^TOMsa8X%0v-_8yN}i^d4ja7EB>hf#IZN%v5GlLMb8=l%$dfCj@fQ zn=msnp+#UA6h{(c?+mpvMNo^=yWu7>o!%$hIKX*5OQ{-0OQCdnPs|+&#wqBT^n?&B zmW5$qNU&*1NVMoIYDE~Sqo4%WXjH9j+a72;BIUt zAwfjq4hkbfNJ0`R=zYR4qKTfd0tyg-*yr42z^Y|#H$+CzGjU6LBoTpt);jB+Opzdn zOd_H8q)FgTD3B$!7={r_y2KQ@4Xgm;Rh63xa40fvsb-{jg#dE{3gEcI(v4|qU{Fh? zWdp=e9LYe?y9g!AVSor~fylfCI0TG9vafPe0S-sXQ+8=gJKa09tn5w3m7O(<>P*p* zU^!G$Nik>Y!Qce}Jpv4BskCTBj;!i{yk3ix14n>eps~-mX$!c$y5CyekGR>us{|Ne zfB*^1c!l^EH>K2i=3dXd20RC>*=OA3zzV4RZ*^b;B>Vmel%Pwalwo+K00000 LNkvXXu0mjfD_$)g literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a6f5b49b37f13cf890f82309ea9aff678916ec71 GIT binary patch literal 1324 zcmV+{1=IS8P)!-zPFNiH}Zhs=KPY&iT$c-BmZZJDWN7oS*c=C-(4s-h~~xcn!JU$nz!=Bu;Gaw2{OE@#UU&j{A`ut>Hxs6gf)*o$0PbAe z$lT#!AEHk$Jildp$5_$YGmD!*2z+++3Rj=J&-B5qcwL6T%?&M_mFvoLHxBUb@#6^~ z)VOcJtySh)tGxf>UY@?*;P)R^Xx}gX7W~!4 zoqTX=A9MM+`S~_SPCiAb)n4DQ1=}Yk-kE4FaQD|M+wF*QH`%;)c&Zmh3>+tfs}Ifvfm#_wxP zPc-P11JnYXSy*80d*$0=34r6*XE?KefyL##76j5W4l54?X)m#DaysDb2Y!4cflzCl znw;Go%GGbUwYZiiO$2ILGs8JnpJwS&1mM{06xVLvPwcGH)xki`C{}MW(R`Uu8+m1r zz>ozKGtCQQ)6FzkO_Zd;aLm%gnDf$1i6+A+fW6xXj5M}0wqu5=x%u3POHk%%hm6Nq_#=k5dS6*B|ZwDHh1 zW~QKu=KCtbn!$rvXG}!bC!pepSjTH~!%PWon0EcuT%}tk)YmtV7f

*?ByP_X8H9~M72*D(-h7sq*dTTC&Wnw10-dj4wOR#LI?ra5@RHmG0Scp zuPaL|6R13v9x`HxjO@P{1jQ|VNjuni>8=qlqarm1)GHeXLuh~gu|(O9 zT_6=q4N*s{*H3c*%@8H616lImovlDxF+J}k0xT~+psKuYSKOn(4q!Jh2WvQ<-!Pr9f8D#4rHv<-W<89*UNukaxL zg$3WB0^trQFsA~Y_!1WU=QpLNPV@=Gu;6Dbf$%EEP=hkeDOiOa5Ef$+77Rmr;>1H$ z`~wTtL+Hdf3};b;oH@BgcppvN54rWR-)~mc|H;~|uwVqrsNpOO>VH~f8`CT}USr{g|L$DX+g%eV$5cp0sb9Vp3M3;S2K`sl_MEMo$47AWI#cpJlllH5d?xAee~%{O2f z=OJf-GA_g16#5c!=fnJlRvxKefn_wI1TT8_cWg?`Oody2=)S|RkHCUe&|LCP3%=8e zLvJ)-Ic{PMd(aIbnP>LmjCWa%hI&8RwgpzAi8_?1j;Vy+SJd<2n_XzaO3b1Tm8ko! zm1v?%-3NCHL$Cx*^gsn_zGDd*kd`*y5>}uEORyIzuqUyjb%7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5c4d8bf471825d8960b8d35f6060ba620b7b0adc GIT binary patch literal 886 zcmV-+1Bv{JP))k|ptNP7Apva?4~R5Xpk!f6~jG>VLjA-M7!GsYZiSj#!Cyin(} zfx5bP7X_R-%8)wabIeLAo<>Q@6EXBy%sj%eV^d;e0!NOF%WfZ)gXli8vWDeM7=+0* zAO`xeXU}cy-hIm{o{(@uy8%AA-r{j_UA`EY(p@|z=DKzR{Ll3kkBYjc-2gY7-r|vw z9q^$GCUh4!8n1dcAU}h!Uf9M~(_4Jku6BxSQ#A~p3~UgB+2?wTM?_qqNU*Bm(ZEI_ zgoljY;yZV?Q6xmw@Ms`F2;&K-w|IE?Wr~EU8g32v3!4Qak5|3L!@@36#Heccdor*^ zi00X@x43E`TGjCP_6DN(VbNP$H4x?P4R8-^5<*R;ey?A(-w{_0gsK`IH{m)VNZ297 z3W+)qtF9TOY8c#}glkODWuUNCCj!+qe-L+j6ssl->YmUO!_VU6JE<>}40#l&fdBvi M07*qoM6N<$g7*)s5C8xG literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7ecfc7d4c86e1b5569271ae2a90d4465e64bcfe1 GIT binary patch literal 1642 zcmV-w29^1VP)^^zJ!Ie_VTaz3Y{FaJ}pE_K)W&Tz}oDlK4&D zd6FmJ@Avci{CGak_lqR{pIXr5{oA=0a7Y5kXg|d z-$_O*|H_Yn%&IqdUW0e(-xnDx_#IydGMfv@HI6e1thg-zDc(BT4)c~qS4sY5>MrBt z^@~ImST6OWeivN>RkSOzN(wF0zL$9($VlB|!2qSZc--rxwyBWUit~6f?50nBNIs_a63<^4=K7%3f?b;vL!E4 z50V#MYeBwanF`mUM2GcGQ$YPlqpPK>8x(Cuz=I10c;_X)qi%cS%Se4U4J0~Hz=f_} zDN>{>PeB)wvEn9x&z|o^5f=hh-V(skTvpe=MNZ2h{_L&b=rpfV6grI}U3r=sp{8vL z)!Z@X+=+VBSW1EW@8q2=;J2^+lKbWbc;{uFRB*WzU2o;ozjU_oGbq%wP1A;%)KL9X z8mXBBxEE`$@%0+_GXB^G*3Jt6+&xrDzl<30xrRi^N15 z*LQ9q2Ut1_0LZA`!%zE9a#n+PPAfQhP}Gfjk7p?v>(^(E_-Lyxmo3+L9a1Y&2Qs^n z>A{@n!fY?0xO5krrvVh@@XcrwQgn?pv~*hd#~aY)^5u$0TXm}==07E0QKqh)K&nPM zjZ}+t-rCc5f~zwlkXigHx$2zm`L$N$E6Y@DxJssJE=X66Lkb*26Q^u|YNS(Ws1!Z5 zku9@o0Qg+>ZZ61Ubu$?#P>rI!g-Rxq|CPdRxeAxt-q)as6X-9`atLmIiZ^<`>rH+( zamj8VK6Jeu^}8NcxF=uX?tF#2@-=GwOYwL-tsJiBla&-6_W1mg04&w_=?lkw{v#6=o~ZfXrs3Mx+aN z^*5klC))8N{sF9+s|Xj>7Vx?ZJ6}`A?Zt7o1u!x)l2*)k(w+Jvl*F< zC}%6r0E=e<9Q-3|#?LXTwiM%^+FmrH_<7{3SgT+#m{v^(vXy8>>ay~!cBD3|DVZI} zI6)V%Y!<-LSNZw%Mil#awsEzfc!LEnJUpCM&F^L@(QfyWZo8H`Ek)0ufzym{e~wLa zrQl9r?X%xzTgmTfPBpHJDBg&S%C++Q{b}W`%u?b~N-=v-%#C($rDF5LJOgZ)GXhin z09evp!u#sHX?&_M{Z+{L02v?JDo2LXA+Iw-h7%c16mlaCqJ%HQ`lJo(K=Zd>^9S3> zjI<{rU~!}$P)8o*jVW0Pg(BzP zBd&e46z`^J=C61@00*!f_yX{G*IxF@YlpJe2JqWa2qJ}S?<2Ot*87AH{p4Hx0=UoK zYsJj)-_pIHXa~Qt&;B~FgEK>QmUjxESdR@7wREA3;79vuA9AB;5BkPe*ff{X1>KBZ ze}S(RyvY6~@Hnut{7sIj55)|$>jjFP1=J~mVtwePE#v_!H$KD@2VbEa$o`+NX22rg zE?_a>*!q3G>OR8QZ+^gT;7h<4i}U#Kv5=0gS4F&{Mtcu2HK%@N8W^K}G(o8!2BNN+BXY7(ioj7Tl z%w%FGHrOVn)x;Rm&}gA5DC0s@LRfw5Jb3UPynVU%-TuB8=@4o{+I%zLyYFt_`JLbS zo%5Z`J^%dk#0JN4#Pd9b!{Nwe=c%cwSurs&MI$34o1>zl@&rLJ;hsMj3=U6CO*Pr= z_9lbD(1vFNvMfj5XGh$pP$(2xW1+##2OvojTOb97pUKP3t-a%pl+>i8BvL6A#N9F= zNnx6qnW5p~5xUXd{)yFUeO;kYoC3h_27pab6z|Q;%X@WgMOlVgt){^-2Xziik;Uqk zXS_k#pjB`gi3Tn^HHMa?#1Ri0S3kQRY-wqE8{nS-2=D(501G@=US58-lT?xhEM%||86)j z{XYTtl2&PLX-P?a<@ySJ)1_{D9HD}idW+?*&7stQ|_San#JyJ zxjv=~`(1gDRHhj3Ez70uJ`2^?e_ZGH`yK{}@V5eR;J^VEGvktsjB_>jZ(rWlIYxvErwy(&ILl zETeNR7pbYK=?4(&(QgG{|Ni|9G{r&e~UDtvDUVQOI z9xA`KdslVdu`?a?(KToHLt6?aS7t_Mg<(M8BYwz|Od<>dfD{Ub!eTHavvclzr)^|l z(m&ZhXuGpATfOSBZ7aAdZP)1R*|R&587CHEfoY(utZYrgvYe&5y@xM}`h?`hhbql! z28~j$Kv@6)8EXIwN%#>#e~^VD1dg4)P@w6CZNTmhh6BNn!8T^CIQ00+7*Px|EgVZs zOsriH02p|-rh3Oqb{|i#)%Q7avX@-hUYwG_j4A~#tl&8&CU8L_ewf$L0O&_pdaN#6 zbI0Tm?&$=9S9f&}mOodWmA-UI9G(2|)BrLf2VwAiBY=Yk55D$5&5kFp4|?eBv(};V zB6D~7vbbb0F9LwX3%rE?68s1u2}H(%#InIJ`cGdU@3cDnB8(e(fz#ZyjNMavPh8&j zR;AIWryHkwdwWYz7ka)Cz@bBj{#dhP+oNq37uB7gu&gdf?_8gsn238JmB5DtffEsi z5I}?=g%GRMKp|e}oa|~Jayk?OXCRp~q6lhl_FGG1@E>5L?oo$}(>+4$w2Cgjx z;I--<_dVG+?x#0D9U9NfOljVA_`bF;O}?dGchB!{J~WkQNeQX;D$pKg!pv*8lG4CF*x6PA9}IYL9|()cpW7;05gt zDBkLD1tuU$=t78s45BG48?i>Tpyah<)|sX8DqqcWdkXZn$tgN<;>4RM7EjF2g1P6B z{RbixqNAhRcI~Xmw~W~7*T*_1(w3yWhuAw%XHL~cozVU%2$9_;u52oWVAtOO7p*3e^JO;_LerZr}D)zdo* zX?S#u>gtZZKR!Ob2Zlm(jVB}|PmUnFIH~&*#j#CgHA*59(#|P zo16cHe&I2%*DK8$XMM(s6)OlO`l|r;?b{cj1fwh~E9>L!_iZz>#^lcp?Noo&Y8TY{ zvx}mQoobcfLPG*QgauSW!<5678uYj}R_4d0J-RiI^g1m~PT3;{+S}X1*pNO#*YpSs z*=GS16cmufV!1s4W)v59r>CbMuB=>dOo)r6E{m1wTl;BX!V{SG$u@5w?9?bp8?EIN zuuJNziqom!&SY}AedL&)CeTi;t*wOqAwoO$gI0ie2*s%DYXR)tyO+?{MJ}+GDi8>4 zG?`4VFJHbqD|gvaG8pwVd`d7u)#X2bLY-khKGm057D(?&Ho0#Jm0cq3&AL5XJ=DfTpS^GvGMWo&tQ!`DJd!0 zi<6VcXf(jM3ayq%ZjXm1Y?CxNI5^(X(NRA(Hueb`*tb@$T&Z`tzTAP?5zdfaucwO_ zFS4@yW2F0EQ60YuU~ZaZW@b`ya&qLwy1F`QY-~&gg{A0-OR-Q6(#nX`@nVx1gjZKk zY?{GZ8%rm!Si5fBx;NIWSrf(HX9JFq+wG2k`26$Fy#Tt`a&6QPA97>-5v6b)}w@G6K8mKjj$=;`UP z9zTA365j`m9XIr2Ub}Yf>(Ev?zAqv%X9J<5!ootP9XfL4$N_}qS!P%<$LQ=a0sz8N zqEea>+kEICN2AeX;kp@f5vConw;;xSNWU)B{`MDMcwq?Q6LHT2kg;g-_oLU`nURqZ zVIU_bCn9pA(MT`9{PN&C@4QpM04Q(cedHXX`{(%Rw|Vr>}|U1uDc$Ojg8enD-AZA z?NzWQ&rieI^VE5>fmzz4K8CCI>j}#DTjSv>!4XD)0WD;qXR?D;{JDDqo zC_co5kSNDRie~D{%H`iGmd@X|T$}q$sB6BI7Dowcda?8D;)SnWD!u)+>E+SM_Vn;m z?dgH1mGmzQp^3ESLouwi|L5X&_x{I?i{djXOAVrWgpM`ZXq1w; zzQPL}l37JM=hN0wSZWJ~UhHez_HRkQ@Vp~rtdyc$UH(7@K0x4yufNzizcT&V;_`dH zvs|0~4Q(6F?8$kSrGLuL#UtXjwS$LnoGjG15!f4^vQ!|GjTBuyuU2B%wg|K+a<>aep zsg|mYe{7U=dkWWg&~c27H7EC7#7`*tw&V#iE{#ePgyHzUOT?jO=RE^BZiF_Pz-?`| z1|zSG6mEY~>+EwK>E23lrHZrV?$7=4{xkpnol}=z`}39B%njBylb(_UfnxPag`=;W z%xM?G)&^iM1L-MC@oR0m&7H<`ysi zNik6nV$YlBZNg7rSY@_!l&OUSER;`R3Gw}a`bx<0edo}zrfW1yYd%S>+(b%Crad5) zbZ!w5y%=fq-Tl5=(<`a`cyb8C~q8zvMz3@^in1z-$A9oyK{vxDX8eVm({ zUQM zTAwJ1wU%bwMA(>=*Td$_m-y7Kdl@clgvAO|7r#Y)aS`iy7z7{?Rv;`m#^Buh;8*`9 z-`U9J(+?!u!=Ez5AR$ACP;X~RdtT(6~KY>s#$~r_su4~j;_whQH z&zJCnRbGD0Fh1e1YiA0T&*R}xaRf(OLyop+mK}?+0-*&miZ$9;dIv|CpDR+WRvF*E z1B77PO*b=Jyv#Sh@z)$Ze1t=Xk5H-BsYjaLY=)aRO>paVTe$R|;mENCf`q|wKuS1$ zx(haAdAJULS3*{%i`gP2vvvMMblWsuq!8jX;yff3?3rZ{t%uHFHnFrqj!jpqk^ z=Ke3R<@zyx{`~Vi_UPYHsnv;$r9Z3aZuPh@RmWNoYb8cXh~l-Q{imfP{Ai%Tk}`N& z;2MFnAS_5Dgp9PI(F|$Hb&yD=dGEwAa)oYMb2$#Zb%51Mne97o#Sa2{`})~_<4zKZ zfJhsZ1cg8;aSdDuFxJx7o#t5aC_kGyfbaMyySBk1uvTC#Sa4*l4N)8;l}9#T;Owd6 zxPHLE@CM#`<8|6QI!PpxRI4Gg#TjnebqkJg5bJkXjAi4d0M8cy7y~UCkD*N=KY0Bq zFcyJa17DLFT)qCRMXeLiXbW9^LtH7&P_0%O7~KR|`UZ#4#xPr)K}v^gu8mm7m~}UC zWEmdzxaF2KwVEc5ExYe&=h*U#oSr#{r~Ip6>m=5rw*o9m#o7=XOBBX*4UX{6YkOI^ zGE2Uz2W<><#UkSqTRDF82m?bSc&~e!?)qb;Pwg?tP&`qq+jZlFuu&v1lt9>m6fb*L6I1 zcrQ;M_z6--u<}|DSPRBzBtW#@2SO&fK zjJ4SHj!Gm1iG;-SB#qEvcGhB|B^psotl`e_JGriV3t=2xjT+X$9obfv!(D9)%_v?k zLqx8tmzHdf(Xnl`cXkto5pVxulK#OFqBvRuH+DU(Yh%}F8is~j`0B%w7V#7p4?oKM zxqqcmp9A5L&vc;I_ggE7bc7&ar2FF~dE;o;F4u9M2(_lsXwsVNWan>w3aMP$JGu#y zDN>mX9bG*%8zI^lw6&P^3^W>++iuJAM_-wt!~Hg^3$J5LM0M#fi&H;F>zI1f1O(b> z!Z@TY-SythW4r(06WhnWC2tzZpMT_m+kRhbb4M73FSw};TXx(=quwBqZlSd;NAKV; z%JYbg!K_DbtR)N$AHOTlgI_4JGW`Ur{HU8Qs2N#>!-~Ox5K6H0mDnA*Jh? z=NEbAjS~PQeV4#f1fIh86oIRzt>w?OwNGec9?{x-UP~#pTqEN+;5Z(kZeXRrpopEW z=_vEbzi)^QPfD(Y|M}(-4C@;5f3XjJ8@}HhoB_UQt^JO%_AiYwcUmin;+SM%H_2oN zm3nifCi?%SVY|L*oz?|uL%?Exh3j9B!29+paF>M#B5nS3dZ~JYw4NU6*!=_TnZMOh z^E9=2gpB~_*K+v(C9tM)1mBOe-BStm!&#+I&X<<|E0gdLHilmr^DoVaZu2_72zWNq-=46L2ZUVuUG-eI`5{DIQSt1Q0*3A@Ui@ z)5Oxt9qzXqF1>K|hKs&HUr&W30ache5qRV64|smhz5|MG3sSrY6rab4yy1Bc*w?dIaBQq>3AE;D3mt8!>#j6HZ-~Y&qPaK>1cx#aG3{d<}2#o9)b)IxM zLH68n>-7V*YC?=<(A&%A9TE?!%6-3ocDmlIe-+Q6o!f8GvoXM(;d&}&cWvIZexTZs zkQbm4N*t0(auG;_&Ynu@2@26lPlFjjp->3(q)?-X zMu@O{a9NMTJ?t$#=q;^#()YY2&p1ndF1Wst9ivGr!Ow_5;z_qHUf6%{cW=0&gqsp_ z!&>`1NbDplR6I1A8GSto)s6%)$nwYsQ*%83xBa|-^rJ}=erOV2YIBD>O}Pg;4C|@S z&7weIEd;RE4#DOL<^42;TBoqEuZl^+Tn3E{oKF~Dwt(Ai+Qc`nx_pJ1JwbFE0skKa z+DxZGS2&;ky4F$s&Whoo?v3l#BHj_LO?!r!pmwVBwMw}+^Hx5X;jDbUGZ2B!3e+mX z_~aCi{Pl%{;`B1GV{IPr7TH)$s}Fwrx+|8P|HVEG$fIFmWKo#QTcHrcT7eS-agap_ zQE*rp4J$*2S;^ZGju+5q7^{c-`O?s`3l9D3{oBRcy?$iJs56rxaqg+xZ@z9|$)YZ1 z8b&=c8immar2-t7v}9fi8&+0?lzY2Lw5!D0B^x0`$a7e{eve(SEl1 zE9(cVwGKY2M;e7um{9;Z5XIQ_*cd;)^KKq`e5~aF(_W?+LE-I5)#YN;Yh< zjT1-&5^*I1Du_v0rLoNL#%zJ#3H*NdyXP&+}zXI^DL(hP4jJuXPTZ*nF(9N zLf(?LQ8qg}8=M1)g0%R)BJjq(w`evSFVuvmfw^-T&6Cd>%Y$k)Nt6CWgharJ;l1PV z#32qJn4EU-la9`w=DB_sxKF$J;qjwMBhRi`GO#2`1!{>RTuRv8XA81+N(EitW;9oo;<=k2PY}=u=hlL z=FX$j$Ht2+G-D`2?t+G9PRM30WV03;g%x((d=zVhx+QtEV|hh+chCG0bDjaqH@O8B z?eLYfR_p4mEL*l>!Rm`Q+~d>a`(|JoQPYEYmhC$F;q;S52+e2&ixy(YV-fNgvPKNq zY|M*h4B2c9c@|@qhZq{sBnFpTK4X=PclYQgb0FzJTpjV9cFBg#tNRuY{L+bA8AJF> zk>~qP)Q|6-nw~lwO;iypD_@dm>NOT>F^|Mpm{}n)2#R2ef~j+6s?MAWMJ{I!!SYSA zV(Fk?uyl#<>gwx=VYWKgY$nY{E_rOWo=*^;hzKI$%nUQ4Q4rK=VU~rwky$JDn za?P8jzS`6i9lgpMOB8n5S$v${Ei8XZ`jUo=l|Ip;A+>4+v?^v1Y9LLIB&j(cXhgA^ tH|?482Jm*rKcQMRUOAun3jgoczX6D+^g{=JGpzss002ovPDHLkV1g(D^A!L9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4ad1a1a8251b5b93eea10fa88e74d76916134b6d GIT binary patch literal 742 zcmVWn+{w*_4=+B#Og*27J-)_fG)V#8|$wc%H+T4c*?`1n=<< z)C_ZuCJ8T_M4ckr&wrf)+}73>CqPuga8%V1L28QZC|jNw6>&x(N+H5jYns9r0IsU4 zQ$Q4A$`lStSJ0j+D1rx#)B>9?4p^9<<;;Bh)jC|K(|HYuDaSFdQ z7&{Nv>0Z0RxrH~_-+9X5yKl%@i8Q0kNsHN@cXxWOE> z3P;X{dBeLfnb(t5TZ6%13?z{m)$ylsKH$rePsQ>@~ literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png b/toolkit/themes/osx/mozapps/extensions/dictionaryGeneric.png new file mode 100644 index 0000000000000000000000000000000000000000..54ae4f93fc5c8179b44d062f61cae2bee4a652dc GIT binary patch literal 1769 zcmV!Ck9eR~5(M-xzbQz4u9S z^3ulEXb`I)L?QUr3sHok)`F--Y<D!Z zFf$~Y8SR}^IQQA%MmPE0Af4L|wj_aFH*5X@BGfWhL}%D?mc@pqg)bJzP>tyaA3Y5sFh zKKUd^u08&7GX`M8j4^X+d%K>y=R;qvM~;2>^qqHc=FA!X8{;_g$iwH^y7BGX{&*!oG2$thsdl2kiZRo0GSm;OtvYuv~4iS{+8EA~UQM5|E&`PV|X3 zZs@%ez0t-sx!G{GPqaJU{w?Fe&w`;40K{S_j4g;1p+GV=t%KXUfx=AXAYZTB>g3kI;Z(&819#XOmh?zcVpTfk&U({0v{<7DFK=#@4BB42z0@olWDh zXRjfNS}ICo>K!l)G3U%kvMEH5K){qDNxT}EQ{CuOqa~O-r4~YvDfTvv_4X;w-hD3@ zFYho;6R!|hF))mo%8VJf!x$nXDF&M<*|f&8tmq2<{M;^YI=14(p(VxP#=5h!9(kc0 z<8xp68f6&JwBe--4>Pq+9SS}<5(hX3S%xr)o?_~WnIMfG$;B%>yz|4KU}trbtJl}8 zH=P?(=SnR3%(<_FiD-@ORi#S|L*XBri77e;#*8x~pk>lSq#!eyB1w{UYus_?M>+Gp z5Ad;j@8{Wmm@B(Cczidw=ZohkZuDv7=|4Tn6Tdl+AyQ&d3n|`^Gh<*faEFvg2^R<> zGC8^$h(6)KC-3_LSM3OQ-}fa}%PrbC^1>63@z;lbL@m%$^bk`|q9-YgnaYd=t&1rI z5#K!Oaz%$Rd# zBv}L$1CxV^X$o$P6I1K76gSolGI-**=ec_MDK7l%$5Ukb{{qSDA4$3ljy= zLWH12kV<+tcG^TOMsa8X%0v-_8yN}i^d4ja7EB>hf#IZN%v5GlLMb8=l%$dfCj@fQ zn=msnp+#UA6h{(c?+mpvMNo^=yWu7>o!%$hIKX*5OQ{-0OQCdnPs|+&#wqBT^n?&B zmW5$qNU&*1NVMoIYDE~Sqo4%WXjH9j+a72;BIUt zAwfjq4hkbfNJ0`R=zYR4qKTfd0tyg-*yr42z^Y|#H$+CzGjU6LBoTpt);jB+Opzdn zOd_H8q)FgTD3B$!7={r_y2KQ@4Xgm;Rh63xa40fvsb-{jg#dE{3gEcI(v4|qU{Fh? zWdp=e9LYe?y9g!AVSor~fylfCI0TG9vafPe0S-sXQ+8=gJKa09tn5w3m7O(<>P*p* zU^!G$Nik>Y!Qce}Jpv4BskCTBj;!i{yk3ix14n>eps~-mX$!c$y5CyekGR>us{|Ne zfB*^1c!l^EH>K2i=3dXd20RC>*=OA3zzV4RZ*^b;B>Vmel%Pwalwo+K00000 LNkvXXu0mjfD_$)g literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cd50735a8943c0b63d42b33064c9411fe2900d5f GIT binary patch literal 12007 zcmVF5r^;0~J>mVMYDTT0M@RyJ-?i`ObLUn~SKsih-*e7+&N7) zhcVy0cOPH=;v73(w4NeVC(JDep2?$0;@+hlWzo8S`FcKaA9xePR^+@?=*-XakUop(U%GGrC_Twcy zR2&iqK0*kj6bLCeGg?N7bMdM8nZvd5(S92pyECdCddBVXG5vjqFDS8jTxBgOW%j$Ycj`)O%y$7n@i zVV1)D41TqYJa6n53c>FKTR!yHPyFN06mICpw^)Ax7R;tySto#0hU;o=b_I}!6fBok5@6g8dBr-V? z=@t-@(&9X`Q)if+8mCsS00Ltz*4l=}6@22Snp+5~2j1qF9=~z9pGC^-R#8A$E8tfb zh=U4I5HLA%fcCUu(}qpd>b35`4?ptL1pC(O*EmtEKVmF+i3|t_zgA&>W`dcCGc3$b zVK5kLF-^-X1RBJhKNYxP^}zK(&8uJ760a!KJ~l3KP|Xntly`Na1)t!q?f|?eHQ2En3+7o?8F&@AVh007L2lIjqaTf zpI%fy@i1^%Rfg(8BQm&d1`}0VXD9dnPTz`4|8(hRy84E9%}pG8#9FRv3@?KNQ*G@9kn{cJBSaJ6=erpZ(+4Y|iDfziSk4S+nUfG9Bxfn_l41?%m8zjqmqeM4IotTA7}TCAZ78H_O)BvEY0FU~PM(0APn3D@_- z&(&(as8ni{ibYEKX=?dVv|q$EAsaXL@|xFO#kx&>1d$<(G;wSItFZQ2srFBP=hxmc zpD!FbJvNaYo1DJ#d6|Xc?6EgVCxJE+Azh@Cq_=l9rDEZIk3R5~4_$l1>w~BNhIuFO z7K>>>H3nrY)=DfeIX%s)!M+y^?0bIclD@hhUgJ2Dtpl4_zhN86WS0EoL2Bh`>g6f4 zR!AY)a?v0y?Fqhn-${(I2*F)DNAl08+TZ_;Uv?9T#OGq8-dwI!nV6oVTCeHn=g@?^ z(Fzx1V&b5V)|$4qZZf%Ur&g5V_D868>fl$m zY`bXPk#sKiW{qHKcAn|EdGf`wea>(n|EKr92;m@vixnP9M`#@pYt8E6jR+xs``)j9 zv}ft>eBtBoYfm>Fs9wwdZ$IWyjy%Z`eW8yk04v1AutTfeX9X|-s+MtYK z{l*T-gJa8{b9B$AK8ldP=_K3f?+VDuGDnV|#+8E2tGc=F(jl??(DCO6*CHBXmJUc4 zAsph!C-f`Sy%cL#t>V;){R7sRKZ3^W#&KO%t-T0qG)7yDF<2`w&1{IJ5mUYp+aLYL zl^sD4zTZoA;-tGkdRVP7QH@%8hA^m5tN8>$NEF5RK{E>`1QxsT{PP{Y?+-jTkv(j! z-Cz(@@)P7|CRo+u@>~DuI>LI0Xk>d%K4;}x*o|cA)^5BU&vOa=GPY!s$M1dl4>LYgFV@b2F^ob}IvG zuA)*6@I03&s1o`Wv~HRO4OR#Q4fPre9l7kco-N4PBVXyo+E+*~c}u2qiz;jEl<5PNwrpHZg!D;p+dFlbH16h#{A{d z_x0$3?X~>kwb7|lgnmd6#`x8cpkBkT*NCGC(E$6b^XTp}(ZZJz@;AW&@zk!hMQeo> zNsP>qOuNKE4XqW%>T~wC5ts|G5^JHSBa5-}i$Cf{@+0555qO`q_U1&ohfMb-RB4p* zkuN`$y%G|HXqX-v6PAV<(pVE=brlyy6lumMrYRK47LIYntgii zH~W!PE=DVjiWQ+B;`;%9y-wu&SgoC>73W_4ir=0S()-{KgKfaI z&5Y8y2QyEpwh=Kj9HVH4Vn#&>Yd`qO552zo{A(3Pzq`#EeK?Wnd|R@0HQDYhIBtrl za1yLKcY8tzuohz!puonUW3Vy8Dy&rq6ao!aW2`2Z%W&H*FQ>J&<*bbb`1qfEcK`7o zuCw)i#9}d8(b-dD#mYKEYaAk_3BwqzEyjQ&W7;zLM}Mru&6O|xl@IKD=zqNl;cPey zSV#wi#2DQeBE~j=+NX}%*rsYF0J2u6cdRAQGW@;#iXv@l&y5o=4VKuteEM`0J+*Lz%i>1JqV=RDWp z_Sf9X*Z%R_EanUGhd+PlOXojpJ^aCT*Bja%t2v^=XEKPHmW16#%e`m1y9;-`o8E!J)7KQRkDMW2MGrf zp2ZuyoW)JQ#-|Uj=J)>albjiwKv<123fC39_Ej%s?dqYAF87=XZ{d_1>9T2a4?}%LR`yQNnk@rj*Q?)ic-M~&_mUUAF5Y_8t2PSh{<#niQaBASX)T;p z>=MAn7=)B`<MsIrtDFm@iGcYiWHu5t~PyL|u(yp6q#Yrp$8==D@CM;s& z8rCSPqQ&md-V){0Yh`y^!s%>Fi~hb2Rt@*j)0HFXfF~^p2l_6)8Fj;-^1ge%!FTR^ z7_7z`gXc(I{*vo%JN%vZKh4c+`|x#+7}{7)_w z{)v`()^FSK4$n*WIga;5B>G%M-!^gxrZIZSBpiBjN$S-F4(#8_BfIWra$=Ou))X`O z8XGoVhPC|su7AAq@2wSE5ebCzR0YVYkhnUHuFarp^B5CT6|MGI?xh;XNx0GqM}jNC zkrqcnYfGB`zAiFp4_8`TX-RjjVEE0S;qShEf-imLTUZ*=jMnj1NAN$N{_w+p1_`2g z5`XppQYMhX!*N`Mm5d!d(g-qyKnStx)$cj9>n92qU|@LDTzB7!|J2ph=Q=`el2TqH zCDYbgu!h!@rCOL|^z>=Z8Nb6AjW!_&i8T?r zK1WJu`oSBxwzP9 z8W93VJ~!1quL5Ov|IqP)p^bY4;emQH-T7)51nBMl~E7jm{WHipK2R z5jYa0Xuxi3&5}-eIMU!sL$YTB>8swxH~!BG zJ=`?h$yEbwT-=|dE1f_}ac&pQ;J&pbgXcL&VR59v+j_mq}0!BKil`QmtzyW=p8zX;fIi=z7C0nno;}cUCgkk`&!7DO!>) zq0)rPkZ>ezDG!Z3XC7ozy@ICzI}#)oM;PqIZ((9|j*}x(jf|L5bgjJTL*NnV32M`O z8@AS2s@lUK8mg6!c;w~(?}1%E4Y*J9wwo@w;h!z`k!lDlhSuO{3)+Hk!EupZfIh~95;dMcsPznVRnjA zegP>Yjugboyyux1{G{MEJF0M+y8tMcD!)^&R;e9(oYs1tmhwC;g;}zdJgt6_HsjOd zYO48ZGD(jpR`d<5rs_u=J~7&imm3bp8ij8%bfnY55fJMb9V?<(5vvB=Vi@BzA6!U* zXhM_%WHZT}N+xk+Q_aEXrbf5Bu8ZrrcnJ^Jb4eszJg@P0 z8K*#)%96BGAKTBWfesug&_^Rwnb%Eoj#;kkryjKt#iTt)H3=W$y)Xz#rki=wu$8yyt!5-m9AR69*EKi@nj zl&bOXoU0-{9j0B939`=@uDHOyRQcxc?nnNmwKcb0#R}JTa2yxUb8sAq<2tymi|Yuy z@mW^A{PondrM0sUA(QNVa-1u-uD}{i6en#`_#)vr=qNy&MpJ#}x-pp^hPJ%~aEa9{{yAIg;yMY0kc=EXbT$WpHhR|^e(%W# zmU(WwjCrun1+G};xO6}Ftt}q>(tpmR*BgDp6lQU=bVkq z%}kT&8KP1PSiNR5`;HytqIKQ4(xNri!_glK?0tJFq? zq3PU&z;VEFK*SbdVeQp7vS!oOSQPdAUUZN@r&{TF$=>6I3}ATWFEnySv6{!9c?`!rt6C2Mj2u0HH8BW_XjvOzX8O1JVd+mkbE+_w`-^STAym|Qh-~Ww-m-y96ZL}|}R~THop?c{{ZdWa7+6Pw- z;HFZ{OiXa#k%yR_o+Ovc@N=(xGeSVATqK)qrCbeIzkVx^?tO&yLoImP!}9`cSjAX@ z5iVMIXlc+AA{s!koKx!A8-5CK6FAMrW^1`?9>L>T?*>m@;zj)7?WkmNw?8GxD z)&TZRx2oIr9m&qk|NF@^E%hy%Hs0+A{>E~tthcQ!Z?Mr!Y9YU1HeLK; zDW!{a+@|f)SS0z$13a;NKN~K-j#K#rp&!xPJ3!fwah)Vj9@$H8PI1$Q0h>zXL}FkQ zF)ooz5Xl6wlO&Q!B9S1l4w02;0a0iOLQNQIf=J_sh9ET5BaI(uiq)86CE$1O$g*vy zi%1n%Jn}c#sEn7$kW6Hl9k21|_fK*8%|Fk^YhHzy?FSn&bNHJaIk1zp!z*ZOYeVZ8 zV-!k9aWawJ+u1!J>-F&NmW{9bhYJYPy3at~-tqD^xl1?pJkj0NzP6*Ije()gcPVe+ z)co9hT&|SWP*-KZ6;3iT2G4b*l{vC~+i;RCn7EFPD>%|&*8>l6_{mXvS8Sj)GsI$S zC|9baGC3-K#L%ks?BBPC>-t+oE**hi#K;uJNueE!G6LmDl$7Tx9HM!vqIuWi-0Oyz ztN`H%Hm`(jLoHZq2#SX={vvL&1;&Ma%Jco&wP94JqrLyD^%H;hp3H_fe0YgXKBs1Rfq2aTY+c*&y`Jul zwY_~^WHYUJi59O`n(=DI@#Lo7(aqi2`5wPEFJv;umg|3=u64KIwX8sTZ3yKfYIBSo zd6d8Z(ig~=eGVU=;JWLsrzM-DQmtT=LIBBBj;^jg9z3yt>lYDz85>s7k&lVP#>yKN zp|rwSbM85WuuGsmwQxi<h%hHpV-UEQ~N2ErtcZu`OzDf zi1M6B-3x^kzwyQ`m$bKKuNoNap}no0T&@>uVum^whI>hVqSMzOj}K{hBQ+T2V$56po^63kWw& zDIc@%fl=aQiq-47NoKl;@<&mHQH-jgRpSdiP#NMICkGl69aiw|AAE!M(U0>>zx+zB ze(~!$^w>WU1U~)!gI@)@&yOhNGGGnBs^P)6uNdf|r@NC}u8UMU!{~_vm@x0<+ImH- zEW?{`q^+%+U~&)s^keu_k5Zp}l(0C4RuRc`l8){!23HPp>f{-&x$a8ButIt2aklp6 zaipSJtrABeL8NJI@8&yW0fApbg+9jWrtP*o^`|&D_|6va&sKqr(HNx~6nIhWRLElsJ$7etgYE9h+K$#F znWUuu%Adnu^G3e-f~iY=FHCm~{VWd>_h;~`aqio*nbUz+qzjh=8P&WsyXUuYQ^=%rSv(UJzoDumYO>S|i6 z<{mLSH_OQAIG10ugYoextaa$>ZsEWDKW`zKbWw4H(J>x|P5p+0?1&eJv>_sbEMLy6r;W^;BrnNOK5r|mF zv|jQbI5T!1YuEN80N)R=GWDAKzIpGbU;fJ5_MHt`CBWZ);cqfW5AOSz<2awlFD|AR z=I7PIVp)wW^qo4j(D!7Cb)$WKU1+W8?d!#LGdNDOT5A+ag~TdkVseawC+8{aL9V#r ztz3Ei8xS~DDKBP^%jz#|z9aR!OED zs?{1Q^3h7u-oKHHwqDH4%rwc4VXBd_fie^VWu1g$vn?r;PI;sf4sQ1v0zYhI?ZS|B zI*GA~!IqZnm-p}4{oc=f@)KFWx+BM)ycYOWA)ikb^9!nwFX%#{Xp4oSEfz}@iX{dz z(|tlfduIo^_I8q~Zmcy8rK$*}Bc42Zoc^t^<>i;YxY1})m?o;1NoR9pTH4t0(wpcX z+`yqDd)T~j1Fopz*8>9IXD}yuY}AlUrHNccrlpfz2Tn1#W-U@iK!~+TwCfTXMQj90 zYr-HV2qQvY5rm2`XzW*BnZ+WAb(zwc#|a`qJBtPEXg{Uwr+J zS3UB;{cm#DY})eZzTLb2u3Rkqa-mQZ#X><83q^{>f+!Trwood|Rhd$U?;F-`?x8g| z2-c!hgw`=yMI1SPg4I{NiPak}KWEOf9RnDpuokM-BG>GA1N-)T9p4vx>!0sueDo-e z8`ss;o>h+hjRSL(F3hGa4+ zJP&m2Zby)5NfU(;)moigu7ib=TPPLt!gXcYTJzV}-EiZC z3y90E*?#9|Kk=8}io@{3g?xUXSSYAsxnfG?vQ>U5VO8L2;gL*bNvE^6oeQfI*CmcV{^mLx7xe7xUU%`uB@g_QZ2eCQ?;jkr{gRqLRibsx3 z)4QS*gM(5fN@I@`MixJ?RBHwu!7to#GfB_I4@1W0W|^Iyr?+PXuIr#xjM6cS<&w45 zkaS?77TO?cI!WbasOF~#BB%wL7Lmdeuxr-|Vjbg3!(e|qbuXY?snFh#+R*Pv{mR@9#z)3&RUF~ zqC-h$&g3Z;$|PM)yGOCRcgZB;Y|Ja~{oehL4fOWBrZt=W zt%MAIy;u>LRE;PyEEbA*PNOkHc&&s{fC_3Dr4f$e#DQNq0seHqkz%2l8C=qMe@I%8+%$d~;0-t(Eq zKip*JEMeGa#=Sr?oawoFf13HbE?U)kf2Hnyr9NNooSa#}m6k+8kV!gZS`uIkDhfeq ztOz)L;tZA+4jmpvipEUUOE|Q&w_r4;#6jp_L^H@|iVEm=vfXA`;h0fNa#QMEaA zP(Z5?Khiw3u$r%pu4Sa!#%LwSnR+)PY~`On`xgv!Xu8{zcwVFJHI;PO^XMt0?-Nxu zQN<8OlBl9-=})j&sjzL+YU+MS?5t*Sc9c&4pa{!T)M|Bd{&7~+zQgFq#65rV>0Pfo zaC~uoNo{S}Jh-z2s92`TDFIomn`m^ZcJKPty*q_4x7O-Grsn(9>osa&nuqTHIz2r@ zB)k;8Lt7XfnIe@*vti37`d6+aRxycmH>F|`ZIcx8lXyb&mYc6&^yDYT(e^%Q5Z9{`9@U!Bvyx5qtH609%;Tda|si1AL*1!Hk}~pHAbWr9?3N~vGeag z&5FJxt*vR|Skc|r&fW)~q%!9dR}Db~p_PbilJ-82Vj)j0hL)BrBH2&1G)K7}6BR~D zR*qsyBd3p@T6pyrzj^F`FC$8^T+z~5rn<`#uuEafosXRuTgX>F+uf0A^Mmk;Acz?o zAEBdX9sBm)&*a1iSV1b+j+jwQcO>uVR2!We0~n4G_$jlfJa;BFw;{bluCKte%lok=L^iv=858%D2fTAkV-9} z62!dtrft+}b=Iukf@k66=~H-~gTQby?B&`kU&3X>t!x`?$0m|Q%^a@P8m%>PtdSjS zQOCbYZ%+%Uq)Qw(mP&o^(NS{})Eua?HhMAAx?YH7xnu;6Wps3cLa9ozT&7s|Pahx4 z|J>K^JN|b|oYrzBcDvkr=K@eK1o^CzohMJujxFYEpUbAbb|K~E+9sHrn_+Qro~fxZ zlrp3g0Zm~!Y0@#j;Q+)K@cI5Nmi}t!F3Z1t?Xy- zzC*ZPl1Co-9>XhEFxPc6E4x|=wIw!&xmX|^i8Y{=LFt$%(!|=5Dj%aG6_Cv)iDSj& zLdAY<_h_t_B&!sd3aI(esL8yxQ;WtPA1lb&*|`sYchA(DM<$BPsr7u2%R+XSsah^~ zv`fIAh3t$@=BuNV`FCBuZSeo(GQnrFE$y8h9V-yl(9zjR7zHdW9w%QYu&`LY_C876%(*J9h+~cK$CT?K!yCG&RedtOX_QiAvN=|7-ofnTVHTn+ExAsD*pPAs z+CmmX=tz>OW;2SLz;z=;5aV{P!F5N7qKGKgtXtJCmhtRo*Un1Q*bm2wUq3aY4xd-R zJ-<+Ee-vPMnTCet;F_}yAm=$d3&NGUU21WoF_@b{{&t)LAb*J_-B^aOk|!mpzZij{<7XPnIty zdHE~=vrKD~m|3j-V8~ z-+K=TO+G)x;`}_7N}V{4kWfQMa z+Wgh3zP9B4W8;*I6;duF9Y{)WF&L$|;@Ybaj%8;0G^b9U=G5`iSR```^9=O$HdC95 zHa*6XVn7%>)DsRu2((fJenb>#!cY+ejpTY;c$}`DZYCz?I63ky(k&f4ad>Pg)MLPZ z1r9EwR-R$_KP#{-W3a_D02UX3e7^lUH`A-sqJIQ7S}Q(tVtnCcE{cpN$hd-}v?PS3 zud9`Tz78gj9b$H3f(QP24>}u@%%mxmEA;hrHey$+@k|k~a*{$kMACDRqFJ2?BWm@C zdZ6$_jg5U)1iSEM$b7y`sTNYLO>%0Y%o4TUxkRljowhzpkmebj*)ulsiVMWK7kYpG z>+`G0rYGyXHk%NiYfF1_u&13>gI%m$)q^K2nS>;rNFb3U6AAX6JV`ySv1aR5ZoBz< zq9`DaLyQV3s5W-$S0kJxQqt^>)C7T|9>mn^5$l5Q(4~(tHa1SV5>lx}96Y(e-X|C4 zfVTq=o@XA;citBQ_-vwtCI5QGPv~Dmd4^C<0@P!BG&c5Q8&P1CsnkQN^=NUi7`}dNrus=BKF>#7Xt@66PnANM+F*QBSVxjt}gC`bWfAD1f_%do;3er4B zsLx7t&hv!zOp1M`Z}(g`iXYo>wxYh!k?{_LybM!Vl4EmXs5NmjWw%>SSVHr zq8MwT9zqb*uvSv4RH@V=%C&%UHKJ0F&_)tP;lk`<_4a@G&asadE73El_4%muS&wAr z$5iC=1%A$P&R5lw*7DJ`CoUI4uCqd-jV0aILjQ_Bl8H1=>^#K-_wOf>N|N*p;884> zDU~XeYZ29YNX?I_`Vor@`A}LGb6uW+~pJFvQ_u=lYEbF)Q)83UuUvQ<-!Pr9f8D#4rHv<-W<89*UNukaxL zg$3WB0^trQFsA~Y_!1WU=QpLNPV@=Gu;6Dbf$%EEP=hkeDOiOa5Ef$+77Rmr;>1H$ z`~wTtL+Hdf3};b;oH@BgcppvN54rWR-)~mc|H;~|uwVqrsNpOO>VH~f8`CT}USr{g|L$DX+g%eV$5cp0sb9Vp3M3;S2K`sl_MEMo$47AWI#cpJlllH5d?xAee~%{O2f z=OJf-GA_g16#5c!=fnJlRvxKefn_wI1TT8_cWg?`Oody2=)S|RkHCUe&|LCP3%=8e zLvJ)-Ic{PMd(aIbnP>LmjCWa%hI&8RwgpzAi8_?1j;Vy+SJd<2n_XzaO3b1Tm8ko! zm1v?%-3NCHL$Cx*^gsn_zGDd*kd`*y5>}uEORyIzuqUyjb%7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fc6c8a258357de80221a574049b4d76d2567c74f GIT binary patch literal 554 zcmV+_0@eMAP)J|P2`b9RP5OWN%HABOACC;y-Eyb??!i-XjG)T0}~m9;he|Jv8n zz-`6$7i0iS>^kpoAk7)P#A$WNCWrqi$9@0DANKqo zbI|>N*d8Z1_E=%|-*1xRNz2LF{{yBu9|6S>*aaSQEdPT9Lsq-|58h>u6Pry?|Ie+$ zZp$Ib>cJ()rU!EYpYt@c|Fx&`{%0Hv{~x%+_P^62!~Z7zYG4|~hhdNy$g3cmL7HJ< zAYk01^51`}H5fZA(Esl@!Rc_@gw|RR4dTP(KftQHUs#L+g0xxb}9ddVao|xi$H-2qLIZx>ae8)E>0O%b53biUm)!d s3Lqe@3i2a}hVuQP^5!7*AdN5#0G@%Nl7(9`A^-pY07*qoM6N<$f+8agumAu6 literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/extensions/extensionGeneric.png b/toolkit/themes/osx/mozapps/extensions/extensionGeneric.png new file mode 100644 index 0000000000000000000000000000000000000000..6a76774c7bf6059748b511207414b34515b7c6a2 GIT binary patch literal 1302 zcmV+x1?l>UP)atVpz%s>@>I`XezltQ?tu8l#9XUz=C)| z0p((t0g(*pFj*5T

%^HO}<8nXDLUCj0XAJZsBkT~=a`6EpuAhI0=8-}}7JjR64r zKeeEbmc7lqyz^Pvrs{y9_K6*|DS0dRq@oW0hbSDTJzP8qf`@VQ2Gmuq2C>3l4xMcTt^D{CeSs z6L9g)Dd_ys04?LyaB}Pr7;YAT{zg7jT;C75ms21}=W$JN0IyuOIPG-8pY7k(&1qgb zoCya8k|9tl8xb7fNez?u=JB6sdQ|YiuofC`mCiCR{3-+XUP=N*LkPI5S@k1HLU|5* z%<}+KrnMxDRvHh^I{BOZ+aRjauNTde+%g#dNC3=w(Xg(r=<4n%bM24{GP>g-KqpfQ zR)%=zju`f`R~k1!#<`b3)w&r{o8#d1wnWIilnSNBtSR&SFLyxJKq72AyB0Rq`a?vu zH-sIL!|Eyz2q}LY0uQ@^Ux717)4AhLN+zEd0Gb!P)2D{!vG?apIJ0@q)g;(6r~vhq z7*GvFfwF%MZ2vR}w)Q>+FLe7r{KrdS7zLzf#w zwoAe5T@lO+fWj`M`T7r~5Yf2^e2T3AV3TpBI>)e!%zvXh7ur0$lb;QM?bY*Xt#dCn zn7gS>aT7jyd>@q>J)?xP$2{hO3?f=wA-K*FR_P@Wa$EvoZ59ADSg5ymeSZSlu)}G( z(*f&z2R)-~x98-Mt*)@VMDmm4PWCD)+I=_IwY*FOvV9KI065i*&7=CM-hT9JEu^<9 zZf$9ZIGNX?thMgi1lFrvNb1$7!mWdposP>TtzhWWjsJ>;;=gotJ|D zVevgQ3$@vZXr9wZ6*}9)+fCU%i2yR&08O!zG|z?3_I2$%EGrcKjsQ}d0JO=}j}!@B zY6Re0BKiXXmf8e}(}&zXGMqa-Qin2NA5OJhJqeKgS>l4sO#mG{tf&zGjR0Xb0YZ!9 zgS1AOPfqiEeU)EdaN!e~$>+irWNucVK>{u+u3$zL$Wc|OhA9ACAaiJkqcv(jZZe8` z(=xG{p3ziWs5xnE3lIL~;@{lVT&Z&^+v}O@aAReS7(6vI`T%aP&bUBU25CKv0b1EN zw8`Ky;YiPDq&}?FDBJ12P3o|K=(rCNd`tpC^%7WpT4H+7>MXD(n&;NqD|I+(GFaz- zQK@0JPhi4L`d5hWQP|hC+&8xtl?P^lEzvSzd2;IQzQvL|c#m1AXHXuf#gy%+aJ+LJ zg8gEGuor+f8OyK-)tcICvaRpjc5x*XcDn&+jj~L*RW%X%imdCg%iMafYsTvE86 zP=gdAJElvG=D8n^R6dp+#CHNMwP*`#qEaIxYvyz{cz_X;*TVqtlnR&ExX)>aqp+Dp z^PEp$TB`tCSqIhm%=gXm){3ylaMuws{s91MS70r~cFO7B|4km;KZsJ~yoFSdT>t<8 M07*qoM6N<$f=p6r?EnA( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..655f4c4be795d4c49cf842e4b8858e74eef816dd GIT binary patch literal 2949 zcmV;03wrd4P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z00026Nklr0qoz$^?$|PW&+Pwz=X#J7QPFnQr00000NkvXXu0mjfQzLf| literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/extensions/localeGeneric.png b/toolkit/themes/osx/mozapps/extensions/localeGeneric.png new file mode 100644 index 0000000000000000000000000000000000000000..4d9ac5ad895281ec1fc1154d24270bad38e21be9 GIT binary patch literal 2410 zcmV-w36=JVP)+aI^SsaVe%!tH-d%fbkA4mcgO&gyB+4YBU|h`Rz=2~UabdWb#rg4vQ;g0` zr$$i{qnk!?$bx1;m|L6!K`1g5kTSjsT?_r7rPp5Xvv>D?KHvU+0RR91004p~mxn)E zM)A#MP`hbVni}N{a={>14RUMUUh(~Yk{&ITd^I%mikLZ^y^*Qm80000400000 z2wv)^pADPwmwRQg|+eSiJ}Pn?@~=Ow>yEbeGW z4UJk;7{tOLP*#Pq%AM_Ru`&uI#lY*QKVzkub1fNC$01*HnJlO01&Z!M|-Tuf>T5mAMrIYO5ywep^?CLN(MpUDh zOfy&lA#~}z&{(Gc-lsO#`2I+@U6Q%u>Q7RnY<4rKn(!FbMxmvT%VqP z=<|>EET%OYF)j8_vUiG0Cb@KyJuPM++YhX!VEUGn5@&O zGf}6Qm^Mmg)oPKr7K?&NEvRYKUM2bqra$m?)B!?3aNzv%XJ0*A?Re;a*9id?00M;6 z5F$o^C?E)cFl4FM^iTiy%R&S}APho55D0>R`X3X_@AC(TKZg)PK(jTq`3nypSSF~b zDk=nQWDIh8Dcun(1uHm@HLQ;4X7n>wE>x_I2%rR&3IK@iBcAwG)FDFf;LC4Z*DqZ0 z>>C%D93wC3rxX`lT%S|l=sB`@nbzey)oTpLXvXw2=DW=Icz>?%wtY|f)U|I}?56rf zrK~DNRVb@cS(Q|X9>Tr*-0_c>`_|_UMTjQ0ZU33)-t1Bq6aj+^cJKX9*M9JE)A1&g z<87UAo8H=0UVZmtc5ZKK)JbxdhMev)(Kv3Z^{#H3sKTx!$)$OsC<}R+%gT%rIm(yV zy6-j|jt~XSu^)No^agoOlAGCozu8?cN{a1h+P$TaFm@Q!l;qanoDJq!lY8 zPWPEiVz0fw*V{|m-Ehf+k}{E$iL@G7dH)vc#aTN4L5|`u#pTx^ju2HrbLZg`b7Td< zIq}v<^{ZLUa;RQ3#93V^6bN+MM@?^i#q+0sQq&Brq%4gv-0!7hd&mo|_h@iTBZU3DjLJNt$f=*vFZ)_qqOUIwW3`}$N~Kbi4LdvE zc5>w!WjIBQ9BGDut1f$1vw6fuwxOSQojH6fy^5s4q0g}QOQaP^5+aBoDJZj?VZ!`= z8-rM5Y)vdy3u-`BDyl+KvOs;LuDspMs@l_y&9$RLkx+4Lm+pQEZ! zk`?rtp8D=r7^UPHRY{g`**DnyI2SM^EIZ0P1re%^UNX0Bd;6N>M;EBY9Q`DFziK%- zuc!)nnM;bqC>j|Q1NE@38Kyc@hiokFqh6Ai6eTfA6eW4WaKz!8SnN_i?xN&IvY$0S z?43gup|$`SL+@?p&()e)3!il#EJk&O%UagB7=Wxe09ePY_S z$qBQawltp83JcSAY85*@cjO|@y>Xx8U;8#zkwXB00OQyhJ#hYO+EcfD=;&jj!Eqz& zM!FJxxuIO5RG<=8O}j>4(5dazjAP^VSi9M@G&*f9c~vo*_Vl|=<`4ga!(aX@%wd2E z0sugW4tDzD(|5b_rr)~i+z(uG;Y}kwxxP#ymns#S;bwd4w`$co>QSs&i;OiI##*sq zmV4v$Dc?DCg0rvO@5rA$gtO?P1PA~CP(ugXeC!Wz^i!YzJ!%)zhQiP#i^CQKVUqymDsVH(xt!eewKhC%^lr&OP=#&ZCPG00IC2K!`ESV4Kf8 z@(H`IyF<{}y?xZRE$`VKWcKa8Mi3AQytBOK)anHX&n`6pHRCwC#md|PvQ544SpEEO={4u7u z@dX4$TTFwE36Vg=V9}=f2JYO7-Si2%@;SO{%|xk=5GaH~Q))p)s(k}N+6GsKx%Ya` z*p-VicW}|g9OfGshVQ$-IcJ7D+!(_yatU_9D+9~`|E*iqMq{?x9N7BELta}SBnNA@ z1edD-ZY@pYC3tnlbboIC-o7?!?aRT==)VBY&9^#2%9oz!*8o6E0Hw9!wtzX`(?Uq2 zqNo~0(Z=mN?R~A(t4|+C!&HFPdb=Yytmg9hS?rny2}(#|xR5NGPOSkQ@EEwRF}6j^{DQad2FaV#E-8 zDTA^old@?IT8q|9+`;iE9Bc;feg|*E-p}LH8;o(rsb}%7!&#c$$8lW*bSZ>U7)3aE zzA|(5YwuAvc-ngy+Kl)J4L?wO{WsylU~AYrIhkDld&{H)blkXu^ng^AO8IbUu@fvm z?vC;D8){%;c)Wb*48zDOwmA%-lMOGk?j+WjX>-Q6>=({qi~ zjfAS88XLj;7tfxrj4XVy$U|PrILk`p<0-~9gQ9WoSo)f%L)5X2iD$oC8DIu@e*F<( Y02Xl?bhbfYx&QzG07*qoM6N<$f)@l6EC2ui literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2761f19255511393ac671c0d075d4af2f63cf316 GIT binary patch literal 1559 zcmeAS@N?(olHy`uVBq!ia0vp^f8U}fi7AzZCsS=07?_nZLn2Bde0{8v^Ko2Tt~skz|cV7z)0WFNY~KZ%Gk)tz(4^Clz_GsrKDK}xwt{?0`hE?GD=Dctn~HE z%ggo3jrH=2()A53EiFN27#ZmTRp=I1=9MH?=;jqG!%T2VElw`VEGWs$&r<-In3$Ab zT4JjNbScCOxdm`z^NOLt1Pn0!io^naLp=kKmtYEgeeo;J&4sHjE(uCSxEHIz#UYgi zsro^w#rdU0$-sz9QwCX8VC7ttnpl!w6q28x0}I7~jQo=P;*9(P1?ON>1>eNv%sdbu ztlrnx$}_LHBrz{J)zigR321^|W@d_&tC6dbiL;}tqm!|Vp`nYLrID+nvy-!lnX{Xd zlZ&|lOs`9Ra%paAUI|QZ3PP_NPQ9R{kXrz>*(J3ovn(~mttdZN0qkX~Ox$j9#%Uf@ zZwhX=xZu>Q4|I$^C}NQ!8YToxJs>7L*#bH6grAxROzlO$WbLJRvWJ0zsoT@VF{I*F zPmr&Vu%pPmpEq;4f>aa^CaiYrxY)$$$Q*ZtMOD#_f1Zh)N@h=|L?`>+iZ4$hh4&U- zXg)Sy!m^|DBTJQ@XisaKQoz<3Q?^a<+HL&Ue7CoT@1L1xr`Nted*=JSn)efH*{gd_ zxppLKEdB4Y+dT1Ux?OEW#-G%SuNsOZWQzM!XSV;Ti+_J&@$$d_T_351-72#4y2*Na z)1)~cf6U(go_#~$e}AFxoL4H@7OcK;Qo`HNFZkn4mv2{J-1uleal*EO_in6pF?@{+ z@0y(To1eO__?-B`ND(KtrrlAu!-{@pXnn2q(%dOAe~w1pq!O98%ck(mp5tw!$ZmaD z!q@mzU65O`!oOCnnF5OriY;>Gx~Z`=MhP*U{j=)HgfKaQ&$)2-fql6roy;lSDRBNw|bdAOIN>hEWgAvMf8en@VUtv&AErf z;#AKx$esMz8g=97Da(gdJMOgftvVaUf46Z{?qh@1rOyroNS@-jrF*sii;=s=hSnvE zlJ3qFJL6EcSB1Z%?eMj)3s20MH0e&Y!6nhzzt*I$nBb>>{XxcEu}NO+hh|!D$h*74 zs&>`;2)2dm9o9VenEvs}&)56X_1*oSd|G{bm8ZwjihEWo+0O{;2XDR3P{LsM;I7+; c?F`HeDlX~=R~8U}fi7AzZCsS=07?_nZLn2Bde0{8v^Ko2Tt~skz|cV7z)0WFNY~KZ%Gk)tz(4^Clz_GsrKDK}xwt{?0`hE?GD=Dctn~HE z%ggo3jrH=2()A53EiFN27#ZmTRp=I1=9MH?=;jqG!%T2VElw`VEGWs$&r<-In3$Ab zT4JjNbScCOxdm`z^NOLt1Pn0!io^naLp=kKmtYEgeeo;J&4sHjE(uCSxEHIz#UYgi zsro^w#rdU0$-sz9QwCX8VC7ttnpl!w6q28x0}I7~jQo=P;*9(P1?ON>1>eNv%sdbu ztlrnx$}_LHBrz{J)zigR321^|W@d_&tC6dbiL;}lqm!|Vp`nYLrID+nvy-!lnX{Xd zlZ&|lOs`9Ra%paAUI|QZ3PP_FPQ9R{kXrz>*(J3ovn(~mttdZN0qkX~Ox$j7L*#bH6grAxROzlO$WPL6536V@SoV zk}0tsB7q{u&;OlKnqC%Kr0jcEWQlGq&nAIQpA>66yB4Ut@t&~ZjjKS5#tC-rSFXQW z7PKsB;%Sn5bsWhp!>h%!8E4HCd;X-twQ`+#(A|4A-)Hm)o@y{s zRa0|6HGPYuisHPu^B&zB&h)ri8UI=s)n-xN^yq>^ph<$oa~r;t9Oo0ak4>6$hUag} z9`AOS?R?fjRs1#CwIN4DMH%%bEM_rycEIw`wBCFvza54zSe?G5Eot+bY+H9MQb5>D zp?0@xZS(oO6|>YWlXSKuNLFt&bBq-C(wNZpYmc&A|1Xtn_0vJ2OtUUCO_yT2D#_sX zC*q*%1|4aqL{_mTK8`jP7ACbXzfL^3!(_eQBIc~j>i_0}+{;pz0aG1EL+de*Gdxbm ztA!;*Cp_SDY&zXy(57zLTKCTHe)$SL^Y;m^=DMqYU9>cislWLAGq#@u$GEN4f z4js7-8+bU{ePr_WJ+XPjAmq zXkoa^z@boF)8&vp-MOJ`-M@;HZ_Do72#el&b^oQg#?$TZzL&mNdV2qY1(6RHb~Qw0O!vY-Nu6x|l%M^Dcj&|^50oUG#U;N`TZeM&un(tkcXXBx$4hFMIU zw^hKGHzoQaM^B%ygw#?4%d@OUTPAOFSs8s;#IZqirpLjxd0w0+Ol{P%6%R#y;p+?d z@wNT%kvH$A9dvO4Hiio;?FFA5pYS6osP&TLg6@iY>Y^l|mOPVxWbBmJq0lsTJaea!IcQE zMBFGsJV!IqKg7jJGIQp;_s)rurs=nQKUxb#awekWkqSg|Zrh%&C`t|*lINs;pfKJQ z(N;Q0j_dCO9O;E+r%&kJwA_j@re&W3{GewTtyV9rz_?;%vuz*4n1v>xG0JiL z1A1N{Zm=Hb*qz`DO;uHwGbj%YQ_W_xL@x-FSFESlwGS+p%S-G+K$@0iP1A$kuwQ}- zzhEHZYdLBU$k2A=sC^(KX8;Vsa@;*#*XPiw2xyyu(Q7o;y}nU-VMadqckq7kQ>9W_ z!MTa9a2PQw8ODa`dEx>GpDEW5FM}|8AnXV6ou+9ET_7J4X5`sOMGF6O5%4`>yl?CE z`aEF?cEY)=KtZZjtEm!h+na~j8)blm{uGO`6QCn}&t%ExX#NXkrwqz-x-X%4D1-q< RyJP?W002ovPDHLkV1kc@xmf@J literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dee75516b771b15d4b3648084c8841afff06ccf0 GIT binary patch literal 1041 zcmV+s1n&EZP)bbHSWfY!lH*iooX?p{ktL(aQ}4x0*%Y+a z#ZJqp@!V$+GmlWlc}qp#evE$As={aj(h4KGLD@wLGNNR3raGh*MroN{Kw4px zl-UiW6-FtUT|ru59YDHB&V>{qU8KN*E>e!MIW00>WT%X^GEWIWa zYxc}joj)3LYQ|ETr-rn`SR(WEkX9J?WDWsog)t{{7)UFOB|krXdPpmbDVf7TT47vv zPNM-~kjjj(XbiW|iWVbmgGK+;rV6ALM)WUGsX$s`)R5^Sg&4m9VDX3<^Yw2|00000 LNkvXXu0mjf-#XE> literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1dc2d8504cbf625fffc9d5707ef9eb459c70fc0e GIT binary patch literal 1979 zcmaJ?4Nwzj8jck`(IQHK0*7_|NRb2bw+V(sg8A7*5Q1Ec6_rd`vI}G&*>tm*kQN1} zl)qeck+zltJqv0P(L1o?Au6_F2$rjVQ?6VUQR(5`(eu=zr84$5RP=7v0q-d9xo+VzK4|vmuMh(xuBxgoTItFg&Nl#;{qeq%}?(YAVDjC>PH+Tjk(h zcMpKfm>g_M&^J13r?Ys(^6!$%bapBtt(^J zzGFUsrXf_J9Q#6Zle^0qS)BA8N5Y zN82eq{=)D76t){mY&c(!+lgY*#1t+s$`{HeQ98iKsP7fl!EOrAQ*I)vCmaQX!%e zX;;l~5yDh#!L8H`7kk0g%*yq(g2l!}MsU)+4ac-3VS%PomYHYwqMB9j85f(~i+WZr zpE1Mt74|<0J#)l#k8kcHpx06g;d!myYSu8((Dxx$v|1#cGTh|7?tWb{* z5C8Pjb!);aTTXs=>W3p7r?pU4VD^D^;mzmc!9HaE;M%gX7cIookgwt z>5=_?-(2omkm_!kvm1Qkhy`u=FN5xHMlu(Y|H?X(65@e^o_wZBF3P~h=d-^{>P(T` zo>W7HV^`UeW50r5i)+5DM?8;A?}H}-l0G*Pui?DCHoC9tva+gnt8?-oAV(yvp2R3P zR7@9F4e+iZf3F^hWyYGRS4DxwioJfl>9LAO5x}@Se_Pd8EwOjh96!3v{WJz^JPR~5 z&pA4fy)pa^APHIi%hm1aqFvedCe{T_4i5)^apFd+tG~dnluomxX`{QA-3W+LpRG>+ zRDPjjqVs0)#U))|1#Tals9#a%Xfy;*qHyKmAgLHUPC9Nf4P62D_1J-*t{Q-Lq@)P4bx#zVZ(w3gn*bGUSA*a6Y~3lr&E-pGq{KKxFYe`M)z_LsZw z^y$)C_AG7v?1v@&RHv%}YrnH^{hRcy*fmjgB0)mi$c{ zuVtX?@ZBvrryZRK74YJEt|0l;(8YU$p@583dE-=9$-czjF6z4f@UhIQfBbZNurx-P zyYv2;CTjz>{_BEU4M{zLZrJ}y!4(N6fG+p7>`&o^ggF94(@)kVr9qjKr?Mj1yXGL&2~;0eMXP06TVcG zZP(0CDNu0gqv3pP8@P0ouaMK3hrHSF4wY=1p_I-TZR|eTIb!}Nli`ChZ!@gJlwcQA zor>xyYCud+F8X%w_X_srO79tZNa>o_$kwY{I8h3ZIpw;)e?ZkcqMGYWf9+F@hJFN_#q@;WOw?~paH@0GF2rSdl8$}J?ny@R#;Q6ZPj#po( z=`gvw0Q>_@nmR7XtC`O!8(&<0J4}B5#=6gUUro!3aPMu`-vUk5198`o*xG^Dt|6fi zT$#?^Ts4#E)MX@e??=r(Yd*2J^2<(~S{C#{$vSP)9F7>$i|sJIG6!LA4vcE>y8hdO zOQQ&&Biy<3ww4+4s5-czHk*xS6Y@w)HeVo>O1&H~%%wB9RtG^D>|Dai z9%oSDR@7oNQ$~^iy^MxTGM|#OXr)grn9LKjg!S1r(Hq9M8_awG5As?XPtLFIBW zs5PKQ!W)m}Yqhc@!b%wk6i-sgSu_LBXvAca1eHK3grkvoF)R?M)KWMBQN}Ap(MolE zyh;L%V^t)YZ^8*`9E;Idky;!hN|3@dR`gt~w-rogy0Qwl7z=PrZ6QtIm}Qyq#acu! z*76jKJx9wkAL18c`Lr0m_hA2U(Bn&V_jsohZA%9ejgJ#_w_E78Uey%E1puG7l2k~l z{og;`VA|@Skdp7*p*NaFM_ZmyjpnUjx+-?wP`O=tw~COhS?kBZe(M~$O&z>{sNr(N z-hC@R8}j&kv#xeDY@oZ!R(gH-;`QMf1NtpC&jenj-{Qdw|_ zqT7TPez|yEa@C$90cYdwzL57el%~1#sHac%Eem=k1$O8R)i;L~&W-cfR&kZ<(1W!ME ze3=jYBD@|=R!)}dz6 zw55RK?>me7ioa{!@otm9YvznnbJ6KKi6JKU4WMG>k?v3a*OAf4!6dyI=;S4y{6~u5 zxy2f!o9o`vZjRAV3!SEYv)#hV9o51!5sh`f#OS8bQDb734B1uimj_#CyT9yD^t^m~ zN9;0}yu!@U+!+XC(E988NZwv&Us%d<);zXl3Ho#754)#daf&;z#2}ctZPm%{ z4A<~hBnZ~}92q)x_sG9Sv-|EH?O=Y`)mCoqOOFz`-Z~rM8~Eu>m#O8_`UjG4qxEGV zHhQh=+*M=|fnabr9 zu_F((A;C*l9c_-kX&aXP=zN6+SZ}klApYu!h%E67j3V7$9 zsCRp0CE;@8=}AdzA8dZ*Z}s1#k5n#qZel;&4=$U;x#oIg;^>xKxZ!&J&i(S{ys0&l zyR69#g%76*G#A^&&J(s~iRKO&CsJ6%P3z7};Q=-0`m4?igW-8y&@=;iuu4wq? z(GGN^caLvyq&_O;(Z1vDM+P6Fp7nRHrEQr&YcUIrO(j}*2)-W%k`*ddJDdcg9e zz7&}SY!B-KeIu;27YO6g7J=l}&l!;>!C6FKr&rz0+ZYFfU~cEmNi`*I{b3)s z=kXT^*f&k#(JKV?Lf5^M8Hb#$sj7x~^DEc*x_+9FL@P)c^Ej??Vqw&GHqh{2eb0}u RqR0FDO-e{nHO0SO_J4HbA_V{d literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..370ceec0f241b320339bd7a6239eafd4f19944cf GIT binary patch literal 1852 zcmaJ?3rrJt9IhxR$XjJ1AjcIFQQKZy9&1O1wnu4$HCW1qifC!CP_(^14k^qBOdJ&v zDIx?p5o8SA6#=(o?2^0x{eSts@AvqBmpdG> zDa_7hl?{PFuoLiM5x(m1J9>!~{%l=iT81yKSZFjBiOR8Li54NSWoR4%2$Yg|M1)9W zDVp0z5P`4|REVRoXyHbt6jhQXW(--cRO4&{At+d{mPmIY7!ZfVD^x6S|KNJ-$i~zW(76JUp5J?IJKtKSMOr`q;1_rJNXb=^mKr{-KMxq8X=`7znG{NLaxyuYPDZuy6e@$kFl*3gBpgA~rKm88o}|(dXBA*X zC)Fy{m;zM+W<^OHnuM`HywVRXDAjYaD&5SO@PSeE5;cWNhRiL^0)@i=hbonGXdNa( zKGyr6!a8w^8li{~9h#(-;s+-unp3HnTrDEOP^}n6ch62SA_2uvT>`2GxRL&VP$E^R z%=>A)P{(3RAgs8ja2Ir%|aek3kD%b2(hU04|Tifdipg zE{sZ(l!ywO<;p&C>GN{UR#2+(%rK%=>_KEaEvf{jTV^WeuLYV{Z-y(IzZUAeTnb(c z#eA^;IOy3B?jG~&oNe*VobeGA?shG1>vzv0?D4OqP5`sT`tIS99&I^f8=^eDM?QY` z+GOXxdsg+iA(?}Ya1RT+qKxCxw${Rc=J)A4^7X{=rK88D8easy_qne}m-&0Y zWFMswA3FCQcnc52_IrpeU#&|L-bMM49O31r?G67mw!)IP#JGRuAEi%STP*rV59tp1 z^IlrrA79Yq#kc_ywWC|9Pq#Hs_}EU2rS-qKx>dd?Ecf1}E4gE?UdI-v>?s0*gH?&S zHI8jA^k2#LdeQJn%`MY*dgl0I>zkLv=M42Nh6lO5@&o68F=d|NugkvGIXu$bKqy-4 zSkqyM0c$Lb&Igu)o;z>0fG4fc*xLZ7e#?n z_~GRi;uVS#r|_)yzjmB!ky&`?ZdPIZm7k&uJ%VPESP_ zN0r(SPcoK$*;E=EmIeBfItq5gZ5+H@^kz$WU0;@|USlD2>$rH~GV?QWny#Zg^~8B% zZ+YkP!O}SyFZfSMhT~x7zXGRta2Bb|GtkZ?E|yBEx1(Q?>)77bT>0aUCrvw^Ehomr z$ZA6>s@okE8933w7>|6-m*N%gGwh9$8~lD+;&ms}R8HeoXj*&U+eT?Z(lU_P0;lXF zczf$z%(w^nuHeO6IL=|Z*@%TQ(sajXz>4Ix99UpQxEr*x Uy0K$$oB4kx2;BtNa&~_8H&}1R3;+NC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..69463fb1af3de8f2cb8c9acc2fcbd61b226cb114 GIT binary patch literal 2177 zcmaJ@c~ld39#4Q}5s`BGf(1KFpga+BOn^X2IFd_u5kW<$>;#4Ok7eh*ncw_=@ALV5ug||u4?q&Z()u;Tg1bL$fZ*`VjBh-4! z)%Z097*UH<5~W6>kOM|VVWJ{kLkH1JUzZ?LuFJ~RZ`y=5jHneViDUxFSkfAh$NT?K znQR@c*6@)(^8KH}YC)C~A@UKmB3&gy2bY95hEmd)DnzJJs00dybghdKDGH53ouW_z z%t&8=ClpEK#{DXu$D?uOYK>4XLbxy;L=^}UiI_%Z!DJ?d%nD`rLS!<`4uDvpOa{}( zkI7~*U@B>i3oAtFGDNOf_vNz-!tsqmPkzqt7$w0(xl|lxrmQ0guoC|Y9y*FI( z#<{>7a*1d%MB`xpH0ZS>)IG-6b=#twb>ky))a@$N*7XVE0~n0i0v8SyXm3xSDypaj zY(j*Llh=dNR$6}VzMer%JIA&kD=5f!a{gHwzCGyvf(gY&wy^ZPi~7ZrW(&(RGxZ5~ zdj7`Rr~AO?AGKvaT1GvuY`%P8IQQ=}K&~w_$2}=~%4VFrZ8D~DzI2O8Wv?1r9FIgr zu=<5QlQ$cS-U$e~8?N0{4A|RwlJ?oJJ^8`jy4X#kmevpF1LkUwrZFzoWSSIm+trG@3T%5&N;?Z4Bav; zEcN%hL*;4vOnexS&W)Q<#WGh|L^WFJZ$jsLJDeJH<*ur*yy$1jCEeD!Q``r8nznPo zan&$e=u=-BU$o4$TFlB5^yQv##@%U(rx0G%_j%1VHl4juUS^UFo`o2%z$h#@<#53S zD%wwbp5L-HvwH``kJosFXivX-ROV9gqCx(>q}FY*`90);PQSgl_Qk;bq;k+>vv|p0 ze$g?ESY@lqm!?Wz;&#-HgaPd@o>b!I>@RXgia-6hyWxr|6ciRY3|b77p2O(H#l{I8 z_T*dR#U_Psdzep)`U2r=vBRxaE~j_)T#IHiEhN!)*a?5~tnhd2T<p;h3vq+P$Yc z@V9YW9NqR*c6pwxB`;v>ntsZ!%6{4LYeqsLE2*o>Eh!D?P#fKtC z`^TUwy;7WO11Bo4w;5+_7F09f3BAR(k9)9pdAQ_BS$*J8aAd{Ue(Z zJ9}~poOovXChVw(ZS31_XM5Zjl_Ou<76lH%E!NZ2>$y5x@L1zahCe)0V0(_^1X9y4 zS?gQ0|JEI9c|kr9Ln#XMFB$iUzI`q7&ajmx}`f*APM!zeu_uJBc?Wu^TF!DH&dA;$Jn!K%C--c{Ns&7v^Ja8>KT;HC2 z*baDqwSQ;RLOORZ4{!cWIjIH+*&i9`iuPN-bMv7>5g}Jm+ylY=vBDmyOxi|Z8Nk)1>%wApB>`NR!`Ax zb$#_vCv~~Nqqv!tKeW*nf7tahfYh1Y(f-imorz6i%TMcYBfj5-jXa)aHR22ov&XZp z3iMXS89z!-ynFIU7}$2E*5*c?J9l4s zuppIOvZ-|EjoUM|&h60z&e%uCTMk7H-xRf9W+j7mrgrV`mBm$KFW#ke-fDZ9V7Rw; z=y_0tczSQzKMTL#lcF}5B)T>AHf8czmNrh{CEWxTtpS3=FO9qFIaZI<_cT&Hh;wD? zfAHp8y)MtJmv3}L7y30h8zg=^>8XxGuU*<`I_Al@SZ_i~`9Q($9M|?Ur!E;(h2ms0 zMsFLrekfRK+j_$J2$0%CTUq%=I`p3``47QZb4*Oq;|mj^VxIA*fXfPpTNob{{1>ME BY6JiP literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..190bb30d717c6abd6b87df313f609cd8a412c040 GIT binary patch literal 710 zcmV;%0y+JOP)D)4l38 zbI#Vg*XcX8%nve$&TX%mBUCC(_R-G&+TK^zN-GdL@Vj}=^Z%W5IFD#;ZB6t)Y-ug@ zDhnQGh%z_Vw6!xi#GnuhVW!(jUv7>%7= zgH9*?r-0IH&(l`3f6nKD&#A{iQ7TSn-2gqfj3ozyRrfR)=bMPSwmy4<>foGY((EWjk+_3 zvG66d9(FAf=w=!0b*7`)$AGff5feE3Y~O{Gg|IAF4p)0Q+NBAY7#)v=$NU7EX$E`P zbhK~`w3Iav6AUmf!-60jA@F+Kn5eoAbF~uF{%@G{2iL>z=j|Br(6G!zFg~|}@+$4q zUr(T#?+md_#OE5pl<6#LF6V*jFwQCzmAC4iHL>O)54cG&7CrkgG9QMz+5GWQV^_+C zU$9wU?5wLOT5T&$hU#J=6e_h{Q>(S9YP5@OtQW&>8>YB^K5UwM*wDHEk2&ZllV$45 z^2yb#9pMKRrNq=X3d%kP#=VL`E`V0+VC1%1*K$|Wn)264JT4Z=b0qO4(nGs5D(+QY zcy8|GM$|L4Ez$4)~7)27TiK9J2Z{qKQ sxTraSXFE?4Px2;`qI(HHZy^YN0N!cbu+$SM$N&HU07*qoM6N<$f>lXO4gdfE literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/extensions/themeGeneric.png b/toolkit/themes/osx/mozapps/extensions/themeGeneric.png new file mode 100644 index 0000000000000000000000000000000000000000..be645f76df92c954c143576eba8a9e43da0499fa GIT binary patch literal 2185 zcmV;42zK|0P)?Zh^lBic9wnU z2OID@&t6R6PPqmA(LtpeTAcv~8jL6dpeNE%zzmVmfb@|NaYwddGIJ+l?YBVOE`m}+ zL!y{LQT+fehL$!RabvIiUIqyK{ow0`B~sZifzhA|l%9hV>9i{7M{Yqs;E1VSdnmfX zam#%ldQ$eI#%lx0(<7jo0-jC+Llb~(jD|ouiAWZ2)XtUl#{~@-;c>|oBF*Fs2ci^7?S~N!O!;|Zqkaorb3Y8kq2wbTF1}A99#z}@oX|(oF zAuO$F-1aoE?Ad}4Y&oRH_D*QooU>$73NhQ6I><7`1?h&hmNj=QHeKu}%! zB6^bkK{bWGF)bbsYtf_9p<87@O{W@X{O?zoG(c)@#uv(7qSq!(X1vn*dT5ExpJJ29z!S#v#s%32>ne4mX`rGlAwTJoh7UxloGH`pG3L3_GA z)G_N3@30B7J{d_pg=Eongf@92O%w|bF%nxJfoy&u>iY*FR8Xksn1Gvqam8x}ShpLV z=_&eUy+`>K=ngoH?jSpKCHO)qXoi-|`bo_Y%3@tn8)XaR?Lx8BI#hFRFmTE6Q^fH@ z5Y*^~Yfa}6ULOFjd&h9A;Wk?2V-O4(&?uu2a!W{AIk7&RXCSvwKpE3KSMT0yj%c(0 z9Xh>tfamrFcvsxWx(CF(I^6f&1FFgy?HAS};5LY`aaeCuCBX!R=y99E(J&?hJ{ zUjda;i8Qx8=)Zdgx`aP5Yrad^%1pc`Y8{&85)^jTBDg#N&Kzra<()>Bpb@n)8dZHX zDtc*@J*JUV`UIAzSf}S1h)J##Op>m`G#Mh1uMO&U{tUI--vxK;5|rD0j=TUDv?MwJ zIXl61S&!nxPz;b|CK&-9Jp~hC^OF}*K9tk5U<#E|8s)R_5($m8s;97a$^3Sn0e7E_ z2)S~C>2Stc5Ao_z?_q_zo8Cv^_T{*9WIL)tEPzUTh%c`}va1Dzed7=*LDCEGOu(!A z>DS;zJv46C48ztlCt;oew{z@|bBo)lDXoF2wXwxc5hp6S4DBTx#2+!kNSPz3__g31 z-HZoi6$`?%_)|EarGQ0nC!CYSNPVbg;Dushpmr2?-udj;8j*`P+qiqwQQq@1-uv-sJY;e7^hyS2E)86pOx zI2hiA!x5cuP3>Xe`CT+(N`|nW zl)$eQDqt7cjRRpFI2hKBLt#RkO_wn6oDKtmZ%go%BWv4R&jaLCv+I6j#7t0N2a=pl zOkZ~Qt4%HFqPXqjXp~bXCtR6?wA+WHa3OWy7sSJkOAR<4(}Cy*qsZ-~!RAlHGqQ1H z_i@&e1q~4Rdy!TfT5rZR(-A(=dUEY&yic~&l>u>e9YumM23{qla8EdabnXCdDH@mu&lKtrn%55k_gU~nL}1oL zL(#Ttb%`geHRQj!DU$(7%?hqq$E4jD`Kx35PO?6@mMwV+=d>9R!&Tw1Phpw)G1lTY z8X&M`M9Lz+O;m)_E)Q!3HEdXMMW?q@p}s978H@`F7cIH5iCAR}1$m zMASQIlx_%+E%{{N6j&m_f91F9W}ABghohK&vx9CdB&S#RL*6&`3GFN>R)lAAPR027g8t)U38_mv7nXKN2`-Ic9O|F zGLOv5Z>B92t@aL=xpU{9bH8)W2XOw70EkDB&TZrD0WfcH@9xP@`W)~gKQ676#>!c5 z;5^pU?%N;;mi-`nsvWcz0lfRff52(sg0x+qZB|&0gNLqi<$W%0@1qub=G!orbaUxRf0kPQVd96iwk?84Dp6zJ1wV#rLzo z_oy=@q|!rt;a$DaTq@$NDXf5`NTBd&JTsXg7#<#}a$Nelu65u4T>+Q2tJRAeq9`^v zjsub;A&R18#%q!wfhh9e4_s#8xDWpzpn+vs31hpJ$z+o9AZw?Ubc`o$2UC>l@Z@=~PWw+B^(RAZfBT-CA znwnm}(wM98JdfHL@I4PqqbJ*zJx~?JY`6DkY}*0DFyK&8AhOL90+z2%POi-~<|I)N zfTmMu7>QsDcDA-2Shlrk+xFG2s)DBJfNKc)7UF`KfXI#3YO6D|^L0TKF)kX-A`-p* zejl)THq5>$JC6HN(=@OR6TlD9m*B{?tWbVcZ!{JJu?Q5p?71%JY6pDZ2l>Y?=(;W| zs6D{%0ZKX=(eXrr+NFsl5%Z>wTS7(f&gV}=Y#wS)s&E|#-48K}Adce%dqgmBEHvnl zF|=ONZS9r}2kkZjtt7J8wD@+6&pG$p|J-}t_j|wPU&2|I1^=I4;+gz#9LM#Z zre9{qbj#86|BW%lGa2;Rd0tPZS1ja#8)#Ym+l~Pa@bURx_l4(;xN7d*KfmL=$rCSg zOG%HV9!DrqY)Nbhu#6Odp2VYu@3H9$fN6k^P?3z}n)S!FEdRJ}amBIjO~)fj2Y#ICye$ zz(R~xydGgjus>q8OlWNENv_-6v~khWss;O6;=Az+5{!s1B;i{lzybf$S(nUw__=5Q zP*9XJ0<0)?VarfKi}pb5*d8=~GrD^x@B#-)PYP092}p&zA=%Ldaxf19L(YPn(c{47 z?m^!6p(0e!H4XOc?`&DPr245fdpq9gO6p-of_Wm|Z-oE?{(>UEd(Mxpx%QqEytj3%Th>18X;;)N^QGR+Zs+aA8qJ9VrZtNrZmzZM>dLwWD6yw1Lfg4 z9^REXWGoDtat*kpMlic8sTz(qbf=zvx#|zgwzj_*Nl_Bi6G`ZI0vyXf`{2BL?!5n= z`4ZRc<#-Vc!vmc=RzRd`HE=u+xfjlcLoIye&2t}ERM(X{i1%P10EPK9dGSG)5FFr> zil>kBUpHlh_sZg+kWn~kCge{T14i3gw0RO5T9c`#U#)&-`PMch!RS1ZgnlK!0e{D} zQ)WK>+N*!|SUYk!UPAC*Xj%U{%Dx#8JBopa{3&;V6v&59R;@XB`@jCjWAzaQ^$k%* z0(oejWic6>yENN9=GyUrJEo5EUS3#S;vRd`U7*+gHw~H%Es4}ai>mH_cYFIg*tgsH z4uZYE3E-xb1c$x-$)=S9+M+3ynhyv_T=8%RHL&ts20X7T;${!=bK_oAuOE}X4*e1gSH0^)70 zz?)ql`P?9RNppeZky&%2Ji8%&Yy~Cnd)Jpuc=quhJ~JpNmk?_+%R3dH$$XorBfRWD z_Zxq$NIqLvRg$CYOu;^WrW)Y;%lh9b@VUQ#Pj5180HTC?Lp8DaRiZXgh5XpBF z5*QIg4aB-V>uX~#Z8@%fT(#;wh?yl|2{H^W^t!LRv|zSJ;31d+ zmKo7Xj7tg@cAW?qLAxazG=jB3%l46w;Biye;4${8?d8BrekhwU<$@P~f5Xcsl5m0= zlx~dpTU+!OntP0{nw85zES!Wr1t_~DcM_^-jH9CdB7nQ*+RLu+8nxLZKO2@0xKtgr zJOxQX%Qk42n_6C!C2TGbNFz!>612{(z{@#McFm-TFFtwQD+pwmBWI4WkWKU=M67|R3pKUC9Q(MZ9k`A2>~)T2?L-c zVk1Wxn~NSrdM=?m;6Nx^4T5(FOuKsG1rPmO*>4N`NH|dm)E#!V8;helBpbJWiWPM( zDouoQ#$=9ni+o1E5I|m+3=I|z84?^}SsJE2G?%PV$qTY*gQw>xb!?m<*-gZAlq@fb z*!sxev_>T1Fo=QSFmLXpA3XGnD;`7=(7fPFCxN|;NK!x2mV!gw#}0syH40-12g8O2 z24;DLfrNm584e2^H_-3la(Fp_ksU2;)S|3qYHb3Jgd&c{V=gVd^R%4hINpA1*=i!q zOdSCeugk$vFz;sd2PCNe?mss?(bS_JC4(RAqthaXz$N6jL^Y{D2;eYAy8}T_Hjh^1O&w#AOnsZj5M*0Sp#Di`IXLGUmlmVREC~6` zqJ;3g0A{iVV~K$zjD%Zm)_#B^DC^%BZhpM6Tdi|Qa8wY}pk7AlN1GeKgTE=G@J-7u zp!!7t#2APcI9Cw8vIQ+KFz~eQ#l|z3Jnc-aPe`x?GB#x9x7b{dLC=Y67^$NOgpg1I z^KMeFw~&M<7j1dMA%VpKGpd;NDj`qkXT3N^fN!ua@n zuj`VFM@^P}!-4BrgHq5K0LQunoWP_HHeidngY&tgfL_Hoq>zCVx)V=GXazmq2`+CY z%>C!l*WY`~_}{Q%$%AEZ7NF(bIRn^FQ;kvI5>F?1GU-uv`MA4&@xX15h|!H?W{X)+ zbUHB=K(D-M^mZ)W+s9-|VX?q8?CvyB+QE{j!B9HE&^+LBd*J5lhu>ftI{a~I<$@u3 z?g^sHhuJ7b-KMcM6-sC(84;S|=>$Lh%CqkN#jkFi?^nt_K<4bg!=n9{Oz$%lSQbYC zQ7W8|P5p$WfY1M)?>`#~ei1SSVAiG!PF6wiv(g}^%?A5(Azicf^Shq?|U0c0&fJ1sF_SaIO0 z+iw5v{48se*V1D^3Kh#l8CyE47p?4D~HJ#C&*(VSR-0JH`&)Iz>J^)(} z;rU(_X^A*QTf?A)BH#(+Krr7=Nmhg*@Ns$br)>DFoDKr`!6gNy83TuUsqC56$U#*y zVb|(P=xFL;^?G_^Xen9f<471$1CXFek0vF>Vnk47koOdPpQ`A$nRY+Rv?x#6C_J?mDjuL9l+QpOb6vu-cw;TCXXSw!adfpfFo1cbp{ z0m11S1xr_dK1YIP6Jarc<$!-US&W&I2>D6N%NJRT&+6W667K zS(Qkdep(11Up#tfu%K|Dl_7Y~ft{<$A#rRE*5S5~7hiNS__75XI0bL@o|6YP83DE{ z=yVy3%AZ}M@qxbAnamyQlWW*Cj5WFm!IFubW| z0ys}GG^mq8Pd@Yf>*)JUcw>Z-pr5kk@m}zg3T8a<tueFO*K34V1bT1-R$@dR{KH-SGl8$^?*zRdL& zPfn_{=$wG<_c~2$Buz#;BA~RlLx%4F7XIB>0HJ!a$HRf}xX@kqy!ia)gUuW8<}L=F zM1R9s5E4%X_#a(tgD=iAi*~Nu4hn+zyLpg2J}8+!2dvl*8qL8Xvp-qTa>Ng-m%I)^ zj{t}t0i)lh6u>H$`m`Wsp?uvGEFDa<*lO{apkU@UbX)eRgbvA$;~=Yz=w=$Y@Hb?i z4~)EvV0*2v@xF&&f2sUX>jq}|aN4Sp^^JFkIN)!-q!7X1yQnDFDBAVWc2JM+p_Ui% zL56*A4)VPVG)=LiG=lGJD~7d8o=5OJT1)^J31%pJve$t;SAzL_MsXevZ&@HD)s=rF zv^ELQUhxjNWP6&76v{3^$2^bCn+uSIl2@{o)3=^6#A7r#IW%EW%M zU=QXjl(Lal873n#WS>0|>$E*I()0dd(9&u_!`3&z=M`x16x}``bBvNN06LEFYQiaH z%Z7%%Z?9VWx81c}+f>7furK!Qiy)@wQvKxk$ z{Sf@VDoklfP+}Tg09EC4Va@9c!6&GY;T1s^IUJq>tNYoki9p;YfcMV@N!^OSr6Sf5 z(AwdLWsBd0lCk5!Et9}#K)fdmx|&qlJ0p!XP3nQ|Ro$B_o8lEQ)d;7hx+wE`{gmtw zalrrhG6es70^TUvyJjaSb-TfbKF?#cAAHpfXyHQ$#>59SK~)2gFwcgyixIr2A$XKM zg6G+QFdkK*rY;JD3cQd%Ce^m+l=an9Z0e z6CeghK};)xw_bat^0{U8Ph$mFlV;8Im0^vw6f17jv_4-;CTc;^YNz;Jo(KIkQH3m@h$+ejPvo9T>acoSXJ~b+ z_57aJT$S4+gp5GDI>6-dX%w<#X>Hc?!W-vOv~?t(hI2*3LfbW(rW5 z)vbR--TXfa{?;ptXFRds-i7%n`y<;cAXc#zQx6t7X!#-nUZ?<0?d$~~iI0J`ul*4M zDCI1S=57Qp@EncYs$#;1-Qm!R&D4vI?l=hDyUJ;KO9EcO zGz8Bx@T4SlKA0PFs|2z_3s(Vl>JmaH_a*f#BpgqG}&zp%F@xru=bF$qgUvs@$G zB0mhv)}FSQ*33Y6F|fTk{8a61`BDNL@V8xMgCCe_6dga*0iB!PwWB@CzF->G_WS{? z@_W8Lpe*K#}7Y@uaiW&}eLU{AL81U>=SAeO?%O6C`hwZsL zIg^6lfZ&PjGtuV{)El9Kl73KcP$*AYYeKd zk3n?D2R3+=eI5bN6YwnH5%3xT|1tz6VtI+q#uD=d@&+S{qdK#4*cpW}Usk_J)@kSc zvVBcF5&Rsq{6i1lis1D@^~Vwr-?Nf}CqAExKJOuxC$gvGa~ZmgvtSciJ}4U)@G#Fy zJR8DV5FC(4%xJPG?NnuE`fTX8y;)oQ+>rLdycrMPGizaKR$|bxS`(5LYaoEWOmg?Y z=?I>$MDSfUcpky4V`0-0VtJkVJgM-SBfPTjKywFHX;w)8B!mo{xSh>9lm1IZ>`isk zM$dfkp6@Np6?%#q8dH#}+5|xaMOIY@TyZ^kL@aXDh^ny z1v9|oZ3j+^*i-Ll`JU8B1pgMbJgM+p64$%l8SaQ!2tE{wYqiYx<7qkkjC&CRILtBL zoS>ux$!+trlPEHiAm zn-tybWNEI81->sFik?9P0N+v<2d#ELwLF<}3XGqMBH?7^zGG+q~&|_O#i>czB&M-y=U}9kW7qQJxxF- z>qHq;?S21{>A!@aD$$%JQ06IVo=upcyl`5VLqPt-yi*3;&eaOMw5Z3xJP? zu~=`S`=~8mgqT?%Vjl*}DNawuLUeEnoKDDJg_xPxdfK zwO6xQ0tC+$!PZslAq&T5*#QiA1iZ66VS()R`2sFk)c%HX-LGVfVMwFzC)|M92 zS+gV$Z-eCMHTymGx360qPv4t-?>UE`ku5Lwb+Xyf{*2v&NAi8n`Bl@(EZ**G-^&wX z>^@F=fBCYL5+SUUdN#*J6bpj?^j_uz8&asGN^}cgzE&89HjxvCeX7}Oy zu2IYLymPOkg1+}QxxJSE;Qi%aJt2~&gdk1$NCvxZrVU)uM`+GOWE>wR=J9y6?_PST zl|H?Fve=hSto@(>D_5-e)`DRAmM!N$u*5X5))x@qkYJpt_5Ig*{at_o0A#Wo*ZSOi Q$p8QV07*qoM6N<$g3JiWga7~l literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/passwordmgr/key.png b/toolkit/themes/osx/mozapps/passwordmgr/key.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e8afefca4b4b4adfa8ebe2d528e3c68bfbb3a8 GIT binary patch literal 658 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2v2cMy~*$5Lck$|NsBj*VoUOF=OV;nX_iinmv2gq)C&ePMy-; z-qzmU-qqFB-QC^T*x1zA*xc0A($dljM6IoDZ9v(!^78VE3LvVet*x!At1T=nEGjB0 zE-o%9DJd%}Eh{UlfGMb~sOV^K@9gaC>F)09>zg=n!kjsC=FXivZ{EE5^XDyIyl4p! zEm^X1#fq(4HgDUyW#^6^J9qBbwQJYzojZ2}$=yJ-d(WOdd-v|%zkmOM1N#mhIB@9T zfy0Lm9X@>c`0-;WKVlDym|BP?b~-E9wL@#BN(#T5Gimi{Hqi$~oL|iaH~D>h*2vYd+%kg0fXgz~%Zb&+Sodx(OHZ@0 zszOcN<&t>kaO3?M{&@!T&ujVGUpjurP9=6W>yhW%PX-un@b>a8zLdfqsr_KW(pN8p zQnj>>o#c7MZJD}yXRD|8N2jym-%bQ>=bez)zP_Yj-PrW<266dy1`6wg~7422WQ%mvv4FO#oVWU;Y39 literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/plugins/notifyPluginGeneric.png b/toolkit/themes/osx/mozapps/plugins/notifyPluginGeneric.png new file mode 100644 index 0000000000000000000000000000000000000000..449e081496f7f0e460de087d76f3cad549d25df1 GIT binary patch literal 313 zcmV-90mlA`P)oABaI+t`GP*qh2LqCs)LB@GWXW#`qfPGSN18&3o0*6F*0S^~&3kIDS z^4JiVK$Ga)y+DNIX|}%z6P|yA^AC9#C}-){<=IMzvMfh@;fb;sCou}nxj}#>2ACLN z$N@!Bm=8zvFMI=y2AbGFi@L7crfGJR0Hsvxeu@+3J#h=&TYv!onE6{3`a=V500000 LNkvXXu0mjf%UXO& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..56b8a3322d9d5054480d8ae5d70386ecfe51ea0b GIT binary patch literal 4563 zcmV;^5iIVBP)6k#SBl?fS_8IYt*W??GER1z)BB$L#n zEXGWzGMb?j!=TYfnZ!vHXM~8sQj*ejsUFSQe&ZSS?zK_?yKuMYZ z)nBLXcJBH9^Z(!fpYz^huIthVSv2~9iw-5m7@euq^B~#3mf635T_lsaS|1)>9yW}n zBx-wxH0vmLum=Sn2-fgL+m89R4w48QTlOsZ7qR1zjy z8Xg>68jr z@3w6+GR~QtTMoGWl1nD#-+h;cdwVI-)<&^pl47wKrCM8wf%Ld&XdTS93r(LmLAi37 zMzUEtJ}^KL$DzNw^iu8x$LJghuLJ5a>1i=|C7Ik^*t#{Fj7I-977nlc7*O7&pcaI+*|5B+F766NB+RFIs z*(ZCEvPqC3^tue5$#54u zEd~$4<;}^H$?fkart4y009{k5#(9K?07jZdP9j0}i!X{}RSBTgW7Qxw!pL~^(V306 z+`_ZTZ=j`#lHgf0cmYJl2gByfA&1Ag>(*AHaT#V=%(PCN^^T=Q_{vtV#x zfN&^8gfNKUxkl(znz-nAmiNIWXe^C<9Gn1MSEvi)5T424nOrVMBk&q;ED$yk1F+6D zYvv}y;hQ76zUFNZ4wsR^L8?M0%-{-v=>X3qK655De&|DD;Gt4U3>d6E)(*|ShxL<;f5#o`aKquNXNNO^>m3$XtmW0~97cpsYk(qH=0bB1mIYQ~l= zB0YKrh?~XYe>OBViGg^+qzj%AgJbqWmt9890N{W}loVB$#ciHK4<9C^J_>#ElVttj z4`)3V4_7o2Ss50SOiw_Io&g&AJ3A?h7)^qNQXsAncg-0y$UzJ_mgTXt!rHYpgxH)4 zm$$1`a^cQ_GcJJ#8bw~O9y~~)#fwSf_kzrvqestZEYoJqB9|Kw#OR=JXYho;tQtT& ze&R$&b!23DY04DJA%}AYPJ(bOfN}GAauBx$0GUufgA){$A2s0xO~}_Rcs7jZ@-S*F z9w#LIFj8ldIVekY4YVgKh5aD6C|6ks41Mg3O3nP^_Kx>W#_b@bZ^wEZy2k8Rz z2wA;-f@ocQOxMkG%&GzG0%M1!5nYZ0!O8{1ZXunf1iYaE+Ju+dJP%hY6HZ|U03SvC z4*(Q5W&(zc17Hl-nB$D?Cdu?w#6cE@9tH=b@tj#I2{tF5PBl&NKphb&vZZ|n#S`&y zz&(yg8^$4ynN$xLHHF3^55nG@LINi{#3CgG2`?DZ*Xz$BvN!MXDNG z6v-AUI4A5W1}s_$%Bv^*i#G4w8e`lmME205nDI;oYmsguS;Z>gg=8(OZ=45=*mjQ! zUUer&8UksF8$oCykr0El3?pz~Fal$qwI~L_%2Lz_eu=`Xnx;=D4ZvLhH!phV;r9S*Khcm0fQVfWwA!lYgh%9jhL{Ah zN$NU9;rbz{xF3UdizN=?OjYoPSX1CQiFepAID@-~IPiRqtasia*E9k_tH9N3|1$(v z_8Gu|njpgcDc4=*2hX5tM>rm5h&)f9&=h9A1u019&wwvD!P$iZ*biL;^F$P+dj z2^fH(AcHpv;Pw##JQ%PFy2dqxP>y2_S`67mq(XqZ+#ma-fR7?SH_V?eQf21A0Wxzr z@4BecveF}RJS?D50Md{Jq6m*B#O*{S3d1#{@Im?%MVJHNXaloA7~^5++d%mY;{*U7 z%LHlD6e%#s0M<~Y(l;L+sWYsSVe2*5Q2Q6YKupt40v_74hkCKz<{J@A-p9`ik3dlc zBM42vBf^HkJwWy!EDZoW32Oiy`J8aSjY`ldN>FSJl?K3#x8FuFEVg)fK*XLv?!li> z1^W@CramSghGb1sq`*KA7`Ch)HTKm|XqY>fF8bmZRm+O)uGXurq9jVzPHcWwl$9Hk zb;L0jW`F_zS~zC{}DC3QTUi2r## z8Q=#S@AZ3&jWT*Lo$jgW0lTWzj=%E7xo3nFEUk*l0a2u*#Td}R?|+}FoM2VblpK*k z5#)Jf(0~zy zS1eq<9KcO7j}C}@UUTb4x_#>2dnNeV62dOCFn^Z+`c? z)b+?CR2?23=MB?Vte{4)vH&bWGt~$IeS<6Ng4fY!9DEZbrG5LT4_~14ZP-AbIBjM+1q`+VU*jdLpO$^(w+utH>`!0Pnr~ZtDNu_o&+0IW`d-JP26;S1=4&mP~=Q4PfOQr;mIy z#&0Sj9dU{CbS7P&d&R)>&(mHE0^j8e*XGi6@x_#AZyy7#_UA9G>L~}sm(rJ9LY@?m z<~Z;H05`k4DW+*+i7n!sG2!|S7~gNitZl0)FxUf5V5JmP|K|{2TpH%g5eA4sBaD&e zx}wxQ^28Ig7X&qg!D$#!+owN0-UFa4zyksmKs=3JafK+U3)my*`P$cL;Qsr`Kw%UG z=qS!>95lvY!Alvc$O;OyAVUt>woHMwx5cg9w%u{P2V6cFVoZ2I89TdE;og&$ETMk5 ze*Ueugau7pH+h)A?K&TNh?XO@wS4rW^xk8Skt1hattOe*LQhrFGMRy5cpsMmWEeh{ z7?igHnBxeRkANd)Aw6=b?A`^(|qV8vU{1FSdrst~TEW`dAx2Y2isk|}Y)H@-ph z?z&49FA;uD-bYXqE~vu@an$e%8LF+1V}iH2C2>&{UI*c>E@xbVH%tm4m9}l%NYj7% zQ)-m8O8&(c{j)E0SFtElpw9#RsW6XKVKY{RU{di7tOYIby?~cqqL$Bnj$+8+T;iwz z=fUcOIN$foGh`s;*o;v*pBFP1#-^$xxr3?2lV^yN?oKtu)^*ZTDM4&Z0_PGzHQ2Oz zHO0UUsuZz@Xkz!6d>$~Qq`-P#jhUmxVvZo*=7TE%7%;?jwg|Wr}6!zwHM?tu>y18$$*w5jHP0O}hr1=ja~98nMR zfp--HSa8R;9(aH*M%s&{Qh`j4nt-RyKo4NPgCKF0HV;;B`OIf(g12%x>H#UcXjSkk z5AaRj346dRMEgK)?$A;H*ekM7_}aKSA`r ze?zo(Hqn);5n|qcK3wxn7-2eo6Ex$qpQSih-$Fq$h+=49>sAF+Z4YqqQF(wVu`9N~ zMn=S%qYIVaTvUEKHbl9YI5)a;Cmn@x!Y~pGi2HX84knJqg@B@Vt9YEAT4VmdSd zZGld|mFU_}5#2VKXrW4E25*Db?5FYLzP~_5!79#g90A?}NZx6|6`# zUL`^`-xvlhtMl1HVcUb5%r5y8yAf!~ZvbGgX#i4quMye`opu+|Uw@2fb)0C52ioGQ zaD}FXNv%#*S;h?j=2l%FWv!TbPi$UgLRBx(0Xz&<-M;8JLob)hPu-JFKaIoV&@{9F zHI>`y8$cBWQxe)XmuTKPqFa{}tx^cDm5T+<{hPXT@%!(R+#d!4J>h!PwtVciqeA%tc+TiPfAb!Phjd4^f|G z?fvoS=uduEDs{+NABC1{0k1=Xbq!IJ{Fi?farLU}9=eUOYgQ6{IY~6@3@sq*dV@?b zIzOI>WE`h=dp^JU;cWJIGR}wP_^fmNTVV^DlUB}dbKT)>M0@ULY}FNvt*}@0eznjPtA#=XIqH37(nA=QWu&o1xP`!Pv5Y(6p~3Z7!lZ zG7DQ>xmR8}<;nKlE(E|tfv-k1y(ARKx!pWn1Oms8`s0N~d9@(&YoU|DD%wDZ3lXZwwowfZ&J z{TVKlg52B1$MJ2ART1aN=Rf<@z`$yN-_HAzeP%iM_w^qpTnHM2F*YJuIrA^J{U37} z`|odtLhI39NvZ*Sdjj32`SQPJGMlXq9`}1@(^+Ip8u3VW7<)aov%xSk^ah z2!%ejTGPHVg|UUCY=>jVrt|S)@!@BR#n*}Gs4QR0xNcvtf0>a0ZOIR!e)zR*4@39; zQz*3ZI$d8^0Q)~vDn0V!Z1y=B=R-2iO*$WPp8q<_pi#NDSOUFD>JoV_D(^XO*mXLe xA~{Z}+K$Wf-jBW=I?wXwKTp#KTmEc-{{w_c#nFh4DjEO)002ovPDHLkV1jrzohbkS literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/plugins/pluginBlocked.png b/toolkit/themes/osx/mozapps/plugins/pluginBlocked.png new file mode 100644 index 0000000000000000000000000000000000000000..6e8e1761bf87e41def9bc2567680c2ab0f4c6c4a GIT binary patch literal 2152 zcmV-u2$%PXP))fGN>c4ptZUO(2^ z#10NCCAM4Kkcfg%A|6d7L<&ME{Am8rh^j@3sDvsc)E|k|Dj=0gMQYNf6`<14ieD)W zjUuF~Jn|q3Bu=6@@q^e7#&&kS-iP<$-Pw8dJL8${Iw^%9_|cJ$c4zLLbH4kX@0@!_ z(5+o!>eQ)ZyQ==olgX@a9x&qZi&>xVH@Dq;?*wkGFY8dgmEix{y}LItH8rdh3OA4D za+{P`Y#06oV6`vp^cDa}u=x1nkEc4;t|eb<>#{*LpQnkD5!&$f+i~2^e^mfd;q>Vr z`RC^!qiQwQcp3;4%7!r{ojLQ^rTzQ+${r7yem|8kREBv>lhjIuy!AS5f9|>AN@wS9 zBgy1|IXb%D(sgo8>hHf;?&(>UrsfpLnK$1&7#$kgAq?Xtq+XBb`?c}jXM5;;_e%Z9+$2UdqzkgYpngRIE?cbl@Ffc&jM1nj% z-+u%KC;98wNiAW`y}hK&&y!NE+Fnei&9bNs^Anw&WC7Uq^XDlV4uhVuy>e62$bu1` zjh{NjF2gPWPJtMC^2wwuNk1qL4LvhAHkQx=0jf!oO|c5+9=xo<*~X0<$qyFV0Mg5W zd39jJL`v$g{O~X(SFE7Hg9qt5~da(bEHzt??UrdOrAxyTsUYp_yR#!k^ zrgss)OmB#h!ui^J?jik?Pi#s}rWPRh*8_`y9RYEEQJQX#MD|#UvWHW_0K%i7CDzqN zUcBsGxsv2!k$iB#L1hLY1a*SGprTN1a*}9afvj3>2>=6OCg9vensgRiPY-daDtI2i z`>ZGre%rxp{~dSObBvi;I~AOQ#G%g4lP!vpl!HME$K&Jy69zjr&1FI~G8volAedm} zI>t3PCZ-pZr)FoVyQ71oK;VYjk|+v&v__*O`MR;Zxs6TQcdZDKzrT$Bio>YywG zp~-L%MUW5xNpNmCKvY?#EI_1kIVxg)5ziJlH5d;%)&&+L5TwPx5I6uppehZA!>&f9*BO4G!9=Nih&G(guyRqI9ER zCJ;vhDnMLZA*>0EN1uI`e0Sct)I~RMCJoxrMn~b(Bm?`#XfDT-4c=h%BSe#yvw8;M zhttZ`)Y9Ux>f}>TQ4lL+vDvACK4#Gi#$f)oHqx$5l7X_b9GW9LcQ%4^newIJo;#wB zE^45Jf0xZN09|slQ!kt5gm3^DXhh?$yh0=U_E81&b9wgc*g@{NYZ^Km&t);Xrl8IP z4?JK8t$?+u=bxwi;lo7m!7Olj7Yu{NQFp7rZd~YGB^IK+!gSg$!}+skX$XBi{@{bu zy=@ys*R7)kY`KyXj2uOqZcj%_{0Lq}OV0e|uT&qtLS8g6-rVXlGj?C$wAPnOQ0Lf^ zBTPLIDDB_GtCe&-#UK5~Rw@4Cwl&hxlGNDctkH zzy)PsYnVW59rCd9c@!lzcHjVM*m!t#3QDVZ=@Pl)u32t(gH^}JsdVmKBe)5HPQCad z)dAdan3~hHp$Sc65!$C7kGmQ`sz9qJa1P7fM%0JnNKnP*QiXD54vrem96d?`DwZ7N z_93yqpLBFazO+<|7C!ojbj&FO?DWeollt+;_B>SC{NKvueLqg6etoW18{;Bj?4s#x z$Xj&_tP{5K1)`_FMYIk7gYE%M`7ejaUNkLjZjKDJUK4cN8;z4D85MwhFJ4g9qfe*P zf5fBfusLTB(-?rKdAD_rNqm4Qy-RfA4ABPzLL@qb=wWA)D?TB_%+LQ^EdGO24@I$aWC=yoC2goWsE1G^SM z+11djns>#tVT}B~P}uv6T<-OpVT|(T=NydcH-5BuKIgb@Ig@Q5NZ8EZE$jU*Ar=!t zBo!foE&%>M(6K*o|EM6(?^dgS`pMkfuP)c?=ke}muq>?ld0$JOFZgcY%2A!PluugL z$XN)rTL?KWNozbpD7-yai^awBs(N@&KL5g@O68xJ`>B)bimUdo>&uAii$950Y?h>f zAIkF15H#?Iv9Z^W)oPcReOTUEvH5j>nl*n<@>!(KG5gEVvBhSAeNzCM%hvsRQQfkl e|F`Sk0t^5p7Iq5lfsGmf0000K^Vt>v%7cUHp#_A ze8mSCMMV>}P*aGas9<4bVPz#&Hj15MCn(rhDHup&BZ7*8or+>5zKLKAzLFeCE~n=` zclY)&-&owKLCwHd%rNttkD32Z@&BP7j(JL+x$*2Dt7?K^pB z<7Wk^W5XYhzyBHzb*2J{Jig_926u0v@}r23BNp^5;?rzMP%jaA6`VxS+P?Ve+JSQx zkSZlIBx#4yuVvnS$P-AKtZPVjW)Zg=%Mk)EW^&qNCSN9uVb8wJXMmIgq{b%-Rb{E{ z`b|r)?LLadh@o3!WaT?}MapH14Lf(RZR=|Kdi%*_(nN_=0mlK9j>A!dGdMFy|S( zu#2v2hDL1gLIDh7pymZRKrBFX)W~64zbt@J#F3^rQ=q-Q4X+_+F=#PFiJ=wego?HsqqZmHBg-f!2AiE)`mnI z8i^rJOtVR?YxQzr91t6z{^dzAsZQO_Ea~sH)9wakS^TQieJiO3LKquFz%S3a%QC!StDJY1a_ zD*yo?YEG;zpbc<=j%5cg9_n1Nxw002ovPDHLkV1oS|Rm%VX literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/plugins/pluginGeneric.png b/toolkit/themes/osx/mozapps/plugins/pluginGeneric.png new file mode 100644 index 0000000000000000000000000000000000000000..6ada1616bb125b42529b68bf3bb66298cccfce9b GIT binary patch literal 1939 zcmV;E2WP)Lv|x~jUW zx_WTOmdgXSJI2_Kha?zb6QKkHu_6*Lh?Gbm0>V<11&IY>!^R5+C2WEOECPbaUJw*Z z2o?rpC1x-ru|qtChImMO=)wJ}yN36w-f-_>QLnnIyW=?R4OeyRJ-s`e|D6BabI(yC z!vC`=fTe&?N?Ct;xAy2{-E0$M6o9fU8O&O5KeVyusEC-Q0EjPT^0PmFW#Gsu=dJgq zt&(1VxjGF;PC27WsdXX}eAGRyC1|eBpZ1|I+C#~r#*X%PFZ~9+;`Sr=K-PL30Nebuyw2x@0ls&Fqsq>8kpM1%8 zYd?5w&$@d?#{7FiZIm{Gpk({%%p#iNo!tKN#Px%fSl4L$YyGYuS16NCmoGz6jW&x=idbnNClnmT=;>#t|V zhS#i#lgXq>B<6;KAmsXt%k>F|(olhk2_LT+v@Bo(L<^~G4@0HZ_<`Zyqh*{xQEf60 zK(@;;a=Fo_sWJ;dDWx7ebZo~Pm#r;-Ja?6WtWQjQ&>o_F{HAbyw8pyO)eJ6Q&h~Zv z@J{R4^G}4AZcLOKG%5DR$fPWkXha9C zZUU4A`N2L`l!{y)uQN97QE`HK{n}KMvsY$FSRn)XIEfgu>&3M8N~Tk6-!;stffUR8 zv!!D%JQ>D;#N@a=K2vE7<#Q>vZ0Vs|&EwYfq9gO>Wsl;GDnkQZh?PJa0VBkWkd!8q zBQ`fK_E0Dekj)GL1G8%jnFO2HWa;ZoQEeK0#~lX}alj}E$FyO{wjIj0LoSzOa9N&a zBaYWF9cNv5`#&|**%~w}sAdJ#oWW>?QE7}yvwqv%^d}UtD8l43aW31GCL4Wy7ukuT&4brKAuTnj`%O!O2$qHq$uTNdfGMKWfw zVMUgnY!|hrP-zJ5h%X3@dU*s0Ovfn~20=o>o~luvs!=GUDdu`<1W7WfI9u1|X9eH<0O#%02@!k^^}1sNS^+U& z`PT3JWmJs8XoDDoF$OV$7!!4w`z8iUL9Qpw9LA9=y`NBK@1-wuQG`#d= zUkSKj0R{+yhBJ;VxTR+8ws=>qg>t!$;|acQ@oYpEbrHb`Xd^hj;D-=+jd36Vj1_Gk z68dA(!F`JgHQ$V~ke9YlKC}l~sCz=FTcDyN7>bCr^MNpIR7U8`j6N($5;Z$6hKQb) zwhMbRFu0yz470sin;rqe&f8Fj&1OY8O>_`oLX8fBh=G~H}~Iv;F)A`%_HpvAMOmW|B=ry9o-j!7MkH=KWaPo%+<5M zJALHaFWQ$*odu>uM5~TdmjnvHvh14OoBHql`gg5V;S;w@D!;Vv(@eXjB|P|Gu+8e^ zxp!Xq-uJJ*dGtIm0n7lNh{(dEG#;f(y})4Kj;}tDyZhmTRy;Mh_-M4S@B88|?7G46 z77N$DJ9+8#Up#;A=%LquNnjdqMI^jsVWN~$Kmy1BeZb&dpF8xG?xD?3C@Y>>m{jk5 z=svDZ&kw&w2af&Uza2jLlgED*3AOcXzx8lo!-EHQZ2LH8 zPQ3cF3%_~p4;?%^ojl(bz!EE|1dxsVqZoZJM?BY~OGcJdn%zOOfG!}_K52j--KPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP! z4JZp9`#v2200HbtL_t(I%XO1Kh!ar|hreAbNfQ*(Y;)BSP$X%t3Lc_Gu2FCs!6MCp z!GNXqAu5QiAi+W~R;lhFxPg;k9H-(Y7mLL| zx~`vw!{Hx^MB;ZopC3AoBfyU1I293zcq?C+&1R;EbVQ_6C={B&6JV=at!@IFz{6}d z`^EK4r_*<5faP*|*A+yhlT0R`t-(*%cdr0qvDmKHCnC31fNrm7199KHpZ&*%36 zEX%qMkVqsxczu>-Jpf=l9{==_&(318urirU31Bc7-0^Co(dfv1gTUm;WOAzO`T?*l zB3I_~dD{;Mz_#s^AV4~uJ^;2vWMj2j9r^WszkjR(G#ZU@(8BBW`g7oOuh)B|s_I3* z-fFe}ssQD3xe)}Y)oQOrq!Wom?gi 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 0000000000000000000000000000000000000000..f3e1f8e110bc178888124a58ef675fcfaad67cdf GIT binary patch literal 502 zcmVvCT32rNX`^-QT$Iu}JTkFD*+VqtbV9lcmATBtAGAc_8F|0y}4 zY1+|wLXxCo zECOrhlY9$k5$7(%3BU`l30V{H%&XOEN3tyM#R)(>Td&toD3)>7Cm@7~f*|DL1W?PQ zQmL)r5!MVuW=+$)k2BG-tOtr=M)F}3m&o5nqwx|qL9^L>#^O*rqTWFQNyp1~yWP8R zcZ7!8F2f|Eu5mR1(r&d{M!0*s-F~GthFL~kqc4Q8!<#VJ2@<_X;x-6-!b+u5Im0!c zDvELu2A0d^tA4+KFqur=RaN}}?`P!&KAhZaHhaW*KI7bv$R6bwhEd>$!{JRjoxbEY si1B!=^7Hxp4G-jS69LC@zW)Rm08_#%&T6F}Q~&?~07*qoM6N<$f>RIUp#T5? literal 0 HcmV?d00001 diff --git a/toolkit/themes/osx/mozapps/profile/profileicon.png b/toolkit/themes/osx/mozapps/profile/profileicon.png new file mode 100644 index 0000000000000000000000000000000000000000..f67a43714eedae5f6aec3fa41535027a6a25deff GIT binary patch literal 588 zcmV-S0<-;zP)TImVK(f5zW5cP+cPaz z@>8czRsD52g)$CH2|NR*zy=fW8O;9iR7xcq92Km9&?1; zyU6GBZ^AHqfn$Iz6wQ4o)jU9a-~X!nTf(S|>$;8Ubn5SeVu=u=P$-g6a=D11 z87V{rK5Vz!uK31rrhLR0jYdCvz1|fq7*a(VolfV+cs%y>n1n#s9LKqBHk)5?R_O{L zRjpR*5}%v6;@u0D%Vm8A#$vHJ84Ly=JkLAR=TNWLuZP3o%Sxp(qm{ogdN~qMtyX{6 zYPFGMj7!I+8jZ#`Y_t9HB(UTsy_u8AOpYv;r7B0x@OXfq{m-Gs${=a}EVl$uq a5nuqgT)EFPnnVWx0000!$0vZ&VzsXzMWrN-MhjA$s2LqUX{1GE8k5?X zR{YV18rzg2ZT*xKtql^ZI5WzFf-TB`d`4S3F{$#G=`d@*oI}nW&bjwu(I%5(4U}9aQHt)F9hC>;z{UZ>>W0S zZG+YV-*18+i6R;r1bu{=e}pzdzX87Q|DG2|0APOvK1g<%ilcXM-dLu7`jx1N-*vJI`&qcI_%B+sc(IKjt>V5Y|0~;7u$H zW4U`(RaF^bVPQ^`N4p`>(b4Hwu3Y&#JUskeT>h6mb{p6V)`Ksi@aK3qyabqe6%`ey zySlo17A#ocbh%vT9S+CgzP>(=zX|-O48I!1!yJF$z=6c5sHh0MrxM@mOH53R1HO92 ziWSHE`}_H#{WtLW+<}FBI>}B@UD0@weqhU3174Bw+zF~H8c%Wu_zxI9N5*p}7+!8X zNgwcUa?mgGoI}rbh;^y)?(YiqAzSP1+qb*Ly3}|XKV)m|?Ck6n>OE592l3p29;x@z z__V31sVPP8Cu2>lP;9B<_Bbe*^Uw2X11XP_4TLy{QQ#0hUw&9ga;2E{Hxg_-n41c+w?3j=SGeknMF2q z$Z4|S-TTZI@zSMB57B}>eE6`-Znx)>4b!QTY?+yvpXe5`4-E78$&7|N)~#FjH*wmK z4KJt^rp(vC-eGfi6pELi?yVIK4a>>NsS&3Q+2EW@m@*#&-^=j#qgVr_cG&Qp>4k-b z_2RT48`|(aUq8@yfbSF!GCD<{ws??SluuLODf~#Vh^G;0lJGN8q*%m%2K=|GP=dCy znI@qG#WO!?LV-XOF(#n|#WQUh{NpGVn1m8v(hOI|LP9VXqzUF4Ee3O;&_F4e`xmsv zVlYSN(q!nP%uJ(w4O(Fs%-xIPZM(+Wi$k@`<)U-)48X{(b z3O{Pps4*KiZp^xT`SL2oXo#3#72em^cl4SyYnBuj7cW+fMzH9>{YDdR^5n@;iqY^D z>JJ&=LqbBr6{8_Kd_XfJyk-0dfe+Yqz?PPl z%JlT~y@>5^8SnvZFCsK|rKP2P1a8q`zy}^Sz<TG}|oQPlLhIAv;u>+>v>_c7^po4^Oo%Lkc<4|w>1hYxu8!0*oo zyd|(dn}3h9@W89Ox;o3x&(ELPV%ak`13r+-b~B2Vyw06F_YzKEzQd)mXKV(1Ae(Ij z7iqq`U(m2wQmd>eZZCvu2H9_afP|HU%GW$~zE(z<306 zf0{mh`e=5~$)2?-_`u8Z4&-n+=9H9_qz48D`m%d3vuABuJ}`g&e1GBt-QC@VbR7^7 z5Ps~~vGrE@02=)u@qvzxj-BpUJt*jj6DLlrw8{sjO`A4`_y7XHKQsK){QUeBOMGDY z^5p?Xj~;!yt*xykB_(BpJ7#EauS-fw+GT|gBqt}2J9X;RYuBz_yPlYs_$Jx#uB%R` z^L;CPAR;2dAK^o0Yinye+ET#q&8t_h{!GOOhU5n*_Q&zvICt*c61v73ScM)fQ2aWr z%nwlP-_X#|0!Q3Gy<-ie;a$awU#FG%0gC<6h95R>-uw++V+}l)ot<5#_;p&HA3y+C z6c-n_N)rRw%<=NBTw6@@12=BmsM)b&$20N^wr$%s@A~!YF0=dqhQB^HH#bVg=j7x#urYjR zmLCAVDJv^$2#*G}4F$eY<^$exY@e5x_aS=QdGh4RruhLAG0qRbI~u0rx@)ui z0G>TSs=GGJ57gDwy-upTwwxa@5!3vDiJ0aGOvHMAKqHp(0~)cMAJE9~^8@mxG2Hxs z0lxok@yh=TSrqO6g)EBp|3Vf;%?E}d?V8x$swnxuFhtJ>EQlc=upowfz=90&0dKa= zk)e7R-dFR+GdwkDhYxu8z^|462`~VMhV18mi%}8)0000< KMNUMnLSTZewr+U< literal 0 HcmV?d00001 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 @@ + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + +xxx +yyy + +300 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 @@ + + + + + +

+ + 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 @@ + + + + + +
+ + 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 @@ + + + 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 @@ + + + +
+ + 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 @@ + + +
+ 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 @@ + + +
+ 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 @@ + +
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 @@ + + + + + + + + + + + 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 0000000000000000000000000000000000000000..5ca8ec5ac6b815cc849c70098a05334e7aa9a4e1 GIT binary patch literal 256 zcmV+b0ssDqP)_tHdJ^rDBl9{=$dooxd(k0lmCP$> zlu22PJ6Py7UV$j3(krtenCX??BMLnDR7OKE(i7h$Q|xds1jE*&{(_`}Lh0k7){Ne1 zL9TSX^Dmgl^78_|2L|@gk;zrRYhkwqDItnfT0000fLxb3y>s9RB8-x^BMRT0CG|3na5H>r{k9PabzI*DJIwfYsfO+m2Q*N zP3X1&5WC{MBG^T)8 zmaOSxV{YROLIntZDW4+%vGfh0JO;0)%^L=9t8<%?FNkXZ8U^13f)y0ts^CLHsJ4O+ z1);9Um;o3z4gL(o5CBw&3?#xL4Cun|7c523^*oO?K+EBpg{D*JWEE`tya!LU622+i zBBnASJ&>lPC(?}cN?JHfp9Qy_ao;JAHEClJ|K2cCNh(M=>A;`4!#KIKHs$(`$2!))cm*&J2#|_kE)|4M1waYc@d{uDv}Iri!Mg&; zJ~9J6SBPsffOCHjpRQ|QFZ#;05PS-mEEwfbzf^2}j%@%)2m^vD9mc5Z*Esm@SBVaw z1Ly#(yOYZ<)OfcE2~gPp&iMaQ^oarhA8sxWvc{$@6ae1IlRqL~|44c!%}Fn;IUjoj z-{U0~l0$!sT7T p>r=S%LeT@n_P>a33UvSh$~P3DweOCmeq8_n002ovPDHLkV1nAL9dZBw literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/arrowS.png b/widget/cocoa/cursors/arrowS.png new file mode 100644 index 0000000000000000000000000000000000000000..9b2d19e0fdaf93421fb50fe2b0af11ac6ccb347c GIT binary patch literal 256 zcmV+b0ssDqP)CTF{>6h5ZE;H5(H+|f*#C*U}jb=7{DS3mYn?sO$6oqk6;r7 z8?z1=h2V4kDG>h$jlhD5O+=VYz-k2#uuBrwc79{EN8~{Sr*H=+Gp{`&!=2s1O38ck zm6_FCvLuP%AhB$&5tI@S0`Wz2g+P82EaB{n>j$6AraS=@vT)pN3FNK-0000b znTW(%+qq{XojY$)Kok%KTxiCapwPAl3?pH51$@6B7fc9A(mg_-F<|)XUV)e(1Q;Oi z2#**K41YZU5heryNZw&Ur{kKiA}k4Cj3xK2SwvS$gk}i25E)h&Kjg7VC>}^4?nmc# zFlZxK4@oE<=r-^`H>5RSU;&pkzmQF6YryY9WevB|ptey{(O#nU^fgc7rUGIz7X?HC zQGkBVU*(=^wL;R6yg#&hkO%c@9At8iJlxDJ1o(nw2%r55cKTw@0CJ<&1Q>Kb<@G6H z!+HKEVL|vHyq6hrV|eU>=Pub+D#FmT<^f8NAGH@F+iX3A{^Or+IKK1-qwEcb_-))Y z$*Uv4${K)f`~;y<-4Kjr8?rXAZEd3#kh@PJFsUO#ke{F}K&LGai~$FU$_4+88h7IB$~S;00s(y00000NkvXXu0mjfJqaGF literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/cell.png b/widget/cocoa/cursors/cell.png new file mode 100644 index 0000000000000000000000000000000000000000..5284eaec576e1181775c883adc50d544823578ce GIT binary patch literal 268 zcmV+n0rUQeP)r zR#Xf*ayN;mWi;JXP_ zK?sq^0!8yFjrPQ*=!-4r^I4CYw2+7`kQB}HO>c;mVT0O2BKahNf7T{!i8hUGTLINj z+h%NHdMH((AyT$RCWb|$udbH@Luz}BZNwI6vA=i4*a=Xx%a~SdfhJp_F9u?21yn4}&-0cs|U$(W<-5<6VLCu}BmXykycapnZ5$r&?Xjw~Rtjetp5hY_&& z#P$LvVapKki5((f61J?%Cw72@iwE)O1gLpn%q?>)ne&1*j{Hp={xf>W#HZr7 zIA_62@s}pwF#e!=m-zha8< hlCOTvS_^!Xz!PFP&F$Pjh$jF5002ovPDHLkV1g|KBF_K- literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/colResize.png b/widget/cocoa/cursors/colResize.png new file mode 100644 index 0000000000000000000000000000000000000000..4e3e19e223f915e6c0246071b717a14d07647b66 GIT binary patch literal 321 zcmV-H0lxl;P)`D!&n& zICcpl!3c?Pg@jlXETGVBT#YG4E?AHVmZB50z>*6S(2~L-D9G&;xceRpccF3!Dsr*j z55FK{QM%}NqXfkGow6o!3v}^pF38+7J7dh5k|5YD&SHi?rX^Zngp>!rVc9OD1eoms z0!uBoK T(3wME00000NkvXXu0mjf|00M@ literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/colResize@2x.png b/widget/cocoa/cursors/colResize@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6a92cf68063e51617c26edbe98147c3e85660bf3 GIT binary patch literal 836 zcmV-K1H1f*P)#w3oW7(9T}tBkjjcmr%7A)FpZZy}&(AD?6V1?Vdb^qCt1-^DQUNc!o&0xI;X?K^=3K@vB zyVnw5Rh{FnzXC(3Pc3y&UDyCPcK9Pc77XZy5a@vqVCrBRowDy>ufYNM z3@(MC@EzPK4BRETLK~Ot;<1WhVxeP|d-2k-3a}pH!4_zx z4z|Qda+uqa4z}DLEefGMT6BK6%PH{-XPYZBW7(17aM$heIv7ys37C)*mJJlg>uxVN zDDVx~12NfSSx0ff;r5CNY;r+rgO{Yuvdz3Jru8M?|9;K7+(q{~e#b8X0dlvc>f^lt O0000Q)MPdtXje&@u zb|JKgMH)@iAOW#z_4XckSq#rHbYc78;4t@|bA0#S_vRiEtrh>qV2$fe1BG#)p)xiY z7C=0!BBtDJpf9{pC=_1wdc6;m$>jTDv6uk7zTq8W$dQaB16kY@4u>E2`~8nBG~e6X zBcIPls;UwY2n6Wh;D8Y0oJ=ehbAltdl7S>13WXduH#fyxE=P8|9SKWev)KqaoLd4% zHjxdmReQJF&48#V3WDVEcG%=IOMBqY0Xh{YZ#`Y?MMx#Mkkw`>}Su7Ts&1Pe0KugzvN3OlBLS}@ddc9sU8jWN&n<*F!(*FKFAy%nW3U0UiEjZFu zp?BFa9ty%vB9YiVJ3IS)eSO{G5&q4i_=|7QpLmx{CU=p8TyR!jcCv{bTNZVF$#}zv z0OEN^#GWx8+=~D82S+sRi5S0aKs;|ECVg=J%O`W4>km{^mHw>2>}>!5002ovPDHLk FV1o1LMkD|L literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/help@2x.png b/widget/cocoa/cursors/help@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ac53a973386bfe552f27201174ecce63bf1ab7c GIT binary patch literal 1693 zcmV;O24eY%P)@CV=xvz;_K2A(G##LK0->sX$Zf2_wH9L>=zdLH(4}{g%5m9i8v8k zIBel1po_Q5?T^dP&-YGGPv2xQn<9$3y1J;gwwAK8vM3=Tf$VlWfZzUOSFc{h82?xp zd}n56ZXy<9N^HB4L}&@<;vI}=hNrc)^=mGPeu#MW>J_!Lw2;r|Ll8^ASopvfhFG_6 z-_Ag6;c!?^LK3hlKEA!Z{UX=&EQm)(N9oL&GjI+I@I?$5Vj?y;v?TNd0?S6kH#awD zG0{8-J32aO-@bi{NF9iQvVtKtIE*Bea#9mOM~ITT^yKB`rL3*34T9+5!-uHtH^kU% zHph0~N7cXAW?CdNJ3=Gh%Tem1DC+Dq6Al8Ei4+zE_L*N3Za7RH)s0rAl ztvO|K9hjJyXad2|&=AGO#=ccZLqh{;cH_nkB>IMLOiYYFHVj*u*+Z1F7)TJuFkMP$XQs;YLJ6W{-~`Y!3who+{ceg!c) zN3eR1j*h0))m2(vUiOy+PfFreOM&+I@#7t1@Ejl*bKc{>y`sTo+yyOxz^lK%prGJO zZg;Eb-BwyMW5#b3NmiWlXNE#U#A+zxkBB&3z zVBm%Z(c(7BNJ}6}Zdn532-ou;2zKt=xv&zbtE(fkVUnIac`~R^c6K(wzzq$eB?j8G z1W;oRXHn$GID9g_A&ieLb12zrUZ{Za2LhkJ9Dx%T z$5g+-Nepf!0lkEtKY#uk5#eP-B{o02%(k?&WZBo;M!~I=kUojq+S=-r2U(cVA7tQG zCb8Z|xo3aFC9q~%O1w{i1CtkJw+?Qljp`3F##vs=V6l#cmSrKBv{=aDeW1L&+_E1y zVBiF|@*vZf5{whP{H@@tvdu=PPoK6VWPE&_414+Vr6r&A^mGCTxT>qGzXP|jl+YJ* zj8sNuUtixnh#4Fl#LI^&W^8PX44a#qQ^zAFV#A(1d2$b&;8qrM`f{3)%s7>pn3&IP z?N1QgzkgqSuoV{<`-LwqE>cBBh5Fz^Oc*#)Qd08432uEkt-sKCM+bs*nSo6jfVj(7C3x`j6sSkZyMJCf1jC2qkJb19U zr>CbK9m7Zj`#V>zT%pX&%r~|caSZ1$#t;r)#6T<&pDrASlx>ypHnxl;URlE@0`l7{=MpeB*e9ml nk9;5cGwbe$?%jVi_!sRTCBqPFa8!r000000NkvXXu0mjfn4=rJ literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/move.png b/widget/cocoa/cursors/move.png new file mode 100644 index 0000000000000000000000000000000000000000..1360f822777ee41f649fe9275ebb7b3d9f0cd8e4 GIT binary patch literal 284 zcmV+%0ptFOP)8|%*rPJ~u(%?g@wgV62EdJApl3ULCQhp=2ur>>v z;f+Rk^-d8ug|=DsTC3-_3pl(}@Eff=kbr>0K>~uk;Vh{I7+$J~7R3??@X2+(7eUnn zi3GTzD(InPDey)MGiQpVHGCzIxj#YB8_lY~hzCX#N#ON*!AKt)_|UcPmHT7_Wq90V zV>IR+pXqFjw8?9dX~T~;t%cWuS~5sN;lrLE4acQ$919Crk`^;h5Do&C1S5(7;ol!P iMlayN`NxrYWjz7e$SD;$@K)*o0000!yx?M3o=1~5 z<@2qos;ACNAPIpdZZ84)^;*kZ$e8p50Nt_VmIBGqmpxJ-enS}~fP0qr3eCMnU*X0? z{$D{L9=;3MLFF~qhxfXWXi0#>e^`dqmFC{V7)Z1sz~M(8u!}D-ijWWmba+8R7f|6v z30**f*CliT30{}b1tfS~LKoohyis7ZdI?>C!|MWELKmoCHkYrzEZ=nkZj2jy@i&!F z1UP&Mc8(A@$K34h6yWfPx`b}t*i0Z6zHxzg2`-RX3Y;8=WU}VMC5B)rz{Uk&+OrG@ z{G%lXVCvi=ki&SKkiZyBITN^CK7Gr?vP@tJEy2CK3R`?{!nj6`#3lSjW0{9YhuE4-HF~5vjW|p<(({dt5i6565fpA<1 zf5KdA%yhIl0^9vlwlv#rk z6VC7xWbBuiQmhjPMLtX4#V_(DUg+j!)@Q5KXD+Xw8j$t}uA@@(%qG-;00000NkvXX Hu0mjfhkg@w literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/rowResize.png b/widget/cocoa/cursors/rowResize.png new file mode 100644 index 0000000000000000000000000000000000000000..4c16bb8bd662a0878a309094dfed2ddd9b7cf407 GIT binary patch literal 331 zcmV-R0kr;!P)rAEQPC#6a6!K6)QA%vfL z{`|20Pk@6*CLZov0Zv8Bp!T@dJ(m{H7hkIIldhL$hytb*H$tWbc$6v>Gn_8}NkE2G zP#Xrl+*={D1Nt{5M}Y;_*y9pegNyps8e1TThl>Sx%)@Bw1B@|q_*g)WN`pS4XDj5f zfCBXw0%{ba0SQxN1ZaxT;eZp)xZ)OBXyaKsGaH&gX9CvXe()7|t~RkHCzU2Z31^pgj<$0CNt72&aC21Y$`S#k?qqGL*Ph zyp@@%0B!C|J|N+$sD+g1Z?2U^;7UN|0&ep2q9PuP#n36yH9o6Q7m4W!xXD+=6Y(re zDA8xssX7vw3M3`p5N#*n2v?#_T}L82fi%gdlJF6jMDp24ATWXCGm=0cljQv+`~`y8 zP}wq)Pb1Na4Y7frfc@WJ5R0Kv+=Q*dJh9;11uubuvpgu>j`j5EE#Q{e98scAL_`XK zc+vsEDx$`-`doCxy4Vp1;!IqM@0LJRhq`C9-62+o*bVxs(0Aa-SOO$}H}R%;DZ1i~ z7>Ye{B)*8N6arVYIU>f8m|gm4-Vs5<5-2#2OFc|kk_0^B)SbtwUPx#YlV>r{XKK=r=JI*Gx<|;-~m! z2}E_Mdqdl6F(&3EeVo$g$oLZ5!>Cv%&?j+7@N+u4pwltQ{4fPnhq`0hUJ&b?*oX8r zqHkjuH6}6w^m!KFi`@w>=6300K%XndL}o7~SeLvI9fH0U!wD>>hs5rS4t+L_rG(#N z?q6+a(l6%xm($(b<+S&flI1ipQ!J-FHZ*k96Ys>H$EJRd7(HU9*wA=xDY?~aj1~uf z@ml48wrj*Pwv-dt)N|Vh;-ffD@nY+kHUnaKZ0bE4-G4XvajraYVzCm!;NI^286m#2g7)W@uOJ*hVj ex*vXO;FRB3(u>{v?2?uM00009+W-+ literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/sizeNE.png b/widget/cocoa/cursors/sizeNE.png new file mode 100644 index 0000000000000000000000000000000000000000..f62c046575c830a21d7663d4db225accd275b913 GIT binary patch literal 279 zcmV+y0qFjTP)1Cl20R)R)k-y#kzNKiVNiGm7dO~D@ROhJt_ zq(H%-lAuis6e1MEaG8LDad>liZrr`@CQz)uty2&`wyb- z-H*bLC}5BX&z~#>XnJRSm+%~sgpJ1Qh)|=!1&1Z!zL%WAifl=^?=#hiM0dvP3g+iu du$3Pucmw!wX%mWe*wz35002ovPDHLkV1jLRdW!%6 literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/sizeNE@2x.png b/widget/cocoa/cursors/sizeNE@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..98d19e9efa143a3bbbe0e572ff24ff11138f8324 GIT binary patch literal 794 zcmV+#1LgdQP)Ks0lH~hnTDa2LTzLL8_nuTHrb8fENZj9Mb|#Py-c;P$D}4*?oqOEEMH^MM7jP;PPexXju>l zDNzAl9s(Y3R_C-BYJrYH9Y5sMsAj8XTvm4Y;(qjq2h36T@#g?kD&GzJYbj!)^+OwjX3%u*sd*(bmRy z@CkeXON9sREcDXWQ9?iO)Ik`rQiflHkKASE;F`?%dd6`hd8D=`AfL3U!lO9uD|pXy z&V(TcWCX@!2zsCmsJ(y_1z1HfmC*SL zLpvHS@nmor-o_#>X~*R$cndzqg#RKJtTDV$4~6l_vb>GT@4b{*DdAV(9hk?2FR9^8 z7|Q;Gj1p5$r+cyhuff<99;B|UjuKrkV5cYK3cLa#$M|~ZPcQq2xjJYwY>$MzR=3-E z`^RaIYr@y|@EWg`IYtYAegV^eqszp!hvzSl{x<=_>%}iLg+L*YNfkeI7XpPqA#i5? Y0W#W>Kis2_oB#j-07*qoM6N<$f^DmA>;M1& literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/sizeNESW.png b/widget/cocoa/cursors/sizeNESW.png new file mode 100644 index 0000000000000000000000000000000000000000..0a077fa67414f7e3d4271614b7e6db84833d4f5f GIT binary patch literal 300 zcmV+{0n`48P)as@SL(?%$(a|I6cEE4eh9=KeA2SZ{MK?I>ha0bhUU<7TB zpyHiH78MF)c>}uda>f*BtLRD4%JKQ4!dj@(q&}@O@Hq2bV y$BIR)q7w5Quq1+UMO0SN6qLSHY(YVOFL(o3yc^cNOT_p90000+yT9eX72)a-KVhi*E#8$*R|Np&N%y~E?U5#uxb}~jq zxzZ;!sS}@XJf69&??q8`r&$ZE1=a%M#T}}(KvG%6CQc4)ViBVP^0Sr*T>@oLB_8lo z@OW&6pLK(8=%ARleF+CV*+|p=E{akBHD*d&}R5qT6j?_6^L!jB9v&hO2h?a&ih8z@RP~p zRygPar^^b+#>^LAT<22hks1h8cvNC|+qP9md1L>P68b8XS3pjSy58G}?ywuVDo6xSs}M^F_(ciGjjRujqa~~ODa2Mn z{%yMgi8_}`$FzXW6E(PF4%oZA1Y>rR-@y&}34VZY;4?eSSKt{4ISqHz`Y#2tRGxOa z4qB=oqQnKmy#?DrnYjeS+GGJ|Fe-wPIk7c)iv&7F&@sm-ASir9Z1UkX zWs}X<^yr~^u4&{Wn!b#R$@9>JW1t6%(u0M$3W#gifq{~J@xe3fG)WKcq*sdjKOAr& gh7YIAm+AlXACnbt4p~|!kN^Mx07*qoM6N<$f?n5nEdT%j literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/sizeNS@2x.png b/widget/cocoa/cursors/sizeNS@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e48fd0cb3a461667e2280e008b62872d14e69f30 GIT binary patch literal 660 zcmV;F0&D$=P)%@94baf*t%I=5u$? z&)R$*uY#nj2IoV*C?}&seq(nzbxEIh*UQ5Ec zfPV7&5-tUFlK+#08v%9lMiSNp^piK2uq6GapQaN3|>K@Ay6>(nR7F)b-r0E*}REcAm)ab;2I3U6L`}Qcw_94b1%78 z3|e+?;&r*)n@=7v|HIcRbMZtHEv18| z9^9}YnSw(1CT34C<=h+FO}&wXdGh80CK3k8+pUFE5{oU#FLovGx|mZ*Ou)xR@*iNb zD|vSU>OZGGoAnWxkP)x-W%53pkx7YFaW?=%GT=3Nz~Rp^t2|-?Dd>_^9x-`&PAm`k uKwKdY`FuX;Rvxqdx0CvyqW)Kd9RfenDKI|(n51F=0000^ c3a}=@8#Ht1B3FEfZ5Xo41a3OeA~BXoFA3p79#__{>e0+tqh02(_3Lz7?@(iL!l&l&6* z2)+d>oD?T*0h>QmV>t}9Kn_6$KV)guIVo-y0v>CA0H%y3N${`081$4t78!7W&$OR_ z8RLl?{97=mK$jWyvJwcmlbnJJ#uO*`CAeV*Ml8i@R*Tr-g6nYh7W)J&%X*Mluh)Ne z7!;T>i)~JdoBlRR0WQ1=xD*0=B{rMQiudPRz;T>MCAQnG%7iMRiYTGu3Sb-}p)xT5 zO>G;c#E}HlP}gkRj*{5$zCLJ3i7W(O>%r<{wOX-E81!KhP1#nYCLnepIrG2Of|U{y zj!22Lw$W2?9uoXda0h!8;$qi zBlxCjJ&A-^*oFkyN`sNsn9cb)BXe*GK7gf?h}m{@L0#{YOtsTho}hh}(wL0E2^oPY zxB&0Kmn0J6q&6Z@(jFyS+>v^mtqy3D4j6E|J=Y~zCULS*khx?a4YDFh0EgGTY9yAUV@3W1z+56MU! Uv&eqyPW_ literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/sizeNWSE.png b/widget/cocoa/cursors/sizeNWSE.png new file mode 100644 index 0000000000000000000000000000000000000000..0574a584c196e6698cf140c109eda79052a4ab65 GIT binary patch literal 295 zcmV+?0oeYDP)i}!&;biP2rC?0emAv(4urH5%DP&C1p_A%@cjU6t-ynr5o}j50#_>tU@8gle1OYu z1yT~ZQgc|qW=qJcWeG zS6IKvj(Bdzl@hf8LZHNeo|2i{RT4}fNP!-+EkK<>j{_G@)J!m@QtHn@R+#mDt#G{P tEs`l!ID%<~<3Ya%G6#P~6f{hG!53Vd8`cnp8>|2T002ovPDHLkV1gxwYefJ6 literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/sizeNWSE@2x.png b/widget/cocoa/cursors/sizeNWSE@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9a0a276c34b502ff09066b53320fefefabd2c6e7 GIT binary patch literal 976 zcmV;>126oEP)A762YC>H@EQV; zCmzTN8H;nKCm@3N7(@}2Kp9j)4b+q1dsQAAfda@&iL?bQ5qtom_y&fM5Lrm%Mg&|= z7=gwO1Yc%pdB90qz~)3nmP1PfG7Ea}Etb|AFN&LmfX|xW06oTH68r#k!MYO2A_ER5 zDuXT1XFT%;{}k*{puvp#SqTJONjhMUG0hYF88~DH+APIFCLdx7gq*Ys_OBDLEbF(# zcs#y}F(}Yy7Aw3cu3AR35U7J~FcbnaNlYe_5s&}31suouqr`MNRhdvF6v`61bpec7 zBvd9gKuIj4dP`v?k(vOP_IH0tYf!IYy(=m4SrHgvVfW#0U8p zBdGy26_Bfte&v5?AK2Ko?L-Lud$1K*_tgZ`Vu zl*euI{1w&RCiG54S#G8qSqnVSH!1QCoP!HWj5P@zU82MDw-PQ;lX3-?lWp2cY=WNF z9vlje!AtO7)%u(g13h@L^fttY-Z2q~c#^hg%y#&*PjKV|0{c8)-BiF95+2ACpRc(@Nk)`V znK(9(kOBu_+e|=80J*&rTf?P<%EVpp5S)PXSd2LYhQqTb35h@~z4yQ)@Ctks z4jBu`f6&n-xN^xI9Gn-^)V4;Y1a1Hsi})@2Y_YJLJ3V$fzumVXTZ0000ZaDMbPUEI1MiOf7+7(?OWun$JuPfdvB_|KWPfNOS@=32ds6C5hQc2)2?5+`vbWj9>=6RuCmV&b+#S z>A;zdnh8#liT(`II1s=J9^f5ez3RTAG?v>BeA79?Iwoic?@hrc+6FaQ5vA3D|6m&DuZo$VwC zA6Akhc|OVaUe?~pvg}q<3X}q+Kq*iPlmewdDex}@Hnf!jdh>`c@W^HgXrUFTfGVhw zDhPnOk`dQkn}7@-FmMEtjBWXu$7<{&B#bx_kl`yJ1b0Cr6K#2n=R)?S#QFqe_z0w+ zlNGe(IrfQ!#Cim@vI!ZiD*|7_w&1ZYNadSYhkyqZsIfFUIpHlxI26!Ir%@c` z(Y!ot3izBJ6Vua|5{t!Ru1Rd`5&}hjPr($mBf#=ukf8;M3J6Gr*ALmR%414M*b@LR zO%k5x6-lHZ1`&zHS`urL3V2pa2n@g&9En;tNnEhseb538(h?7&4ibIf+Ze_SMG1SYNpiX>7lQbUG#56;AZ-3$RD%PHo-+v6H>D8g@;0HJ`sQun|}guh(^ z*9vc*g<5MVq3kakCxO0L$Xp6=pTsS(1%VOhiLHcN0qtv+dwl&*z$@?$oPy8b9DD`e zz<2TmoUzXl>wn1EEaijoT?+)7K%4uRhu}H4+;2Ih9)lC`LF1T`Q!vr?K`oHj+D@BR zWyKrCeC=IMr9%cf0AsGO6Tz4@KVtt~v6T*h=Tbm!D-zIPh#dy%v#bU=?DKq&Wz%B# zhz~`%6JVo&(j>MRq7Ir2)y{+Gn(Uiu|4K4E%g4Ha`P=G{L?AX0fw1V?Z8rku-wMOh>)d}41&<1(5W9_>D2!J$D~)d!y^#z5xe=|fIQ?JN$`|&{wqh| z2(+{3GzSc@pd%KTy#j{PbLOni+?keuln)fI$cWVk>hdxWLANC!&)9*UcraGvPgQ{O zkU5m3iu?s;?GZT;DUgv;{suQNXfN3pU#yUC3j{D>S(AUk9gKy%Js|G{cqwlW5bm#_ zFU7h$s0;&_mVg5hJir1zf`$7CoH`)?141KIqEP)uMpOPFuSn3Y{_sPKUoJqVE6yD~8%OK85Fn<=X!ejF{$C{i&iE;(Z@O2P?yP%gC$7YQG3pke&zaW#m=Z(I4Wyjh3FHr-CNW?+HGu+Nkpi9#+k(vq zpC!?gQYuD(!Hia@h%Xr7_a*##yuglFKcancC%NCXZU{%2NrV{Kf3IhYh(LOx$; zk0FPa9zG(+e;ED>yaJEGeQgofide)-tLuS;v`CyV{Ew{goG0P0!2&!34_&lT5$J*= z5KCyYhF7cA?UvzJTKID?0~1#tWD11f06YLsm^W=9Ccf`$p($ezztF?G+Da&Pmk5l( zR1Yi2W2KZeJl~LR$?T3o&qG=vfptn;PXTsc8{3dMY12F#Dd}8f%h6$9J>T>xK(}*9`X5o zO`zslz%#z*4j3Y4SDAr1g>+6iKBaKX7CK}(wm`$OWp*Nh&3lL{!IDWkKynk2V Q8=z4Pp00i_>zopr0P=e$EdT%j literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/vtIBeam@2x.png b/widget/cocoa/cursors/vtIBeam@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..41c47af116acb9570776a457f238e2b12a2eeec2 GIT binary patch literal 336 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(Fp7J+IEGZ*dVAfF>rj9I%Z2$K z98D2b?9%HOEM?yysPn~PqsK$eOUulT2unI{*d!3UeTLJ94JUTF%xu-X`|#xTKP-Yj zUkW%dFtR`i2X{uTz*n8DvRZ&#h;GrQjXKa z8n{lMdSI?xbbA_I_}T<4EutFki5Xam|w~C YcVaG^jL6DhU`R1|y85}Sb4q9e0N#>(wEzGB literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/zoomIn.png b/widget/cocoa/cursors/zoomIn.png new file mode 100644 index 0000000000000000000000000000000000000000..275bf1c69deca4473b598927233865ac83616fac GIT binary patch literal 655 zcmV;A0&x9_P)IrpCX-RBh|`m6YFW@FeHZpKxH zSFkBQyKpXLNZ!cha(BAjZq6`_i`i`U9pH7n)9JiN3>-7TW8a|E^MS~BqKp;S)(MU_D(+^}^Hr4L0HJi;-?9}OWQlrrzF=)+%7-A*|+*Ygg z0lBJEKp8uO!QeCI9FNBdwyFUP9B_xj;TPmejg@I33M^rACKSJkLFB#lQ zrE-Q`)KFe>EEYTFOB+LikRm@OS~U0&^ZWe-ZZsM_;oLn)RVh=({X(JeR`t-Zfw@!R z_qJTJ?Y9q&vCVjt&*$H1nx0 literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/zoomIn@2x.png b/widget/cocoa/cursors/zoomIn@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fdd3f8e71d4eda8cabb8789ffdca66ce8068b27e GIT binary patch literal 1717 zcmV;m21@yfP)q zgg4=h5ZWJL0%Ckf1@1&J+N{4B38x9X0-wMy2pI75nU`(d>q$l@?Lk6JE+~iy(ghiU zje<7;UT5%Ggl+xo>(OMO;k8U&Cmup#(P(tjty{N_kB^UEo12@vy|}pe&$DOG<^f#e z-o(VjHMBunk_l-dn#5Wp@1YceNFpaEC+pt5d*9E`&yT2=G_*w@^d*rXeR^IU3F@40 zEDTAswY8N$dGe%R(eB^BuP$G{tg5T4RbF16^85WN5{amyq9RpOQ?vYRcz9T8Ltpry zyu$R&`f%1eZ`wqbo^4HjRyq>cN8t$~4@=Gqlm|T`rfpa^;GGiO*U1 zCW)|9HKAvlAat6Wn;V~hVP zC!~`u$m#FzuhW``1auM&4Gk()Lm#W0bWIEl4E%stjA>s=?BwNKy(tg~Y?WDKIZAD9 z?Mm=-=gz4_W@oj-qm<@u>orxXk^5DPIG+iRDA+xqr9J3GHZfYH%W=w53clCVtx zeRWO1Cw#*Y%NPZ>-Dxw(=`Ah`Pb-|)*VmiUu}#2~5`4oD3o#ixsHass@`(tt1>2^k zrv8L;EJ!-w(9qDT3BZSL1K%)-^#@`yb_BWDA>iW>ZV~K|<$n^+W3kwBuy{OfCY%zi z24A=b-!Q~NOvX-UE-pLz&k)1}#d_hw_5i`Es;V3#0AIN0^Z8a5Hu-xOV`tc@12hhB zKF$~HmW5`)N}#N)%rOG+WhH=Eh>6(DAz+UH3s|t{>C>l=EE2-@z%c^wWt9+OA*L>5 zM+i(#PGSYdPV$fZfR-x7S2aTMjTnlxcB+b zLI&Tk=H}+_5L1xH97tdl(`Z^+TGivnkH-)I%RUpscEGu<1WmRQhb*(u20aVGEs^>{ zCMfOh?ruu7nZq_YRcmT$TInAOh2Yale!pO+AlEhlb1MN&O43K9!0sYQdwctH+i6H5 znV}80(;C{3UIUg$VwWKy5Aq^`)Hid2?Sj1*E?oF}VPRn|)eoA74)hU_B7C^+6+e zTTmi6B5(g^js*0JBIIZ!{isJ<^hx-f2LmRF&B(z@!p!u z6CJByRrPwk0IqQl&#+Zi7E6Uhwn@U)M?DvpB9b^Fd!tXJpnsj7p8i`UH@#gRH?&Uq zBqkwpopXYOK335rc2G_S1l6(;J8|jKr61+I=(n-4vA<-tnMR!exRxTm+tbtYdtF`K zFET1;q|X^mBFRXYBbFtIT1dE3J(bfWVyFog_Flmu!H0s61;+)S2~GmMJ|Xy2a18(Q z?=Sd<7tN6{VKuu)FXqwWMizDfr`X+;Ri)q{w~19_AqRw&3yHUjp^B!?ck z07opFB#KGm17%n^97e(KV-4l$^HZmf;!$ZyVT_tdVi#w#gT|ZR$5AR|+o(P}b((-} z5|l-p#l43MWhrH|lgZ~$dO_p2wRJI*M3mEGF4vcBEZ9QICY#BJjO5qX3=&}^Ng~-S z@+@jT>inB`o`larBnlY6Ew6j>$rnlMluYoRXN{A$KONise-{4&?<$PSMQ=Xy00000 LNkvXXu0mjfNNq9j literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/zoomOut.png b/widget/cocoa/cursors/zoomOut.png new file mode 100644 index 0000000000000000000000000000000000000000..19d2d89125e4d1812b5397bb285f34cfcef8d818 GIT binary patch literal 650 zcmV;50(Jd~P);(b=8jVI;I-Pzb?Xsy(f4kXizQ;+OPA4@Q4O(Q_0|(qztMvh~ zs#`!A>x04IEBYLd$4O=2fIA!xzads~EHudP{&YIMK#y@U82kPR7&vaXn>c2ISdsHW zgT{BRS^&zLOeQ*FkwaOa-|v6NwG)X1sSF%&Ii|(250FFIqH?+X5jwXzWN<5$${Avj zLpkJFEOy948$*JSB0nZF8+_RF`~3uNG#WkT*iA@PDO1L7p-?zgJv1EPw|U@qONMN_ z^r11<7?1P${98@a^h_p0p-_lC9uF{IvkxaXm}f3>SM}iR^6c-SNF)+@RVtM}^?JQF ztLrDL#o!O!k7BX-nQw3+NzFy>SS_-Kb}^nXUNVjZFBs1l&l$%HimWmm%FngTWL*@p z11ZXSQ-1duheTv9uz~pN{Nb#?Cq4Hw4oHmGPpuZP^!1gV?=d2h{Gp;nOBF%F4Z*5n k$V(M*AgRD1zJF)G01ekxEQLv)^8f$<07*qoM6N<$f)zI?TmS$7 literal 0 HcmV?d00001 diff --git a/widget/cocoa/cursors/zoomOut@2x.png b/widget/cocoa/cursors/zoomOut@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ed46ce75e1e1435a0d7d91a55e12633dde3a086 GIT binary patch literal 1714 zcmV;j22J^iP)Rsa&O+ed2n=e^upBC)E~36v;V$$@nRZ4 z8`mB^dUOGO(3fPwnusQ`63P20g%FY`EG%3*I5_zI^z`(wTBM;bp24#u5~5Gv@{yp< z`NkrUM0a<0^|NQs`W5Z|{rl?d*|Vylp+OZF7pq_}sG`xRDl02fjg5`-_lAaslr}sI zACy;wzF8mcn#V{!3SIa1_8t~#Jl2UHJ$h96d_IWymGFAK=sW)m3_h-1yY@M2C2ElH zxGfu-=Eqbe#Ylt|^J`xu#v7cVLpeD(G9eF@(N z3ChVm%|{TRtE+3@+}zv*0-rl~4sUcJDafX+txY|6@IcvyYq*C#y1&QcQ5_u}3MM`$ z;hQ8PZqp#QBhH1a&q!E0$#dw2?;DDbNcjYH8C-vQW^T7uSo*FVDOC?oOVNc zc5*{HS%Sj;{{CjIc}PGf(c0Ro(ltC|%1PJ6ty{N#KrF^|E+szX=UknenVGp>W{vqM zO-)S;!B3nxp`6;ulP4Ga9654C!4Lzn5RF;w}1Ka<*yLn-o1O!z1DmR*`$_} zt_k>rZx~`(qu_NqZH73#C1m00g7cP^mK4%)E+zPeAr@jXc1TaFPUI66Oyvf zz}VOrR$%Njm%WJPyWzGg1WXCtxpSvyC8klX#yv}xkX^)`ot@vp`S9?tD+?L-KITF; z?;Cdh{Q2(?Q&7wtNMH%mXhudx-P5N}M-TwZKHnO)1MY1lXtI^qYs^Ax^elu@Bhp{U z1eI5>UTsgdnZq_YU2AV|UwA$o4#TH8A?y-t5fnKlU~eU$NlCh13hXX|^z`)D+i6H* zQL-_&(*XVGHDHb;wwe;syHXNJe={f8B-nB4)Tys$W@e_+{h)dH@S*)dvltTguGC8s z>2Ie6rJNadOF{pH#pYF?l%=Jms+Jkb_CW$tPuwAkIxzmEis3$B%y_2U$bO zj#aR#`u%5#~`NjUnb=i*XE67{k-`a}x)*YWZ3e^hGI+vQP9>y%G& z5+c{BrIX;Xia}yC<+NMSARDnmXU?4YQO=8g8yOk-TV|Va)CqvL6!G2b*RTKH+}!+& zjLI>2=9ngtVkGPlTPuheBs}S!%4rgD)C3EAhhVSZL&3*_gM!ZlhXJ+^2|g7Zz`y+a z3%=o5dn9a_=Je>rK3dFSVV81>-9}l}3id!=g8cy7dpTOW`0qXvKA^YIOwI=CC>e?D z^hSXs?89BYjY2MV8!4@F650;1UCza63)}Ass!8l4lLTk0#g2mC$v(+vAFp%8&gZ-x zXPqPfwu^bai0$IaTB0NZ&a`xZus(rd6u z*vV&QWmU8G3Ruh}u*^R(5kcc#m_!OV8t({dP!}q>VFS6JBep@XHq{6?CzBd_Y7C&!X$HO*L*9Syz}YU>Ho9%4<#_{YD*7bo&W#<07*qo IM6N<$f +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 + +#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 +#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 + (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 sWakeLockListener; + +static void +AddScreenWakeLockListener() +{ + nsCOMPtr 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 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 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 (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 + (::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 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 +#import +#import + +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& 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 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& 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& aFontRangeArray, + const bool aIsVertical, + const LayoutDeviceIntPoint& aPoint) override; + + void ResetParent(); + + static bool DoHasPendingInputEvent(); + static uint32_t GetCurrentInputEventCount(); + static void UpdateCurrentInputEventCount(); + + NSView* 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 + 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 + AllocateChildPopupWidget() override + { + static NS_DEFINE_IID(kCPopUpCID, NS_POPUP_CID); + nsCOMPtr 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& 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* mView; // my parallel cocoa view, [STRONG] + RefPtr mTextInputHandler; + InputContext mInputContext; + + NSView* 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 mResizerImage; + mozilla::UniquePtr mCornerMaskImage; + mozilla::UniquePtr mTitlebarImage; + mozilla::UniquePtr 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 mGLPresenter; + + mozilla::UniquePtr mVibrancyManager; + RefPtr mSwipeTracker; + mozilla::UniquePtr mSwipeEventQueue; + + // Only used for drawRect-based painting in popups. + RefPtr 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 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 +#include + +#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 + +#include + +#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)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, ®ion); + 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 CreateForWindow(nsIWidget* aWindow) + { + // Contrary to CompositorOGL, we allow unaccelerated OpenGL contexts to be + // used. BasicCompositor only requires very basic GL functionality. + RefPtr context = gl::GLContextProvider::CreateForWindow(aWindow, false); + return context ? MakeUnique(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 mGLContext; + mozilla::UniquePtr mRGBARectProgram; + gfx::Matrix4x4 mProjMatrix; + GLuint mQuadVBO; +}; + +} // unnamed namespace + +namespace mozilla { + +struct SwipeEventQueue { + SwipeEventQueue(uint32_t aAllowedDirections, uint64_t aInputBlockId) + : allowedDirections(aAllowedDirections) + , inputBlockId(aInputBlockId) + {} + + nsTArray 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(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*)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*>(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 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 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* newParentView = + (NSView*)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(); + } + + 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(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& aConfigurations) +{ + return NS_OK; +} + +// Invokes callback and ProcessEvent methods on Event Listener object +NS_IMETHODIMP nsChildView::DispatchEvent(WidgetGUIEvent* event, + nsEventStatus& aStatus) +{ + RefPtr 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 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 widget = GetWidgetForListenerEvents(); + + nsIWidgetListener* listener = widget->GetWidgetListener(); + if (listener) { + listener->WillPaintWindow(widget); + } +} + +bool nsChildView::PaintWindow(LayoutDeviceIntRegion aRegion) +{ + nsCOMPtr 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 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(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* nsChildView::GetEditorView() +{ + NSView* editorView = mView; + // We need to get editor's view. E.g., when the focus is in the bookmark + // dialog, the view is 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* view = static_cast*>( + 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 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 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 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(); + } + + 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(); + } + + 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(); + } + + LayoutDeviceIntSize size(mDevPixelCornerRadius, mDevPixelCornerRadius); + mCornerMaskImage->UpdateIfNeeded(size, LayoutDeviceIntRegion(), ^(gfx::DrawTarget* drawTarget, const LayoutDeviceIntRegion& updateRegion) { + ClearRegion(drawTarget, updateRegion); + RefPtr builder = drawTarget->CreatePathBuilder(); + builder->Arc(gfx::Point(mDevPixelCornerRadius, mDevPixelCornerRadius), mDevPixelCornerRadius, 0, 2.0f * M_PI); + RefPtr 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& 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& 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& 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& 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& 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 +static void MakeRegionsNonOverlappingImpl(Region& aOutUnion) { } + +template +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 +static void MakeRegionsNonOverlapping(Region& aFirst, Regions& ... aRest) +{ + Region unionOfAll; + MakeRegionsNonOverlappingImpl(unionOfAll, aFirst, aRest...); +} + +void +nsChildView::UpdateVibrancy(const nsTArray& 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(*this, [mView vibrancyViewsContainer]); + } + return *mVibrancyManager; +} + +nsChildView::SwipeInfo +nsChildView::SendMayStartSwipe(const mozilla::PanGestureInput& aSwipeStartEvent) +{ + nsCOMPtr 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 +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(); + } + + RefPtr 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(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(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& 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 +nsChildView::GetDocumentAccessible() +{ + if (!mozilla::a11y::ShouldA11yBeEnabled()) + return nullptr; + + if (mAccessible) { + RefPtr ret; + CallQueryReferent(mAccessible.get(), + static_cast(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 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(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 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>&& 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> 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(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 observerService = mozilla::services::GetObserverService(); + + if (!observerService) { + return; + } + + observerService->NotifyObservers(nullptr, "live-resize-start", nullptr); +} + +- (void)viewDidEndLiveResize +{ + nsCOMPtr 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> widgetArray; + while (parent) { + widgetArray.AppendElement(parent); + parent = parent->GetParent(); + } + widgetArray.AppendElement(mGeckoChild); + nsCOMPtr 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 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 rollupWidget = rollupListener->GetRollupWidget(); + if (rollupWidget) { + NSWindow* currentPopup = static_cast(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 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(floor(aDouble)) : + static_cast(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 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( + [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*)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(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(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(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 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 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 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(mDragService); + int32_t childDragAction = dragService->TakeChildProcessDragAction(); + if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) { + dragAction = childDragAction; + } + + return [self dragOperationFromDragAction:dragAction]; + } + case eDragExit: + case eDrop: { + nsCOMPtr 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 )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 )sender +{ + MOZ_LOG(sCocoaLog, LogLevel::Info, ("ChildView draggingUpdated: entered\n")); + + return [self doDragAction:eDragOver sender:sender]; +} + +- (void)draggingExited:(id )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 )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 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(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 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 targFile; + NS_NewLocalFile(EmptyString(), true, getter_AddRefs(targFile)); + nsCOMPtr 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 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 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 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)accessible +{ + if (!mGeckoChild) + return nil; + + id nativeAccessible = nil; + + nsAutoRetainCocoaObject kungFuDeathGrip(self); + RefPtr geckoChild(mGeckoChild); + RefPtr 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 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 + +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 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 mClipboardOwner; + nsCOMPtr 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 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 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 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 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 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 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 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 currentFlavor = do_QueryElementAt(flavorList, i); + if (!currentFlavor) + continue; + + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + + nsCOMPtr 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 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 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 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 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 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 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 transferSupports; + aTransferable->GetTransferData(flavorStr, getter_AddRefs(transferSupports), &dataSize); + nsCOMPtr ptrPrimitive(do_QueryInterface(transferSupports)); + if (!ptrPrimitive) + continue; + + nsCOMPtr primitiveData; + ptrPrimitive->GetData(getter_AddRefs(primitiveData)); + + nsCOMPtr image(do_QueryInterface(primitiveData)); + if (!image) { + NS_WARNING("Image isn't an imgIContainer in transferable"); + continue; + } + + RefPtr 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 genericFile; + rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericFile), &len); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr file(do_QueryInterface(genericFile)); + if (!file) { + nsCOMPtr 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 genericURL; + rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericURL), &len); + nsCOMPtr 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(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: + @"" + "" + "" + "" + "" + "%@" + "" + "", 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 + +// 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 +#include +#include +#include +#include +#include + +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(¤tTime, 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 + +/// 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 + +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 + +#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* mCommands; +} + +- (void)startRecording:(nsTArray&)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 CGImageRef from a frame contained in an imgIContainer. + Copies the pixel data from the indicated frame of the imgIContainer into a new CGImageRef. + The caller owns the CGImageRef. + @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 NSImage from a CGImageRef. + Copies the pixel data from the CGImageRef into a new NSImage. + The caller owns the NSImage. + @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 NSImage from a frame of an imgIContainer. + Combines the two methods above. The caller owns the NSImage. + @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& 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& 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 + +#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 appShell(do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + if (!appShell) { + NS_WARNING("Couldn't get AppShellService in order to get hidden window ref"); + return nullptr; + } + + nsCOMPtr hiddenWindow; + appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); + if (!hiddenWindow) { + // Don't warn, this happens during shutdown, bug 358607. + return nullptr; + } + + nsCOMPtr baseHiddenWindow; + baseHiddenWindow = do_GetInterface(hiddenWindow); + if (!baseHiddenWindow) { + NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIXULWindow)"); + return nullptr; + } + + nsCOMPtr 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(aDataSourceSurface)->Unmap(); + static_cast(aDataSourceSurface)->Release(); + } +} + +nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface, + CGImageRef* aResult) +{ + RefPtr 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 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 = gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(scaledSize, SurfaceFormat::B8G8R8A8); + if (!drawTarget || !drawTarget->IsValid()) { + NS_ERROR("Failed to create valid DrawTarget"); + return NS_ERROR_FAILURE; + } + + RefPtr 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(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(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& 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&)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& 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 + +#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 +{ + 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& 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& 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 + AllocateChildPopupWidget() override + { + static NS_DEFINE_IID(kCPopUpCID, NS_POPUP_CID); + nsCOMPtr 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 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 + +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 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(kid); + kid = kid->GetPrevSibling(); + childView->ResetParent(); + } else { + nsCocoaWindow* childWindow = static_cast(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(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(mParent); + while (parent && (parent->mWindowType == eWindowType_sheet)) + parent = static_cast(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(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(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(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 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 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 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 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& 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(frame.size.width, 1); + height = std::max(frame.size.height, 1); + + nsCOMPtr screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1"); + if (screenMgr) { + nsCOMPtr 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(aX), + static_cast(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 +{ +@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(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 widgetScreen = GetWidgetScreen(); + nsScreenCocoa* screen = static_cast(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(aData); + FullscreenTransitionDelegate* delegate = + [[FullscreenTransitionDelegate alloc] init]; + delegate->mWindow = this; + // Storing already_AddRefed directly could cause static checking fail. + delegate->mCallback = nsCOMPtr(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 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(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 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(child); + if (cocoaWindow->mWindow && + ((aShown && [cocoaWindow->mWindow isVisible]) || + (!aShown && cocoaWindow->mSheetNeedsShow))) { + nsCOMPtr 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 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& 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 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([[(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 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 + +#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 +{ + 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 +#include "nsIWidget.h" +#include "nsMacCursor.h" + +/*! @class nsCursorManager + @abstract Singleton service provides access to all cursors available in the application. + @discussion Use nsCusorManager to set the current cursor using an XP nsCusor enum value. + nsCursorManager 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 + +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 + +class nsDeviceContextSpecX : public nsIDeviceContextSpec +{ +public: + NS_DECL_ISUPPORTS + + nsDeviceContextSpecX(); + + NS_IMETHOD Init(nsIWidget *aWidget, nsIPrintSettings* aPS, bool aIsPrintPreview) override; + virtual already_AddRefed 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 + +#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 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(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 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 + +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 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 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 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 dataSurface = + Factory::CreateDataSourceSurface(IntSize(width, height), + SurfaceFormat::B8G8R8A8); + DataSourceSurface::MappedSurface map; + if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) { + return nil; + } + + RefPtr 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 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 currentTransferable = do_QueryElementAt(mDataItems, aItemIndex); + if (currentTransferable) { + for (uint32_t i = 0; i < acceptableFlavorCount; i++) { + nsCOMPtr currentFlavor = do_QueryElementAt(flavorList, i); + if (!currentFlavor) + continue; + nsXPIDLCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + + nsCOMPtr 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 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(clipboardDataPtr)]; + clipboardDataPtr[stringLength] = 0; // null terminate + + nsCOMPtr 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 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 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 currentTransferable = do_QueryElementAt(mDataItems, i); + if (!currentTransferable) + continue; + + nsCOMPtr 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 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& 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 mFiles; + nsString mDefault; + + nsTArray mFilters; + nsTArray 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 + +#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 sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID); + nsCOMPtr 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(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(currentFilter.get()) + length:currentFilter.Length()]; + } + else { + titleString = [[NSString alloc] initWithCharacters:reinterpret_cast(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 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& 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 localFile; + NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile)); + nsCOMPtr 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 localFile; + NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile)); + nsCOMPtr 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 localFile; + NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localFile)); + nsCOMPtr 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(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(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 GetInstance() + { + RefPtr idleService = nsIdleService::GetInstance(); + if (!idleService) { + idleService = new nsIdleServiceX(); + } + + return idleService.forget().downcast(); + } + +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 + +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(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 GetIntCacheImpl(); + virtual void SetIntCacheImpl(const nsTArray& 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 + +// 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 +nsLookAndFeel::GetIntCacheImpl() +{ + nsTArray 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& 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 +#import "nsIWidget.h" + +/*! @class nsMacCursor + @abstract Represents a native Mac cursor. + @discussion nsMacCursor 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 NSCursor. + @discussion Creates a cursor representing the given Cocoa built-in cursor. + @param aCursor the NSCursor to use + @param aType the corresponding nsCursor constant + @result an autoreleased instance of nsMacCursor representing the given NSCursor + */ ++ (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 +[NSImage imageNamed:] method. +

The image must be compatible with any restrictions laid down by NSCursor. These vary + by operating system version.

+

The hotspot precisely determines the point where the user clicks when using the cursor.

+ @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 nsCursor constant + @result an autoreleased instance of nsMacCursor 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 NSCursor + @param aCursorFrames an array of NSCursor, representing the frames of an animated cursor, in the + order they should be played. + @param aType the corresponding nsCursor constant + @result an autoreleased instance of nsMacCursor 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 nsMacCursor 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 nsCursor 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 nsMacCursor 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 NSTimer 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 NSTimer instance associated with this cursor. + */ +- (void) destroyTimer; +/*! @method destroyTimer + @abstract Destroy any timer instance associated with this cursor. + @discussion Invalidates and releases any NSTimer 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 0 to -[nsMacCursor numFrames] - 1. 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 nsMacCursor that uses Cocoa NSCursor instances. + @discussion Displays a static or animated cursor, using Cocoa NSCursor instances. These can be either + built-in NSCursor instances, or custom NSCursors created from images. + When more than one NSCursor 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 NSCursor + @param aCursorFrames an array of NSCursor, representing the frames of an animated cursor, in the + order they should be played. + @param aType the corresponding nsCursor constant + @result an instance of nsCocoaCursor that will animate the given cursor frames + */ +- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType; + +/*! @method initWithCursor: + @abstract Create a cursor by specifying a Cocoa NSCursor. + @discussion Creates a cursor representing the given Cocoa built-in cursor. + @param aCursor the NSCursor to use + @param aType the corresponding nsCursor constant + @result an instance of nsCocoaCursor representing the given NSCursor +*/ +- (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 +[NSImage imageNamed:] method. +

The image must be compatible with any restrictions laid down by NSCursor. These vary + by operating system version.

+

The hotspot precisely determines the point where the user clicks when using the cursor.

+ @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 nsCursor constant + @result an instance of nsCocoaCursor 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 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 mDockMenu; + nsString mBadgeText; + + NSImage *mAppIcon, *mProgressBackground; + + HIRect mProgressBounds; + nsTaskbarProgressState mProgressState; + double mProgressFraction; + nsCOMPtr mProgressTimer; + RefPtr 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 + +#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 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(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(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 + +#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(((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(((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 callback = aCallback; + + NSString* tempString = [NSString stringWithCharacters:reinterpret_cast(((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 + +#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 mAboutItemContent; + nsCOMPtr mPrefItemContent; + nsCOMPtr 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> 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 + +#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 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(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 stringBundle; + + nsCOMPtr 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(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(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 domDoc(do_QueryInterface(sLastGeckoMenuBarPainted->mContent->OwnerDoc())); + if (!domDoc) + return 0; + + NS_ConvertASCIItoUTF16 shortcutIDStr((const char *)shortcutID); + nsCOMPtr shortcutElement; + domDoc->GetElementById(shortcutIDStr, getter_AddRefs(shortcutElement)); + nsCOMPtr 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 menuItem; + inDoc->GetElementById(inID, getter_AddRefs(menuItem)); + nsCOMPtr 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 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 doc = inMenu->Content()->GetUncomposedDoc(); + if (!doc) { + return nil; + } + + nsCOMPtr domdoc(do_QueryInterface(doc)); + if (!domdoc) { + return nil; + } + + // Get information from the gecko menu item + nsAutoString label; + nsAutoString modifiers; + nsAutoString key; + nsCOMPtr 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 keyElement; + domdoc->GetElementById(key, getter_AddRefs(keyElement)); + if (keyElement) { + nsCOMPtr 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(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(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: + + + + 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(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 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(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 + +#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 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 + +#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, nsChangeObserver *> mContentToObserverTable; + + // stores mapping of command IDs to menu objects + nsDataHashtable 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 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 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 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 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 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 + +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 mContent; + RefPtr 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 to get the libstdc++ version. */ +#include +#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 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 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(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 cssValue; + nsCOMPtr cssStyleDecl; + nsCOMPtr primitiveValue; + uint16_t primitiveType; + if (!hasImageAttr) { + // If the content node has no "image" attribute, get the + // "list-style-image" property from CSS. + nsCOMPtr document = mContent->GetComposedDoc(); + if (!document) + return NS_ERROR_FAILURE; + + nsCOMPtr window = document->GetInnerWindow(); + if (!window) + return NS_ERROR_FAILURE; + + nsCOMPtr 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 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 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 document = mContent->OwnerDoc(); + + nsCOMPtr loadGroup = document->GetDocumentLoadGroup(); + if (!loadGroup) return NS_ERROR_FAILURE; + + RefPtr 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 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 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 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 + +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 mCommandContent; + // The icon object should never outlive its creating nsMenuItemX object. + RefPtr 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 parentDoc = mContent->OwnerDoc(); + + // get interface for creating DOM events from content owner document + nsCOMPtr domDoc = do_QueryInterface(parentDoc); + if (!domDoc) { + NS_WARNING("Failed to QI parent nsIDocument to nsIDOMDocument"); + return NS_ERROR_FAILURE; + } + + // create DOM event + nsCOMPtr 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 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 + +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 event = + doc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"), rv); + nsCOMPtr 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(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(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(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(aParent); + else + menuParent = static_cast(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(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 + +#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 aMenu); + void LoadMenuItem(nsIContent* inMenuItemContent); + void LoadSubMenu(nsIContent* inMenuContent); + GeckoNSMenu* CreateMenuWithGeckoString(nsString& menuTitle); + + nsTArray> mMenuObjectsArray; + nsString mLabel; + uint32_t mVisibleItemsCount; // cache + nsMenuObjectX* mParent; // [weak] + nsMenuGroupOwnerX* mMenuGroupOwner; // [weak] + // The icon object should never outlive its creating nsMenuX object. + RefPtr 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 + +#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 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 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 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 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 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(); + 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 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 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 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 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(mParent)->SetRebuild(true); + } + else if (parentType == eStandaloneNativeMenuObjectType) { + static_cast(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(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(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 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 +#import + +#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 + +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(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(kThemeMenuTypeInactive) : + static_cast(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(kThemeMenuDisabled) : + isSelected ? + static_cast(kThemeMenuSelected) : + static_cast(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 + +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 + +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 + +- (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 +#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 settingsX(do_QueryObject(aSettings)); + if (!settingsX) + return NS_ERROR_FAILURE; + + nsCOMPtr 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(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 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 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 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 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 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 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 + +#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(aPS); + if (!printSettingsX) + return NS_ERROR_UNEXPECTED; + *this = *printSettingsX; + return NS_OK; +} + +PMPrintSettings +nsPrintSettingsX::GetPMPrintSettings() +{ + return static_cast([mPrintInfo PMPrintSettings]); +} + +PMPrintSession +nsPrintSettingsX::GetPMPrintSession() +{ + return static_cast([mPrintInfo PMPrintSession]); +} + +PMPageFormat +nsPrintSettingsX::GetPMPageFormat() +{ + return static_cast([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 + +// 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 +#include +#include +#include +#include +#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 + +#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 + +#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 > 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 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(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 + +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 uri(do_QueryInterface(aURL)); + nsCOMPtr 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(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 + +#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 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(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(indexString.BeginReading()) + length:indexString.Length()]; + NSMenu * menu = static_cast (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(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(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 mMenu; + NSStatusItem* mStatusItem; + }; + + nsClassHashtable 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 + +#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 menu = new nsStandaloneNativeMenu(); + nsresult rv = menu->Init(aDOMElement); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr 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(&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 +#import +#import +#import + +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 +#include +#include + +#include +#include +#include + +extern "C" { +#include +} +#include +#include + +#import +#import +#import + +#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 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, ¬ifyPortRef, 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 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 + +// 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 @@ + + + + + IBDocumentLocation + 159 127 356 240 0 0 1920 1178 + IBEditorPositions + + 29 + 413 971 130 44 0 0 1920 1178 + + IBFramework Version + 443.0 + IBOpenObjects + + 29 + + IBSystem Version + 8F46 + + diff --git a/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib b/widget/cocoa/resources/MainMenu.nib/keyedobjects.nib new file mode 100644 index 0000000000000000000000000000000000000000..16b3f7e523ed223596981d8b5ce48d7c05993cf3 GIT binary patch literal 1877 zcmZWpYitx{6rJw{<=Jh4Sj#so!zfECM2B&0E52thPzkRXP7XJ#P=_s4#Zx%b?2&b>4B zE5fp-JDukcCY1H)*}K{yMuW@bMk(rZz@H|oN~J?rSNI%i{c>tnsEZu!w`KO}JMFQVPZk!HxEC&}35J%fefpUV&u!fN;!7{D zef9MnJKx&9=iR;g4}N^;@X-_BoIG{z`;#N**w9kbUrwp~Hrc z7&$5>b@Z4t$JlY>Crq4_J~?B`RHwJ}0}*W-BfzRD-JcP$pEIXP;jTU5d|@*1{72 zV?k+KsVfh>e^5@+7eOsahw!rU7Tz0TDm-nTN~2IbCO;Y#o4g>%SRt;;-{$pI^S(Th z59O)n0*mdnbx(=)5K$@XVF{cxOaX&jk`gP{rHID+@f1Fi_xHI(Sut(_MJj*C2ME1? zl$SSz0}(~Fukc2->R98d)lF*v!w3TxU5XS@6**X{2J37|=Ib+eWlU~#sUerBHGt!u zSbYRyVOORUN1&@98s@=_GK9*jrI2|KrsfCDcJxhN*WKNnEZ77AB-nT|PvUkyiVvxE zscYnLSS*jK#v5CH_E~%8I=&vfsE{-@77a>fzX$R`d;sV*VXf`~gTiG}upuHw8^FWn zO>LW=hsMHKTkU#~4^||g zK*&7%XYuujzuo8bRztH5Xc!rI_NBl5t7Bgu=aYCkpUg9Osx8}wY$I(m;n}z59L}?! ziRYg+*PPr77h5kHV_95a&HfztR0;_~k}zCw2or@&VUAENlnQ_(WhJHtX zqTA>Wx{L1P-ncLBj|bq9I2~u;Oq`30a0w1z2`gB`jkpZC-1Nb05gpc48 zxC5WX=kNu58F%4d@n85J?k0#3!bneIBST0ENhcX(Dw#&6lUXF66q31Q9tn^jSxK77 zRewdh15eA zQ7>Iio9G6*k#3@!=@z<`Zll}j9@* 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,7 +645,14 @@ 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) { @@ -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 WriteParam(aMsg, static_cast (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 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(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& +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& 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& aDriverInfo, + OperatingSystem* aOS = nullptr); + virtual const nsTArray& 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 +#include +#include + +@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 +#import +#import +#import + +#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 { +} +@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 (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 +#import + +#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 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 + +#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& 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 mChildren; + nsWindow* mParent; + InputContext mInputContext; + + void OnSizeChanged(const mozilla::gfx::IntSize& aSize); + + static void DumpWindows(); + static void DumpWindows(const nsTArray& 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 +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#include + +#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(value); + RefPtr 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 targetSurface; + + RefPtr 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 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& 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(mSizeMode)) { + return; + } + + mSizeMode = static_cast(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 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 + +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 #include @@ -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 + +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(::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 +#include +#include +#include +#include + +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(&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 @@ -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 027d36f10d..4cfc11e9bf 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 #include @@ -40,6 +44,10 @@ #include #endif +#ifdef XP_MACOSX +#include +#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, @@ -410,6 +418,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(sysctlValue64/1000000); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.physicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + physicalCPUs = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.logicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + logicalCPUs = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l2cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL2 = static_cast(sysctlValue64/1024); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l3cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL3 = static_cast(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(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.model", &sysctlValue32, &len, NULL, 0)) { + cpuModel = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.stepping", &sysctlValue32, &len, NULL, 0)) { + cpuStepping = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + #elif defined(XP_LINUX) // Get vendor, family, model, stepping, physical cores, L3 cache size // from /proc/cpuinfo file @@ -586,6 +655,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 #include +#elif defined(XP_MACOSX) +#include #else #include #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 +#elif defined(XP_MACOSX) +#include #elif defined(XP_UNIX) #include #include @@ -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..dca9ba0297 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerMac.cpp @@ -0,0 +1,385 @@ +/* -*- 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/ProcessedStack.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "nsTraceRefcnt.h" +#include "plstr.h" +#include "prio.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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 +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 }; \ + +// 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 +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 }; \ + +// 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 +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 }; \ + +// 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 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 76521e92c4..40bc92af15 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -1010,6 +1010,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 +#include +#include +#include +#include + +#include + +/************************** +* +* 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; ilength; 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: + Some rights reserved: + + ***************************************************************************/ + +/***************************************************************************//** + @mainpage mach_override + @author Jonathan 'Wolf' Rentzsch: + + 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 +#include + +#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 #endif +#ifdef XP_MACOSX +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#elif defined(XP_UNIX) #include #include #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 41e319ea5f..9128509152 100644 --- a/xpcom/glue/nsThreadUtils.cpp +++ b/xpcom/glue/nsThreadUtils.cpp @@ -21,6 +21,8 @@ #include #include "mozilla/WindowsVersion.h" using mozilla::IsVistaOrLater; +#elif defined(XP_MACOSX) +#include #endif #include @@ -443,6 +445,12 @@ nsAutoLowPriorityIO::nsAutoLowPriorityIO() lowIOPrioritySet = IsVistaOrLater() && 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 @@ -455,5 +463,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 + +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 +#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 +#include +%} + + 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 #include @@ -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 +#include +#include +#include +#endif #include #include #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 { } @@ -240,16 +261,34 @@ nsProcess::Monitor(void* aArg) return; } } +#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(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 +#include +#endif + #ifdef MOZ_CANARY # include # include @@ -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 868bd78123..f2497d0e03 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 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 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 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.)